How to make .NET reflection to work with dynamical

2019-06-19 00:30发布

问题:

This question already has an answer here:

  • How do I reflect over the members of dynamic object? 4 answers

Look at the following example:

void Main()
{
    // APPROACH 1: With an anonymous type
    var myObject = new {
        Property1 = "PropertyValue1"
    };

    // WORKS: Properties contains "Property1"
    var properties = myObject.GetType().GetProperties();

    // APPROACH 2: With an expando object
    dynamic myObject2 = new ExpandoObject();
    myObject2.Property1 = "PropertyValue1";

    // DOES NOT WORK: Properties2 is null or empty
    var properties2 = myObject2.GetType().GetProperties();
}

What I want is to make Type.GetProperties() to work on a dynamically generated type. I really understand why it works in the approach 1 and not 2. Actually, in the approach 1, the compiler has the oportunity to generate an anonymous type that looks exactly like a named type. In the approach 2, however, the compile time type is actually an ExpandoObject, so reflection doesn't work properly.

How do I create a runtime object, that is dynamic and will also work normally with reflection, like the GetProperties method?

EDIT

Thanks for all answers, but I really understand and I know how to get the keys and values from the ExpandoObject.. The problem is that I need to pass a dynamically created instance to a method, outside my control that will, in turn, call GetProperties() on the instance.

回答1:

In order to get the values via reflection for a dynamically-constructed type, you'll need to use Reflection.Emit. This is less than ideal as it requires you to properly emit MSIL. If your use case requires only simple property access, then it may be feasible, though ill-advised.

Here's a simple, lightly-tested, type builder that uses Reflection.Emit:

    public static class TypeBuilderUtil {
        public static Type BuildDynamicType() {
            var typeBuilder = CreateTypeBuilder( "DynamicType" );
            CreateProperty( typeBuilder, "Property1", typeof ( string ) );

            var objectType = typeBuilder.CreateType();
            return objectType;
        }

        private static TypeBuilder CreateTypeBuilder( string typeName ) {
            var assemblyBuilder = AppDomain.CurrentDomain.DefineDynamicAssembly( new AssemblyName( "DynamicAssembly" ), AssemblyBuilderAccess.Run );
            var moduleBuilder = assemblyBuilder.DefineDynamicModule( "DynamicModule" );
            var typeBuilder = moduleBuilder.DefineType( typeName,
                                                        TypeAttributes.Public | TypeAttributes.Class | TypeAttributes.AutoClass | TypeAttributes.AnsiClass | TypeAttributes.BeforeFieldInit | TypeAttributes.AutoLayout
                                                        , null );
            return typeBuilder;
        }

        private static void CreateProperty( TypeBuilder typeBuilder, string propertyName, Type propertyType ) {
            var backingFieldBuilder = typeBuilder.DefineField( "_" + propertyName, propertyType, FieldAttributes.Private );
            var propertyBuilder = typeBuilder.DefineProperty( propertyName, PropertyAttributes.HasDefault, propertyType, null );
            // Build setter
            var getterMethodBuilder = typeBuilder.DefineMethod( "get_" + propertyName, MethodAttributes.Public | MethodAttributes.SpecialName | MethodAttributes.HideBySig, propertyType, Type.EmptyTypes );
            var getterIl = getterMethodBuilder.GetILGenerator();
            getterIl.Emit( OpCodes.Ldarg_0 );
            getterIl.Emit( OpCodes.Ldfld, backingFieldBuilder );
            getterIl.Emit( OpCodes.Ret );

            // Build setter
            var setterMethodBuilder = typeBuilder.DefineMethod( "set_" + propertyName, MethodAttributes.Public | MethodAttributes.SpecialName | MethodAttributes.HideBySig, null, new[] {propertyType} );
            var setterIl = setterMethodBuilder.GetILGenerator();
            setterIl.Emit( OpCodes.Ldarg_0 );
            setterIl.Emit( OpCodes.Ldarg_1 );
            setterIl.Emit( OpCodes.Stfld, backingFieldBuilder );
            setterIl.Emit( OpCodes.Ret );

            propertyBuilder.SetGetMethod( getterMethodBuilder );
            propertyBuilder.SetSetMethod( setterMethodBuilder );
        }
    }

You would create the type, then populate it as such:

        var myType = TypeBuilderUtil.BuildDynamicType();
        var myObject = Activator.CreateInstance( myType );

        // Set the value
        var propertyInfo = myObject.GetType().GetProperty( "Property1", BindingFlags.Instance | BindingFlags.Public );
        propertyInfo.SetValue( myObject, "PropertyValue", null );


回答2:

You don't need reflection with ExpandoObject. It's really just a fancy implementation of IDictionary<string, object>. You can cast it as such, and then you will have a dictionary where the keys are the property names and the values are the property values:

// Create your object
dynamic myObject2 = new ExpandoObject();

// Cast to IDictionary<string, object>
var myObjectDictionary = (IDictionary<string, object>)myObject2;

// Get List<string> of properties
var propertyNames = myObjectDictionary.Keys.ToList();

Another option would be to use the features of an IDynamicMetaObjectProvider, which ExpandoObject also implements. Usage would look like:

var metaObjectProvider = (IDynamicMetaObjectProvider)myObject2;
var propertyNames = metaObjectProvider
    .GetMetaObject(Expression.Constant(metaObjectProvider))
    .GetDynamicMem‌​berNames();

In this case propertyNames would be an IEnumerable<string> with the dynamic member names.



回答3:

Your first example is an anonymous object. The compiler actually generates a type behind the scenes that you can reflect on.

The second example uses an ExpandoObject to back your dynamic object. ExpandoObject has no properties of its own which is why your call returns what it does. ExpandoObject does explicitly implement IDictionary<string, object> that gives you access to the properties and their values. You can use it as follows:

var properties2 = (myObject2 as IDictionary<string, object>).Keys;


回答4:

ExpandoObject is what is special here, nothing else. It's not actually mutating the definition of the type itself at runtime. What's actually going on here is that the apparent property accesses are actually mutating an IDictionary<string,object> that is held behind the scenes. To access the properties of an ExpandoObject (note this won't work for any other type) you can cast it to IDictionary<string,object>, which is the intended mechanism of getting that data from an ExpandoObject.