Create new PropertyInfo object on the fly

两盒软妹~` 提交于 2019-12-03 13:34:53

问题


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


回答1:


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.




回答2:


PropertyInfo is an abstract class. If you only need the Name and PropertyType properties, you can derive your own class from it to provide this information.

The places where you could make use of these instances is extremely restricted. To help you better we will need to know exactly how you plan to make use of the PropertyInfo objects you're trying to create.

Edit: Based on the edit, you may be able to use this class as long as you implement the GetValue method in some manner specific to the objects you are creating. It's not clear what you are doing with the DynamicClass instances, or more importantly where the property values are supposed to be stored.

public class SimplePropertyInfo : PropertyInfo
{
    private readonly string _name;
    private readonly Type _propertyType;

    public SimplePropertyInfo(string name, Type propertyType)
    {
        if (name == null)
            throw new ArgumentNullException("name");
        if (propertyType == null)
            throw new ArgumentNullException("propertyType");
        if (string.IsNullOrEmpty(name))
            throw new ArgumentException("name");

        _name = name;
        _propertyType = propertyType;
    }

    public override string Name
    {
        get
        {
            return _name;
        }
    }

    public override Type PropertyType
    {
        get
        {
            return _propertyType;
        }
    }

    public override PropertyAttributes Attributes
    {
        get
        {
            throw new NotImplementedException();
        }
    }

    public override bool CanRead
    {
        get
        {
            throw new NotImplementedException();
        }
    }

    public override bool CanWrite
    {
        get
        {
            throw new NotImplementedException();
        }
    }

    public override Type DeclaringType
    {
        get
        {
            throw new NotImplementedException();
        }
    }

    public override Type ReflectedType
    {
        get
        {
            throw new NotImplementedException();
        }
    }

    public override MethodInfo[] GetAccessors(bool nonPublic)
    {
        throw new NotImplementedException();
    }

    public override MethodInfo GetGetMethod(bool nonPublic)
    {
        throw new NotImplementedException();
    }

    public override ParameterInfo[] GetIndexParameters()
    {
        throw new NotImplementedException();
    }

    public override MethodInfo GetSetMethod(bool nonPublic)
    {
        throw new NotImplementedException();
    }

    public override object GetValue(object obj, BindingFlags invokeAttr, Binder binder, object[] index, System.Globalization.CultureInfo culture)
    {
        throw new NotImplementedException();
    }

    public override void SetValue(object obj, object value, BindingFlags invokeAttr, Binder binder, object[] index, System.Globalization.CultureInfo culture)
    {
        throw new NotImplementedException();
    }

    public override object[] GetCustomAttributes(Type attributeType, bool inherit)
    {
        throw new NotImplementedException();
    }

    public override object[] GetCustomAttributes(bool inherit)
    {
        throw new NotImplementedException();
    }

    public override bool IsDefined(Type attributeType, bool inherit)
    {
        throw new NotImplementedException();
    }
}



回答3:


Since PropertyInfo is an abstract class, you will have to implement it yourself:

class MyPropertyInfo : PropertyInfo
{
}

There's a whole bunch of methods and properties you need to override but since you will only query Name and Type, you could simply throw new InvalidOperationException() on the others.

Adding the fake PropertyInfo to the array isn't possible as an array is fixed-size, but you could create a List<PropertyInfo>, add the original array and the fake PropertyInfo to it, and return theList.ToArray().

I would consider other options before implementing this though, for instance create your own MyProperty class that doesn't derive from PropertyInfo and return an array of those instead.




回答4:


You have a Method that has commonality with several other instances, the most efficient manner to handle such an event is through Polymorphism.

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

Then you can do the following:

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

And so on...

Now you'll need to obviously add an implementation on how to fill these items up; but then you could just call:

DynamicClass[] obj = new DynamicClass[]

obj[0] = new Something();
obj[1] = new SomethingElse();
obj[2] = new AndAnotherSomething();

That is a very, very loose way to implement this. But it would allow you to implement those fields like you desire; then fill them into the array. For you to handle.

I may have misunderstood your question, but I might suggest Polymorphism.



来源:https://stackoverflow.com/questions/15641339/create-new-propertyinfo-object-on-the-fly

易学教程内所有资源均来自网络或用户发布的内容,如有违反法律规定的内容欢迎反馈
该文章没有解决你所遇到的问题?点击提问,说说你的问题,让更多的人一起探讨吧!