Общий интерфейс для 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";

2 ответа

Решение

Я не знаю, что именно имел в виду автор этой статьи (возможно, мы должны спросить его / ее), но я думаю, что для этого нужно чуть больше одной строки кода. Дело в том, что ключевое слово dynamic (впервые в версии 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 строк [...] для каждого элемента

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

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 будет брошен. Вы можете добавить несколько попыток... поймать последнюю строчку.

Другие вопросы по тегам