【问题标题】:How to make .NET reflection to work with dynamically generated objects? [duplicate]如何使 .NET 反射与动态生成的对象一起工作? [复制]
【发布时间】:2014-07-23 01:32:06
【问题描述】:

看下面的例子:

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();
}

我想要的是让Type.GetProperties() 处理动态生成的类型。我真的明白为什么它适用于方法 1 而不是方法 2。实际上,在方法 1 中,编译器有机会生成一个看起来与命名类型完全一样的匿名类型。但是在方法2中,编译时类型实际上是一个ExpandoObject,所以反射不能正常工作。

如何创建一个动态的运行时对象,也可以正常使用反射,如GetProperties 方法?

编辑

感谢所有答案,但我真的理解并且我知道如何从 ExpandoObject 获取键和值。问题是我需要将动态创建的实例传递给我无法控制的方法反过来,在实例上调用 GetProperties()。

【问题讨论】:

  • 为了支持添加属性一个 ExpandObject 创建之后,你不能做你想做的事。
  • 关于你的编辑,我不认为你可以做你想做的事。 ExpandoObject 是一个伪装成Object 的字典,但其动态定义的属性实际上并不存在于对象上,它们只存在于字典中。 TypeGetProperties() 方法无关紧要,它对每个都执行相同的操作。另一方面,如果您希望每个结果相同,则您的解决方案确实需要知道差异。如果不控制调用GetProperties() 的方法,您将无能为力(我能想到的)。

标签: c# .net reflection


【解决方案1】:

您不需要使用ExpandoObject 进行反思。这实际上只是IDictionary<string, object> 的一个奇特实现。您可以这样转换它,然后您将拥有一个字典,其中键是属性名称,值是属性值:

// 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();

另一种选择是使用IDynamicMetaObjectProvider 的功能,ExpandoObject 也实现了这些功能。用法如下:

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

在这种情况下,propertyNames 将是具有动态成员名称的IEnumerable&lt;string&gt;

【讨论】:

  • 您不仅不需要它,而且根据问题,它不起作用。这是 only 选项,而不仅仅是最简单的选项。
  • @Servy - 反射确实可以与ExpandoObject 一起使用,它只是不会在不了解dynamic 的基本行为的情况下为您提供预期的结果。 :-)
  • @cory,感谢您的回复,但这并没有帮助。为了清楚起见,我编辑了问题。
  • @JustinNiessner 嗯,它所做的就是提供ExpandoObject 的类型信息,它是在编译时静态定义的,也就是说,基本上什么都没有。它不包括代表 ExpandoObject 实际实例的类型信息。
【解决方案2】:

为了通过反射获取动态构造类型的值,您需要使用Reflection.Emit。这不太理想,因为它需要您正确发出 MSIL。如果您的用例只需要简单的属性访问,那么它可能是可行的,尽管不明智。

这是一个使用 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 );
        }
    }

您将创建类型,然后按如下方式填充它:

        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 );

【讨论】:

  • 太棒了。我希望我能给 +10 ;-)
【解决方案3】:

你的第一个例子是一个匿名对象。编译器实际上会在幕后生成一个您可以反思的类型。

第二个示例使用ExpandoObject 支持您的动态对象。 ExpandoObject 没有自己的属性,这就是您的调用返回它所做的事情的原因。 ExpandoObject 确实显式实现了 IDictionary&lt;string, object&gt;,它使您可以访问属性及其值。您可以按如下方式使用它:

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

【讨论】:

  • 感谢您的回复,但无济于事。为了清楚起见,我编辑了问题。
【解决方案4】:

ExpandoObject 是这里的特别之处,仅此而已。它实际上并没有在运行时改变类型本身的定义。这里实际发生的是,明显的属性访问实际上正在改变隐藏在幕后的IDictionary&lt;string,object&gt;。要访问ExpandoObject 的属性(注意这不适用于任何其他类型),您可以将其转换为IDictionary&lt;string,object&gt;,这是从ExpandoObject 获取数据的预期机制。

【讨论】:

  • 感谢您的回复,但无济于事。为了清楚起见,我编辑了问题。
  • @AndréPena 答案非常明确地告诉你,你想做的事情是不可能的。这是 only 选项。如果这对您不起作用,那么您需要避免以某种方式一开始就将自己置于这个位置。
猜你喜欢
  • 1970-01-01
  • 2013-12-13
  • 2013-08-19
  • 2010-11-12
  • 2014-05-22
  • 2018-01-10
相关资源
最近更新 更多