5

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

我有一张从 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) 使用反射时是否存在类似的东西。

4

5 回答 5

5

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

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

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

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

于 2013-01-16T16:09:43.763 回答
3

答案是否定的,因为您提到的对象初始化语法(在 3.0 中随 LINQ 引入)是编译器的错觉。如,当你输入这个

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

编译器实际上将其转换为符合 CLS 的 IL,这相当于

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

Phil Haack 有一篇很棒的博客文章,它不仅涵盖了编译器完成的重写的细节,还介绍了它在处理实现的类型时可能导致的一些副作用IDisposable

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

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

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 }),这与事物的优雅有关。

于 2013-01-16T16:15:15.983 回答
2
(T)Activator.CreateInstance(typeof(T), param1, param2, ...);

这里所述。

于 2013-01-16T16:09:57.413 回答
2
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"
   });

这样做的好处是,通过这种方式,您将具有类型安全性并尽可能少地使用反射,因为我们都知道反射是一项昂贵的操作,应尽可能避免。

于 2017-03-31T13:20:23.000 回答
0

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

Activator.CreateInstance(type, i, j)

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

于 2013-01-16T16:13:19.830 回答