Create new PropertyInfo object on the fly

MadGatsu picture MadGatsu · Mar 26, 2013 · Viewed 17.8k times · Source

This is my very first post, and although I have searched in topics related to my issue to some extent, I'm having a lot of trouble finding the proper answer.

My question may be very simple, but I'm aware that the answer might not be so easy to give. If any exists at all.

With that being said, this is my case: as an example, I have an array of PropertyInfo objects, which I am using to get the properties from a class, like this:

public PropertyInfo[] GetProperties (object o)
{
    PropertyInfo[] properties = o.GetType().GetProperties();

    return properties;
}

Looks easy, right? Now my problem is this: how to create a new PropertyInfo object and add it to the array?

I have seen other posts where users want to set the VALUE of a PropertyInfo, but that is not what I need. What I need is to create on the fly a new PropertyInfo object, where the only available data I have is the Name and the Type.

The test case I posted earlier is merely a small example of what I am trying to achieve. My true and final goal is, in fact, to be able to create a new PropertyInfo based on this class:

public class DynamicClass
{
    public Type ObjectType { get; set; }
    public List<string> PropertyNameList { get; set; }
    public List<Type> PropertyTypeList { get; set; }
}

I hope someone can help me achieve this. Many thanks in advance!

EDITED: I forgot to add o.GetType() before GetProperties() method. Thanks Ilya Ivanov!

I am calling the method SelectProperties like so:

list = _queriable.Select(SelectProperties).ToList();

The method looks like this:

private Expression<Func<T, List<string>>> SelectProperties
{
    get
    {
       return value => _properties.Select
                      (
                          prop => (prop.GetValue(value, new object[0]) ?? string.Empty).ToString()
                      ).ToList();
        }
    }

Best regards,

Luis


UPDATE:

Ok, so I am following 280Z28's advice and I am inheriting PropertyInfo in a new class. I've done more research and I found in MSDN that I need to override the following methods: GetValue, SetValue, GetAccessors, GetGetMethod, GetSetMethod, and GetIndexParameters.

However, when I try to call base with the parameters it gives me error saying and I quote "Cannot call an abstract member: 'System.Reflection.PropertyInfo.GetAccessesors(bool)'". If I try to call the method without any parameters, it does not show up any error but I feel like that is the wrong approach.

This is what I've got so far:

public override MethodInfo[] GetAccessors(bool nonPublic)
{
   MethodInfo[] temp = base.GetAccessors(nonPublic);

return temp;
}

UPDATE 2:

Ok, That did not work well. After some hours of trying to do derived class of either PropertyInfo or PropertyDescriptor, I have decided to not go through with that approach.

Instead, I had another idea from reading other posts. My true problem lies in the fact that the class I usually read and use to get the properties is not always the same. So I realized what I probably really need is just a way to create a dynamic class on the fly, and only then get the properties.

I read that there is such a thing called ExpandoObject and ElasticObject, although I don't quite yet know how to apply them to my problem in order to get finally a solution.

Ok now, what I really AM doing is this -> I have been using the solution mentioned in the following link: jQuery DataTables Plugin Meets C#.

The thing is, This assumes I will have different static models/classes for each DB table. However in my case, I will have two types of columns: The ones provided by each DB table class (aka basic columns) and then additional columns that I am dynamically supplying already in my adaptation.

For example: if this the DB table class:

public class Table1
{
    public int Field1;
    public string Field2;
    public string Field3;
}

And then I supply an extra column called "Action" of type string, then In the DataTableParser class, in the _properties attribure there should be the following information:

_properties[0] should be int32 Field1
_properties[1] should be String Field2
_properties[2] should be String Field3
_properties[3] should be String Action

And to be honest that is ALL I need! Nothing more, nothing less! The rest I am already parsing!

In the end, because I have a different number of columns (supplied) than the object passed to the DataTableParser class, it always gives error during Sorting and Filtering the DataTable.

Any help please? I really need it! Thanks again.

Best regards,

Luis

Answer

MadGatsu picture MadGatsu · Apr 3, 2013

Solution:

Ok, basically what I've done was reusing the MyTypeBuilder class (with some adaptations) from the topic below in order to create a dynamic object and added a method to get an IList from it.

Link: How to dynamically create a class in C#?

public class MyObjectBuilder
{
    public Type objType { get; set; }

    public MyObjectBuilder()
    {
        this.objType = null;
    }

    public object CreateNewObject(List<Field> Fields)
    {
        this.objType = CompileResultType(Fields);
        var myObject = Activator.CreateInstance(this.objType);

        return myObject;
    }

    public IList getObjectList()
    {
        Type listType = typeof(List<>).MakeGenericType(this.objType);

        return (IList)Activator.CreateInstance(listType);
    }

    public static MethodInfo GetCompareToMethod(object genericInstance, string sortExpression)
    {
        Type genericType = genericInstance.GetType();
        object sortExpressionValue = genericType.GetProperty(sortExpression).GetValue(genericInstance, null);
        Type sortExpressionType = sortExpressionValue.GetType();
        MethodInfo compareToMethodOfSortExpressionType = sortExpressionType.GetMethod("CompareTo", new Type[] { sortExpressionType });

        return compareToMethodOfSortExpressionType;
    }

    public static Type CompileResultType(List<Field> Fields)
    {
        TypeBuilder tb = GetTypeBuilder();
        ConstructorBuilder constructor = tb.DefineDefaultConstructor(MethodAttributes.Public | MethodAttributes.SpecialName | MethodAttributes.RTSpecialName);

        // NOTE: assuming your list contains Field objects with fields FieldName(string) and FieldType(Type)
        foreach (var field in Fields)
            CreateProperty(tb, field.FieldName, field.FieldType);

        Type objectType = tb.CreateType();
        return objectType;
    }

    private static TypeBuilder GetTypeBuilder()
    {
        var typeSignature = "MyDynamicType";
        var an = new AssemblyName(typeSignature);
        AssemblyBuilder assemblyBuilder = AppDomain.CurrentDomain.DefineDynamicAssembly(an, AssemblyBuilderAccess.Run);
        ModuleBuilder moduleBuilder = assemblyBuilder.DefineDynamicModule("MainModule");
        TypeBuilder tb = moduleBuilder.DefineType(typeSignature
                            , TypeAttributes.Public |
                            TypeAttributes.Class |
                            TypeAttributes.AutoClass |
                            TypeAttributes.AnsiClass |
                            TypeAttributes.BeforeFieldInit |
                            TypeAttributes.AutoLayout
                            , null);
        return tb;
    }

    private static void CreateProperty(TypeBuilder tb, string propertyName, Type propertyType)
    {
        FieldBuilder fieldBuilder = tb.DefineField("_" + propertyName, propertyType, FieldAttributes.Private);

        PropertyBuilder propertyBuilder = tb.DefineProperty(propertyName, PropertyAttributes.HasDefault, propertyType, null);
        MethodBuilder getPropMthdBldr = tb.DefineMethod("get_" + propertyName, MethodAttributes.Public | MethodAttributes.SpecialName | MethodAttributes.HideBySig, propertyType, Type.EmptyTypes);
        ILGenerator getIl = getPropMthdBldr.GetILGenerator();

        getIl.Emit(OpCodes.Ldarg_0);
        getIl.Emit(OpCodes.Ldfld, fieldBuilder);
        getIl.Emit(OpCodes.Ret);

        MethodBuilder setPropMthdBldr =
            tb.DefineMethod("set_" + propertyName,
              MethodAttributes.Public |
              MethodAttributes.SpecialName |
              MethodAttributes.HideBySig,
              null, new[] { propertyType });

        ILGenerator setIl = setPropMthdBldr.GetILGenerator();
        Label modifyProperty = setIl.DefineLabel();
        Label exitSet = setIl.DefineLabel();

        setIl.MarkLabel(modifyProperty);
        setIl.Emit(OpCodes.Ldarg_0);
        setIl.Emit(OpCodes.Ldarg_1);
        setIl.Emit(OpCodes.Stfld, fieldBuilder);

        setIl.Emit(OpCodes.Nop);
        setIl.MarkLabel(exitSet);
        setIl.Emit(OpCodes.Ret);

        propertyBuilder.SetGetMethod(getPropMthdBldr);
        propertyBuilder.SetSetMethod(setPropMthdBldr);
    }
}

Also, I used a Field class like so to have information on a particular Field

public class Field
{
   public string FieldName;
   public Type FieldType;
}

Finally, I found this method somewhere that allows a person to set the value to a Member:

public static void SetMemberValue(MemberInfo member, object target, object value)
{
   switch (member.MemberType)
   {
       case MemberTypes.Field:
           ((FieldInfo)member).SetValue(target, value);
           break;
       case MemberTypes.Property:
           ((PropertyInfo)member).SetValue(target, value, null);
           break;
       default:
           throw new ArgumentException("MemberInfo must be if type FieldInfo or PropertyInfo", "member");
   }
}

Afterwards, I did the following to create a dynamic object and insert a property on it:

//Creating a List of Fields (string FieldName, Type FieldType) 
List<Field> Fields = new List<Field>();
Fields.Add(new Field { FieldName = "TestName", FieldType = typeof(string) });

//MyObjectBuilder Class
MyObjectBuilder o = new MyObjectBuilder();

//Creating a new object dynamically
object newObj = o.CreateNewObject(Fields);
IList objList = o.getObjectList();

Type t = newObj.GetType();
object instance = Activator.CreateInstance(t);

PropertyInfo[] props = instance.GetType().GetProperties();

int instancePropsCount = props.Count();

for (int i = 0; i < instancePropsCount; ++i)
{
   string fieldName = props[i].Name;
   MemberInfo[] mInfo = null;
   PropertyInfo pInfo = newObj.GetType().GetProperty(fieldName);

   if (pInfo != null)
   {
       var value = pInfo.GetValue(newObj, null);
       mInfo = t.GetMember(fieldName);

       if (value != null && mInfo != null && !string.IsNullOrEmpty(mInfo[0].ToString()))
           SetMemberValue(mInfo[0], instance, value);
   }
   else
   {
       mInfo = t.GetMember(fieldName);

       if (mInfo != null && !string.IsNullOrEmpty(mInfo[0].ToString()))
           SetMemberValue(mInfo[0], instance, null);
   }
}

objList.Add(instance);

This solution goes a little bit beyond my initial question, but it does show how to create an object dynamically, hence allowing us to add properties on the fly to that object.