C#反射和实例化 - 有没有办法做Activator.CreateInstance(myType){X = x}?

时间:2013-01-16 16:07:36

标签: c# reflection instantiation

我不确定这种代码的术语,但我想知道是否可以在括号后实例化变量,但是在使用反射时。

我有一个从XML文件加载的地图。这是(int X,int Y,string S)的集合,其中X,Y是某个地形的位置,S是表示地形类型的字符串。我有一个字典在字符串和相关类型之间传递;例如,一个键值对可能是“Tree”,typeof(Tree)。

使用反射时,虽然我知道可以使用参数进行实例化,但我唯一的方法就是使用Activator.CreateInstance(Type t),即使用空构造函数。

当我对地图进行硬编码时,我最初会像这样实例化(在某些i,j for循环中):

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

在开始考虑反射和我的保存文件时,我将其更改为:

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

但是,我意识到这不适用于反射,所以我必须执行以下操作(Tree继承自Terrain,而字典只是将XML保存数据字符串转换为类型):

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

我更喜欢使用像

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

有这样的捷径吗?我想如果不是我可以编辑world.Add采取X和Y并转发到Terrain来访问那些变量,但我仍然很好奇a)这个{var1 = X,var2 = Y}编程被调用,和b)使用反射时是否存在类似的东西。

5 个答案:

答案 0 :(得分:4)

这种语法称为Object Initializer语法,只是用于设置属性的语法糖。

代码var result = new MyType { X = x }将编译为:

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

如果您仅在运行时知道实例化类型,则必须使用PropertyInfo.SetValue自己执行此操作;如果在编译时已知类型,则必须使用普通属性setter。

答案 1 :(得分:3)

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

如上所述HERE

答案 2 :(得分:3)

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

然后打电话:

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

优点是,通过这种方式,您将具有类型安全性并使用反射作为最低要求,因为我们都知道反射是一项昂贵的操作,应该尽可能避免。

答案 3 :(得分:2)

答案是否定的,因为您提到的object initialization syntax(3.0中的LINQ引入)是编译器的错觉。在中,当你输入这个

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

编译器实际上将其转换为符合CLS的IL,等同于

var foo = new Foo();
foo.Bar = "baz";
菲尔·哈克有一个great blog post,它不仅涵盖了编译器重写的细节,还讨论了处理实现IDisposable的类型时可能产生的一些副作用

由于所有这些只不过是编译器的假动作,因此没有使用反射的等价物(即Activator.CreateInstance(Type t))。其他人会给你解决方法,但最后确实没有直接的等价物。

您可以管理的最接近的通用hack可能是创建一个接受对象的方法,然后使用反射来识别该对象的属性及其各自的值,以便为您执行对象初始化。可能会使用类似这样的东西

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

代码就像(这不是我的头脑)

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
    }    
}

您必须从匿名对象获取每个属性,在目标类型中找到具有相同名称和类型的属性,然后从匿名对象中的该属性获取值并设置目标属性的值达到这个价值。它并不难以置信,但它绝对容易出现运行时异常和问题,编译器为匿名类型的属性选择不同的类型,要求你更具体(例如new { Bar = (string)null }),这与事物的优雅有关

答案 4 :(得分:1)

您可以创建一个接受这些参数的构造函数,然后使用

Activator.CreateInstance(type, i, j)

但是您将无法使用对象初始化语法。这只是用于设置属性的糖果。