6

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.

Andre Pena
  • 56,650
  • 48
  • 196
  • 243
  • 1
    To support adding properties *after* an ExpandObject is created, you can't do what you want. – Peter Ritchie Jun 02 '14 at 20:58
  • Regarding your edit, I don't think you can do what you want. `ExpandoObject` is a dictionary masquerading as an `Object` but its dynamically defined properties do not actually exist on the object, they only exist in the dictionary. The `GetProperties()` method of `Type` doesn't care, it performs the same operation on each. Your solution, on the other hand, does need to know the difference if you want the results to be same from each. Without control of the method that is calling `GetProperties()`, there's nothing you can do (that I can think of). – Cᴏʀʏ Jun 02 '14 at 21:20

4 Answers4

5

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.

Cᴏʀʏ
  • 105,112
  • 20
  • 162
  • 194
  • Not only do you not need it, but as per the question, *it doesn't work*. This is the *only* option, not just the easiest one. – Servy Jun 02 '14 at 20:44
  • 1
    @Servy - Reflection does indeed work with `ExpandoObject`, it just doesn't give you the results you'd expect without understanding the underlying behavior of `dynamic`. :-) – Justin Niessner Jun 02 '14 at 20:47
  • @cory, thanks for your reply, but it doesn't help. I edited the question for clarity. – Andre Pena Jun 02 '14 at 20:50
  • @JustinNiessner Well, what it does is provide the type information of `ExpandoObject` that is statically defined at compile time, which is to say, basically nothing. It doesn't include the type information that represents the actual instance of that `ExpandoObject`. – Servy Jun 02 '14 at 20:51
4

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 );
Ryan Emerle
  • 15,461
  • 8
  • 52
  • 69
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;
Justin Niessner
  • 242,243
  • 40
  • 408
  • 536
2

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.

Servy
  • 202,030
  • 26
  • 332
  • 449
  • thanks for your reply, but it doesn't help. I edited the question for clarity. – Andre Pena Jun 02 '14 at 21:02
  • @AndréPena The answer was very specifically telling you that what you want to do isn't possible. This is the *only* option. If that doesn't work for you, then you need to avoid putting yourself in this position to begin with, in some way. – Servy Jun 02 '14 at 21:03