locked
Adding properties at runtime to an object RRS feed

  • Question

  • I need to add properties to an existing object at runtime.  Our project has a MemberInfo object that has some standard properties(such as FirstName, LastName, etc...).  Our clients can go in and add Custom Fields in the database, we would like to be able to display a list of the members with the Custom Fields(and their values) in a grid.  We bind a list of objects to the grid and each property with a custom attribute will be displayed.

    Namespace: Company.Net.Library.Member
    Assembly: Company.Net.Library.Member.dll
    The stored procedure returns an xml blob like the following:
    (normal stuff like FirstName get read in the normal way)
    <CustomFields>
    <CustomField Name="HairColor">Brown</CustomField>
    <CustomField Name="ShoeSize">9</CustomField>
    (there could be additional child nodes)
    </CustomFields>

    In the class I read from the datareader and convert to a dictionary with the key being the property name(example:HairColor or ShoesSize) and the Value being the innertext(example Brown or 9)  That part seems to be working ok.  But what I need to do is add properties to the existing object.

    membersList is a List<MemberInfo>
    HasCustomFields is a boolean the will tell if there are custom fields(not all customers have custom fields)
    CustomFields is property of type Dictionary<string, string> that contains the above mentioned key, value pair.  All the added properties can be strings
    ObjectFieldAttribute is a custom attribute we defined(inherits from Attribute), the constructor takes three boolean.

    What I have so far:
    if (membersList.Count > 0)
    {
        if (membersList[0].HasCustomFields)
        {
            #region add property to class

            AppDomain domain = Thread.GetDomain();
            AssemblyName name = new AssemblyName();
            name.Name = "MemberInfo";

            AssemblyBuilder assBuilder = domain.DefineDynamicAssembly(name, AssemblyBuilderAccess.RunAndSave);
            ModuleBuilder modBuilder = assBuilder.DefineDynamicModule(name.Name, name.Name + ".dll");

            // just for testing
            int origCount = typeof(MemberInfo).GetProperties().Length;

            #region  add each property one at a time

            foreach (KeyValuePair<string, string> entry in membersList[0].CustomFields)
            {
                try
                {
                    TypeBuilder typeBuilder = modBuilder.DefineType(entry.Key, TypeAttributes.Public);
                    FieldBuilder fieldBuilder = typeBuilder.DefineField(entry.Key, typeof(string), FieldAttributes.Private);
                    PropertyBuilder propBuilder = typeBuilder.DefineProperty(entry.Key, PropertyAttributes.None, typeof(string), null);

                    // Create the custom attribute ObjectFieldAttribute
                    Type[] ctorParams = new Type[] { typeof(bool), typeof(bool), typeof(bool) };
                    ConstructorInfo constructor = typeof(ObjectFieldAttribute).GetConstructor(ctorParams);
                    CustomAttributeBuilder custAttBuilder = new CustomAttributeBuilder(constructor, new object[] { true, true, true });

                    //fieldBuilder.SetValue(info, entry.Value);
                    propBuilder.SetCustomAttribute(custAttBuilder);

                    MethodAttributes getSetAttr = MethodAttributes.Public | MethodAttributes.SpecialName | MethodAttributes.HideBySig;

                    // create the get method of the property
                    MethodBuilder getMethodBuilder = typeBuilder.DefineMethod("get_" + entry.Key, getSetAttr, typeof(string), Type.EmptyTypes);

                    ILGenerator getILgen = getMethodBuilder.GetILGenerator();
                    getILgen.Emit(OpCodes.Ldarg_0);
                    getILgen.Emit(OpCodes.Stfld, fieldBuilder);
                    getILgen.Emit(OpCodes.Ret);

                    propBuilder.SetGetMethod(getMethodBuilder);

                    // create the set method of the property
                    MethodBuilder setMethodBuilder = typeBuilder.DefineMethod("set_" + entry.Key, getSetAttr, null, new Type[] { typeof(string) });

                    ILGenerator setILgen = setMethodBuilder.GetILGenerator();
                    setILgen.Emit(OpCodes.Ldarg_0);
                    setILgen.Emit(OpCodes.Ldarg_1);
                    setILgen.Emit(OpCodes.Stfld, fieldBuilder);
                    setILgen.Emit(OpCodes.Ret);

                    propBuilder.SetGetMethod(setMethodBuilder);

                    Type generated = typeBuilder.CreateType();

                    PropertyInfo[] properties = generated.GetProperties();
                }
                catch (Exception ex)
                {

                }
            }

            // just for testing
            int newCount = typeof(MemberInfo).GetProperties().Length;

            // erroring out for some reason
            //assBuilder.Save(name.Name + ".dll");

            #endregion

            #endregion

            #region Add values to the new properties

            for (int i = 0; i < membersList.Count; i++)
            {
                MemberInfo info = membersList[i];
                if (info.HasCustomFields)
                {
                    foreach (KeyValuePair<string, string> entry in info.CustomFields)
                    {
                        // populate each new field with data
                    }
                }
            }

            #endregion
        }
    }
    Monday, November 23, 2009 3:03 PM

Answers

  • "That part seems to be working ok.  But what I need to do is add properties to the existing object."

    This is actually very, very difficult to do.  Part of the problem is that the object will be defined at compile time, so changing it at runtime requires code generation.  You seem to be going down this approach, but there is a distinct problem with that - once you've created the new type, the classes that use that type either have to use reflection to work with it, or be runtime-generated as well, since the classes in your project won't know about the members on this new type.

    C# 4 has some nice things to handle this directly - for example, ExpandoObject makes this very easy - you just set the properties on the type, and it dynamically works without any code changes.

    Personally, in C# <=3.5, I would just use a dictionary, and set values in a Dictionary<string, object>.  This has many advantages, including simplicity, understandability, and will likely be much easier to develop and maintain.


    Reed Copsey, Jr. - http://reedcopsey.com
    • Marked as answer by Bin-ze Zhao Thursday, November 26, 2009 8:45 AM
    Wednesday, November 25, 2009 5:03 PM