Binding to Anonymous types in Silverlight

If you’ve ever tried to bind to anonymous type in Silverlight (or a WPF non full-trust XBAP) you will have wound up running into a pretty nasty gotcha. It seems like a natural enough thing to do, for example:

public partial class MainPage : UserControl
    {
        IList types;

        public MainPage()
        {
            InitializeComponent();

            types = (from type in
                            Assembly
                            .GetExecutingAssembly()
                            .GetTypes()
                        select new
                        {
                            Name = type.Name,
                            BaseTypeName = type.BaseType.Name
                        }).ToList();

            DataContext = types;
        }
    }

It seems like this should work, right? If you try it, though, you will see a whole lot of nothing and a bunch of MethodAccessExceptions in your output log.

Why does this happen?

This happens because the way Anonymous types are implemented is that, at compile time, the compiler will determine what type needs to be generated in the assembly based on the property names and inferred types and will do the necessary generation. The problem is, though, that this type is marked internal!

This would not be so bad except for the fact that the Silverlight and WPF binding system need to reflect against the type in order to set up a binding, which would be fine if you were doing that from inside you own assembly, but it is the binding system that is going to be doing the reflection, from System.Windows.

The immediate answer then, that should occur to you, is that you can make your assemblies internals visible to System.Windows.

[assembly: InternalsVisibleTo("System.Windows,
PublicKey=00240000048000009400000006020000002400
005253413100040000010001008D56C76F9E8649383049
F383C44BE0EC204181822A6C31CF5EB7EF486944D0321
88EA1D3920763712CCB12D75FB77E9811149E6148E5D
32FBAAB37611C1878DDC19E20EF135D0CB2CFF2BFEC
3D115810C3D9069638FE4BE215DBF795861920E5AB6F
7DB2E2CEEF136AC23D5DD2BF031700AEC232F6C6B1
C785B4305C123B37AB", AllInternalsVisible=false)]

And indeed this will work (line breaks in the public key above added for clarity). This was gleaned from reflectoring some of the system Silverlight assemblies that need to make their internals visible to System.Windows (where the binding engine sits).

Ok, so this is all well and good, if you want to display read only information from a linq query, as Anonymous types are immutable! But what if we want to harness the easy definition of these anonymous types as a template for mutable types, and what if we want these types to be public so we don’t need to bother with exposing our internals? Well, I was curious as to how hard it would be to adapt the code I wrote recently for generating dynamic types to implement INotifyPropertyChanged to generate types that had the same properties as an anonymous type except read-write and public.

Well, luckily enough for you, if you are interested in this stuff, I did it! I won’t go into a lot of detaills for how this is done as it follows the general pattern of my previous post on type generation where I explained in more detail, but the general gist is that it will reflect against the anonymous type (which it can do since it is in the same assembly, if you put this code in a different assembly you will need to InternalsVisibleTo it) and for each property there found, make a writable version on the type it is generating. This writable property will also be augmented to raise a PropertyChanged event for INotifyPropertyChanged (bonus!!), allowing for some pretty interesting scenarios.

Here’s how the usage would look:

    public partial class MainPage : UserControl
    {
        IList types;

        public MainPage()
        {
            InitializeComponent();

            types = (from type in
                            Assembly
                            .GetExecutingAssembly().GetTypes()
                        select new
                        {
                            Name = type.Name,
                            BaseTypeName = type.BaseType.Name
                        }.MakePublicAndMutable()).ToList();

            DataContext = types;
        }
    }

And here’s the code that does the dynamic type generation:

    public static class Extensions
    {
        public static object MakePublicAndMutable(this object obj)
        {
            return TypeGenerator.MakePublicAndMutable(obj);
        }
    }

    public class TypeGenerator
    {
        //note, not threadsafe currently ;-)
        private static AssemblyBuilder _ab;
        private static ModuleBuilder _mb;
        private static Dictionary<Type, Type> types =
            new Dictionary<Type, Type>();

        private static IEnumerable<PropertyInfo> GetProperties(
            Type type)
        {
            return from p in
                       type.GetProperties(
                           BindingFlags.Public |
                           BindingFlags.Instance |
                           BindingFlags.FlattenHierarchy)
                   select p;
        }

        public static object MakePublicAndMutable(object item)
        {
            List<object> parms = new List<object>();

            foreach (PropertyInfo prop in GetProperties(item.GetType()))
            {
                parms.Add(prop.GetValue(item, null));
            }

            Type type = GetAnonTypeProxyType(item.GetType());
            ConstructorInfo mi = type.GetConstructor(
                (from parm in parms select parm.GetType()).ToArray());

            return Activator.CreateInstance(type, parms.ToArray());
        }

        protected static Type GetAnonTypeProxyType(Type type)
        {
            Type ret;
            if (types.TryGetValue(type, out ret))
            {
                return ret;
            }

            if (_ab == null)
            {
                AssemblyName assmName = new AssemblyName("DynamicAssembly");
                _ab = AppDomain.CurrentDomain.DefineDynamicAssembly(
                    assmName, AssemblyBuilderAccess.Run);
                _mb = _ab.DefineDynamicModule(assmName.Name);
            }

            TypeBuilder typeBuilder = _mb.DefineType(
            Guid.NewGuid().ToString() + "__proxy", TypeAttributes.Public);
            typeBuilder.AddInterfaceImplementation(
                typeof(INotifyPropertyChanged));

            FieldBuilder eventField =
                CreatePropertyChangedEvent(typeBuilder);

            MethodBuilder raisePropertyChanged =
                CreateRaisePropertyChanged(typeBuilder, eventField);

            var props = GetProperties(type);
            Dictionary<string, FieldBuilder> fields =
                new Dictionary<string, FieldBuilder>();

            CreateProperties(props, typeBuilder, fields,
                raisePropertyChanged);
            CreateDefaultConstructor(typeBuilder);
            CreateConstructor(props, typeBuilder, fields);

            ret = typeBuilder.CreateType();
            types.Add(type, ret);
            return ret;
        }

        private static MethodBuilder CreateRaisePropertyChanged(
            TypeBuilder typeBuilder, FieldBuilder eventField)
        {
            MethodBuilder raisePropertyChangedBuilder =
                typeBuilder.DefineMethod(
                "RaisePropertyChanged",
                MethodAttributes.Family | MethodAttributes.Virtual,
                null, new Type[] { typeof(string) });

            ILGenerator raisePropertyChangedIl =
            raisePropertyChangedBuilder.GetILGenerator();
            Label labelExit = raisePropertyChangedIl.DefineLabel();

            // if (PropertyChanged == null)
            // {
            //      return;
            // }
            raisePropertyChangedIl.Emit(OpCodes.Ldarg_0);
            raisePropertyChangedIl.Emit(OpCodes.Ldfld, eventField);
            raisePropertyChangedIl.Emit(OpCodes.Ldnull);
            raisePropertyChangedIl.Emit(OpCodes.Ceq);
            raisePropertyChangedIl.Emit(OpCodes.Brtrue, labelExit);

            // this.PropertyChanged(this,
            // new PropertyChangedEventArgs(propertyName));
            raisePropertyChangedIl.Emit(OpCodes.Ldarg_0);
            raisePropertyChangedIl.Emit(OpCodes.Ldfld, eventField);
            raisePropertyChangedIl.Emit(OpCodes.Ldarg_0);
            raisePropertyChangedIl.Emit(OpCodes.Ldarg_1);
            raisePropertyChangedIl.Emit(OpCodes.Newobj,
                typeof(PropertyChangedEventArgs)
                .GetConstructor(new[] { typeof(string) }));
            raisePropertyChangedIl.EmitCall(OpCodes.Callvirt,
                typeof(PropertyChangedEventHandler)
                .GetMethod("Invoke"), null);

            // return;
            raisePropertyChangedIl.MarkLabel(labelExit);
            raisePropertyChangedIl.Emit(OpCodes.Ret);

            return raisePropertyChangedBuilder;
        }

        private static FieldBuilder CreatePropertyChangedEvent(
            TypeBuilder typeBuilder)
        {
            // public event PropertyChangedEventHandler PropertyChanged;
            FieldBuilder eventField =
                typeBuilder.DefineField("PropertyChanged",
                typeof(PropertyChangedEventHandler),
                FieldAttributes.Private);
            EventBuilder eventBuilder =
                typeBuilder.DefineEvent(
                "PropertyChanged",
                EventAttributes.None,
                typeof(PropertyChangedEventHandler));

            eventBuilder.SetAddOnMethod(
            CreateAddRemoveMethod(typeBuilder, eventField, true));
            eventBuilder.SetRemoveOnMethod(
            CreateAddRemoveMethod(typeBuilder, eventField, false));

            return eventField;
        }

        private static MethodBuilder CreateAddRemoveMethod(
            TypeBuilder typeBuilder,
            FieldBuilder eventField, bool isAdd)
        {
            string prefix = "remove_";
            string delegateAction = "Remove";
            if (isAdd)
            {
                prefix = "add_";
                delegateAction = "Combine";
            }
            MethodBuilder addremoveMethod =
            typeBuilder.DefineMethod(prefix + "PropertyChanged",
               MethodAttributes.Public |
               MethodAttributes.SpecialName |
               MethodAttributes.NewSlot |
               MethodAttributes.HideBySig |
               MethodAttributes.Virtual |
               MethodAttributes.Final,
               null,
               new[] { typeof(PropertyChangedEventHandler) });
            MethodImplAttributes eventMethodFlags =
                MethodImplAttributes.Managed |
                MethodImplAttributes.Synchronized;
            addremoveMethod.SetImplementationFlags(
                eventMethodFlags);

            ILGenerator ilGen = addremoveMethod.GetILGenerator();

            // PropertyChanged += value; // PropertyChanged -= value;
            ilGen.Emit(OpCodes.Ldarg_0);
            ilGen.Emit(OpCodes.Ldarg_0);
            ilGen.Emit(OpCodes.Ldfld, eventField);
            ilGen.Emit(OpCodes.Ldarg_1);
            ilGen.EmitCall(OpCodes.Call,
                typeof(Delegate).GetMethod(
                delegateAction,
                new[] { typeof(Delegate), typeof(Delegate) }),
                null);
            ilGen.Emit(OpCodes.Castclass, typeof(
            PropertyChangedEventHandler));
            ilGen.Emit(OpCodes.Stfld, eventField);
            ilGen.Emit(OpCodes.Ret);

            MethodInfo intAddRemoveMethod =
            typeof(INotifyPropertyChanged).GetMethod(
            prefix + "PropertyChanged");
            typeBuilder.DefineMethodOverride(
            addremoveMethod, intAddRemoveMethod);

            return addremoveMethod;
        }

        private static void CreateDefaultConstructor(
           TypeBuilder typeBuilder)
        {
            ConstructorBuilder cons = typeBuilder.DefineConstructor(
                MethodAttributes.Public,
                CallingConventions.Standard,
                null);
            ILGenerator ilGen = cons.GetILGenerator();
            ilGen.Emit(OpCodes.Ldarg_0);
            ilGen.Emit(OpCodes.Call,
                typeof(object).GetConstructor(new Type[0]));
            ilGen.Emit(OpCodes.Ret);
        }

        private static void CreateConstructor(
            IEnumerable<PropertyInfo> props,
            TypeBuilder typeBuilder,
            Dictionary<string, FieldBuilder> fields)
        {
            var paramTypes = from p in props select p.PropertyType;
            ConstructorBuilder cons = typeBuilder.DefineConstructor(
                MethodAttributes.Public,
                CallingConventions.Standard,
                paramTypes.ToArray());
            ILGenerator ilGen = cons.GetILGenerator();
            ilGen.Emit(OpCodes.Ldarg_0);
            ilGen.Emit(OpCodes.Call,
                typeof(object).GetConstructor(new Type[0]));

            int count = 1;
            foreach (PropertyInfo info in props)
            {
                ilGen.Emit(OpCodes.Ldarg_0);
                ilGen.Emit(OpCodes.Ldarg, count);
                ilGen.Emit(OpCodes.Stfld, fields[info.Name]);
                count++;
            }

            ilGen.Emit(OpCodes.Ret);
        }

        private static void CreateProperties(
            IEnumerable<PropertyInfo> props,
            TypeBuilder typeBuilder,
            Dictionary<string, FieldBuilder> fields,
            MethodBuilder raisePropertyChanged)
        {
            foreach (PropertyInfo info in props)
            {
                PropertyBuilder pb = typeBuilder.DefineProperty(
                    info.Name, PropertyAttributes.None,
                    info.PropertyType,
                    (from p in info.GetIndexParameters()
                     select p.ParameterType).ToArray());

                FieldBuilder backingField = typeBuilder.DefineField(
                    "_" + info.Name,
                    info.PropertyType,
                    FieldAttributes.Private);
                fields.Add(info.Name, backingField);

                MethodInfo getMethod = info.GetGetMethod();
                if (getMethod != null)
                {
                    pb.SetGetMethod(
                        CreateGetMethod(info, getMethod,
                        backingField, typeBuilder));

                    pb.SetSetMethod(
                        CreateSetMethod(info, getMethod, backingField,
                        typeBuilder, raisePropertyChanged));
                }
            }
        }

        private static MethodBuilder CreateGetMethod(
            PropertyInfo info,
            MethodInfo toWrap,
            FieldBuilder backingField,
            TypeBuilder typeBuilder)
        {
            MethodInfo orig = toWrap;
            var paramTypes = (from p in orig.GetParameters()
                              select p.ParameterType).ToArray();
            MethodBuilder mb = typeBuilder.DefineMethod(
                orig.Name,
                MethodAttributes.Public |
                MethodAttributes.HideBySig,
                orig.ReturnType, paramTypes);

            ILGenerator ilGen = mb.GetILGenerator();
            ilGen.Emit(OpCodes.Ldarg_0);
            ilGen.Emit(OpCodes.Ldfld, backingField);
            ilGen.Emit(OpCodes.Ret);

            return mb;
        }

        private static MethodBuilder CreateSetMethod(
            PropertyInfo info,
            MethodInfo toWrap,
            FieldBuilder backingField,
            TypeBuilder typeBuilder,
            MethodBuilder raisePropertyChanged)
        {
            MethodInfo orig = toWrap;
            var origParamTypes = (from p in orig.GetParameters()
                                  select p.ParameterType).ToArray();
            List<Type> paramTypes = new List<Type>();
            if (orig.ReturnType != typeof(void))
            {
                paramTypes.Add(orig.ReturnType);
            }
            paramTypes.AddRange(origParamTypes);

            MethodBuilder mb = typeBuilder.DefineMethod(
                "set_" + info.Name,
                MethodAttributes.Public |
                MethodAttributes.HideBySig,
                null, paramTypes.ToArray());

            ILGenerator ilGen = mb.GetILGenerator();
            ilGen.Emit(OpCodes.Ldarg_0);
            ilGen.Emit(OpCodes.Ldarg_1);
            ilGen.Emit(OpCodes.Stfld, backingField);

            // RaisePropertyChanged("[PropertyName]");
            ilGen.Emit(OpCodes.Ldarg_0);
            ilGen.Emit(OpCodes.Ldstr, info.Name);
            ilGen.EmitCall(
            OpCodes.Call, raisePropertyChanged, null);

            ilGen.Emit(OpCodes.Ret);

            return mb;
        }
    }

Again see my previous post for some Reflection.Emit details if you don’t understand the above. And feel free to shoot me some questions!

About these ads

6 Responses to Binding to Anonymous types in Silverlight

  1. Superb! An astonishingly simple solution to anonymous type binding. I really wish I had thought of it. ;)

  2. Adam Sills says:

    Silverlight Anonymous Type Binding Gotcha…

    When Silverlight databinding evaluates a binding expression, ……

  3. Fallon says:

    This is nice!

    I modified it to have the option of passing in a type name, instead of your guid proxy.

    Thanks!

  4. Julio says:

    Is not working anymore, Siverlight restrict prop.GetValue(item, null); you cant acces

    • Graham Murray says:

      Julio, you probably just didn’t have the above code in the same assembly as the anonymous types you were trying to expose. Anonymous types are internal, so to reflect against them you need to be in the same assembly.

Leave a Reply

Fill in your details below or click an icon to log in:

WordPress.com Logo

You are commenting using your WordPress.com account. Log Out / Change )

Twitter picture

You are commenting using your Twitter account. Log Out / Change )

Facebook photo

You are commenting using your Facebook account. Log Out / Change )

Google+ photo

You are commenting using your Google+ account. Log Out / Change )

Connecting to %s

Follow

Get every new post delivered to your Inbox.

%d bloggers like this: