C# reflection and instantiation - is there a way to do Activator.CreateInstance(myType){ X = x }?

拈花ヽ惹草 提交于 2020-07-30 05:33:31

问题


I'm not sure of the terminology for this kind of code, but I want to know if it's possible to instantiate variables after the parentheses, but whilst using reflection.

I have a map which gets loaded from an XML file. This is a collection of (int X, int Y, string S) where the X,Y is the position of some terrain, and S is a string representing the type of the terrain. I have a dictionary to pass between the strings and the relevant types; for example one key-value pair might be "Tree", typeof(Tree).

When using reflection, although I know it's possible to instantiate with parameters, the only way I'm comfortable is just by using Activator.CreateInstance(Type t), i.e. with an empty constructor.

When I had the maps hard coded, I would originally instantiate like this (within some i,j for loop):

case: "Tree"
 world.Add( new Tree(i,j) );

Whilst starting to think about reflection and my save file, I changed this to:

world.Add( new Tree() { X = i, Y = j }

However, I realised that this won't work with reflection, so I am having to do the following (Tree inherits from Terrain, and the dictionary just converts the XML save data string to a type):

Type type = dictionary[dataItem.typeAsString];
Terrain t = (Terrain)Activator.CreateInstance(type);
t.X = i;
t.Y = j;
world.Add(t);

I would prefer to do this using something like

Type type = dictionary[dataItem.typeAsString];
world.Add((Terrain)Activator.CreateInstance(type) { X = i, Y = j }

Is there any shortcut like this? I guess if not I could edit world.Add to take an X and Y and cast to Terrain in there to access those variables, but I am still curious as to a) what this {var1 = X, var2 = Y} programming is called, and b) whether something similar exists when using reflection.


回答1:


This syntax is called Object Initializer syntax and is just syntactic sugar for setting the properties.

The code var result = new MyType { X = x } will be compiled to this:

MyType __tmp = new MyType();
__tmp.X = x;
MyType result = __tmp;

You will have to do that yourself using PropertyInfo.SetValue if you know the instantiated type only at runtime or use the normal property setters if the type is known at compile time.




回答2:


The answer is no, because the object initialization syntax you mention (introduced with LINQ in 3.0) is an illusion of the compiler. As in, when you type this

var foo = new Foo { Bar = "baz" };

the compiler actually converts it into CLS-compliant IL which equates to

var foo = new Foo();
foo.Bar = "baz";

Phil Haack has a great blog post which not only covers the details of this rewriting done by the compiler, but also some side effects it can cause when dealing with types that implement IDisposable

As all of this is nothing but a feint by the compiler, there is no equivalent using reflection (i.e., Activator.CreateInstance(Type t)). Others will give you workarounds, but in the end there really is no direct equivalent.

Probably the closest generic hack you could manage would be to create a method that accepted an object, then used reflection in order to identify the properties of that object and their respective values in order to perform object initialization for you. It might be used something like this

var foo = Supercollider.Initialize<Foo>(new { Bar = "baz" });

and the code would be something like (this is off the top of my head)

public sealed class Supercollider
{    
    public static T Initialize<T>(object propertySource)
    {
        // you can provide overloads for types that don't have a default ctor
        var result = Activator.CreateInstance(typeof(T)); 
        foreach(var prop in ReflectionHelper.GetProperties(typeof(T)))
            ReflectionHelper.SetPropertyValue(
                result, // the target
                prop,  // the PropertyInfo 
                propertySource); // where we get the value
    }    
}

You'd have to get each property from the anonymous object, find a property in your target type with the same exact name and type, then get the value from that property in the anonymous object and set the value of your target's property to this value. Its not incredibly hard, but its absolutely prone to runtime exceptions and issues where the compiler chooses a different type for the anonymous type's property, requiring you be more specific (e.g., new { Bar = (string)null }), which screws with the elegance of the thing.




回答3:


(T)Activator.CreateInstance(typeof(T), param1, param2, ...);

As described HERE.




回答4:


public sealed class ReflectionUtils
    {

        public static T ObjectInitializer<T>(Action<T> initialize)
        {
            var result = Activator.CreateInstance<T>();
            initialize(result);
            return result;
        }
    }

public class MyModel
{
    public string Name{get;set;}
}

And after that just make the call :

var myModel = ReflectionUtils.ObjectInitializer<MyModel>(m => 
   {
     m.Name = "Asdf"
   });

The advantage is that in this way you will have type safety and use reflection as minimum required, because we all know that reflection is an expensive operation that should be avoided as much as possible.




回答5:


You could create a constructor which takes those arguments, then use

Activator.CreateInstance(type, i, j)

But you won't be able to use the object initialization syntax. Which is just sugar candy for setting the properties.



来源:https://stackoverflow.com/questions/14362674/c-sharp-reflection-and-instantiation-is-there-a-way-to-do-activator-createinst

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