Общий интерфейс для FrameworkElement и FrameworkContentElement с динамическим программированием

Я пытаюсь сократить это: FrameworkElement и FrameworkContentElement во многом используют один и тот же API, но не имеют общего интерфейса. Только DependencyObject в качестве базового класса.

Я наткнулся на эту реализацию IFrameworkElement , который вручную добавляет интерфейс и два класса-оболочки. Теперь этот код реализован в .NET 3.5, и автор отмечает, что это было бы намного проще сделать с помощью динамического программирования в .NET 4:

Фактический код очень прост, но содержит около 624 строк для каждого элемента. (Это будет гораздо более простая однострочная реализация на динамическом языке - грядет C# 4.0 :)).

Мне было бы очень любопытно, как будет выглядеть реализация этого. Я предполагаю, что это сведется к динамической реализации IFrameworkElement, и прочитал о ExpandoObject и DynamicObject, чтобы посмотреть, смогу ли я реализовать себя, но я немного в тупике. Я предполагаю, что можно написать собственную реализацию DynamicObject, но это не одна строка. Можно ли сделать это так проще с помощью динамического программирования? Мне даже не нужно быть однострочным, мне будет достаточно 10 или даже 100 строк вместо исходных 1250.

Я думаю примерно так:

// Example, not working:
public IFrameworkElement AsFrameworkElement(FrameworkElement ele)
{
  dynamic ife = ele as IFrameworkElement;
  return ife;    
}

IFrameworkElement frameworkElement = AsFrameworkElement(new Button());
frameworkElement.DataContext = "Whatever";

IFrameworkElement frameworkContentElement = AsFrameworkElement(new Paragraph());
frameworkContentElement.DataContext = "Whatever again";

person Lennart    schedule 05.08.2016    source источник
comment
Что с фрагментами кода на этом сайте? Они пересекаются для меня.   -  person Botonomous    schedule 05.08.2016


Ответы (2)


Я не знаю, что именно имел в виду автор этой статьи (может быть, мы должны спросить его / ее), но я думаю, что для этого нужно чуть больше, чем одна строка кода. Дело в том, что динамическое ключевое слово (новое в 4.0 .NET) позволяет так называемый утиный ввод.

Автору статьи пришлось написать 2 класса-оболочки, чтобы FrameworkElement и FrameworkContentElement реализовали интерфейс IFrameworkElement.

Теперь с помощью dynamic ключей мы можем написать только класс (для поддержания удобства нашего интерфейса).

public interface IFrameworkElement
{
    /* Let's suppose we have just one property, since it is a sample */
    object DataContext
    {
        get;
        set;
    }
}

public class FrameworkElementImpl : IFrameworkElement
{
    private readonly dynamic target;

    public FrameworkElementImpl(dynamic target)
    {
        this.target = target;
    }

    public object DataContext
    {
        get
        {
            return target.DataContext;
        }
        set
        {
            target.DataContext = value;
        }
    }
}

public static class DependencyObjectExtension
{
    public static IFrameworkElement AsIFrameworkElement(this DependencyObject dp)
    {
        if (dp is FrameworkElement || dp is FrameworkContentElement)
        {
            return new FrameworkElementImpl(dp);
        }

        return null;
    }
}

Итак, теперь мы можем написать в нашем коде что-то вроде:

System.Windows.Controls.Button b = new System.Windows.Controls.Button();
IFrameworkElement ife = b.AsIFrameworkElement();

ife.DataContext = "it works!";

Debug.Assert(b.DataContext == ife.DataContext);

Теперь, если вы не хотите писать свой класс-оболочку (или прокси, как хотите) (например, FrameworkElementImpl в нашем примере), есть несколько библиотек, которые сделают это за вас (импровизированный-интерфейс или Castle DynamicProxy).

Вы можете найти здесь очень простой пример, который использует Castle DynamicProxy:

public class Duck
{
    public void Quack()
    {
        Console.WriteLine("Quack Quack!");
    }

    public void Swim()
    {
        Console.WriteLine("Swimming...");
    }
}

public interface IQuack
{
    void Quack();
}

public interface ISwimmer
{
    void Swim();
}

public static class DuckTypingExtensions
{
    private static readonly ProxyGenerator generator = new ProxyGenerator();

    public static T As<T>(this object o)
    {
        return generator.CreateInterfaceProxyWithoutTarget<T>(new DuckTypingInterceptor(o));
    }
}

public class DuckTypingInterceptor : IInterceptor
{
    private readonly object target;

    public DuckTypingInterceptor(object target)
    {
        this.target = target;
    }

    public void Intercept(IInvocation invocation)
    {
        var methods = target.GetType().GetMethods()
            .Where(m => m.Name == invocation.Method.Name)
            .Where(m => m.GetParameters().Length == invocation.Arguments.Length)
            .ToList();
        if (methods.Count > 1)
            throw new ApplicationException(string.Format("Ambiguous method match for '{0}'", invocation.Method.Name));
        if (methods.Count == 0)
            throw new ApplicationException(string.Format("No method '{0}' found", invocation.Method.Name));
        var method = methods[0];
        if (invocation.GenericArguments != null && invocation.GenericArguments.Length > 0)
            method = method.MakeGenericMethod(invocation.GenericArguments);
        invocation.ReturnValue = method.Invoke(target, invocation.Arguments);
    }
}

Как видите, в этом случае с помощью нескольких строк кода можно получить тот же результат, что автор получил с помощью

около 624 строк [...] для каждого элемента

person Il Vic    schedule 14.08.2016

Посмотрите на исходный код блога:

var dataContect = "DataContext";
var frameworkElement = sender as FrameworkElement;
if ( frameworkElement != null )
{
    frameworkElement.DataContext = dataContect;
}
else
{
    var frameworkContentElement = sender as FrameworkContentElement;
    if ( frameworkContentElement != null )
    {
        frameworkContentElement.DataContext = dataContect;
    }
}

Это станет

var dataContext = "DataContext"
dynamic element = sender;
element.DataContext = dataContext;

Вот и все. Во время выполнения свойство с именем DataContext будет искаться отражением (будьте осторожны: когда дело доходит до списков динамических типов, все может ужасно замедлиться), а затем оно вызывается.

Примечание: если свойство не существует, будет выброшено RuntimeBinderException. Вы можете добавить попытку... поймать последнюю строку.

person Bernhard Hiller    schedule 08.08.2016
comment
Но это не даст мне удобства интерфейса IFrameworkElement. Конечно, я могу вызывать все элементы из динамической переменной, если знаю имя, но я думал, что смогу преобразовать два объекта, чтобы выбрать доступные члены из интерфейса. - person Lennart; 09.08.2016
comment
@Lennart Для удобства при написании кода используйте один конкретный класс, чтобы работал intellisense. После этого замените его на dynamic. - person Bernhard Hiller; 09.08.2016
comment
Это также не даст вам преимущества в производительности конкретных классов, но это правильный ответ. - person Jonathan Allen; 14.08.2016
comment
Вы думали о чем-то вроде IFrameworkElement = dynamic_cast(sender, typeof(IFrameworkElement));? Что ж, это было бы неплохо, но это не тот способ, которым Microsoft реализовала динамические функции. Возможно, мы могли бы предложить это им. - person Bernhard Hiller; 15.08.2016