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