Search code examples
c#wpfdynamicframeworkelement

Common Interface for FrameworkElement and FrameworkContentElement with dynamic programming


I try to cut it short: FrameworkElement and FrameworkContentElement share a lot of the same API but do not have a common Interface. Only DependencyObject as a base class.

I've come across this implementation of IFrameworkElement, which manually adds an interface and two wrapper classes. Now this code comes is implemented in .NET 3.5 and the author remarks that it would be a lot easier to do with dynamic programming in .NET 4:

The actual code is very simple, but about 624 lines long for each element. (It will be a much simpler one line implementation in a dynamic language - C# 4.0 is coming :) ).

I would be very curious how an implementation of that would look like. I assume it would boil down to a dynamic implementation of IFrameworkElement, and read about ExpandoObject and DynamicObject to see if I could implement myself, but I'm a bit stumped. I guess one can write a custom implementation of DynamicObject - but that isn't a one liner. Could this be really made that easy with dynamic programming? I doesn't even need to be a one liner, I'd be fine with 10 or even 100 lines instead of the original 1250.

I'm thinking something like this:

// 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";

Solution

  • I do not know what exactly the author of that article meant (maybe we should ask him/her), but I guess it needs a little bit more than one line of code. The point is that the dynamic keyword (new with 4.0 .NET) allows the so called duck typing.

    The article's author had to write 2 wrapper classes in order to make FrameworkElement and FrameworkContentElement implement the IFrameworkElement interface.

    Now with dynamic keywork we can write just class (for maintaining the comfort of our interface).

    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;
        }
    }
    

    So now we can write in our code something like:

    System.Windows.Controls.Button b = new System.Windows.Controls.Button();
    IFrameworkElement ife = b.AsIFrameworkElement();
    
    ife.DataContext = "it works!";
    
    Debug.Assert(b.DataContext == ife.DataContext);
    

    Now if you do not want to write your wrapper (or proxy as you wish) class (i.e. FrameworkElementImpl in our sample), there are some libraries that do it for you (impromptu-interface or Castle DynamicProxy).

    You can find here a very simple example which makes use of 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);
        }
    }
    

    As you can see, in this case, with a few lines of code you can obtain the same result that the author obtained with

    about 624 lines [...] for each element