创建运行时确定类型的实例的最佳方式

本文关键字:实例 方式 最佳 类型 运行时 创建 | 更新日期: 2023-09-27 18:28:35

创建运行时确定的类型的实例的最佳方法是什么(在.NET4中)。

我有一个实例方法,虽然它作用于BaseClass对象,但它的派生类实例可能会调用它。我需要在方法中创建另一个与this类型相同的实例。为每个派生类重载Method是不实际的,因为它相当复杂,并且保留单个实现会更有效。

public class BaseClass
{
     //constructors + properties + methods etc
     public SomeMethod()
     {
          //some code
          DerivedClass d = new DerivedClass(); //ideally determine the DerivedClass type at run-time
     }
}

我读过一些关于反思或使用动态关键字的文章,但我没有这些方面的经验。

创建运行时确定类型的实例的最佳方式

性能在运行时重复创建实例的最佳方式是编译表达式:

static readonly Func<X> YCreator = Expression.Lambda<Func<X>>(
   Expression.New(typeof(Y).GetConstructor(Type.EmptyTypes))
 ).Compile();
X x = YCreator();

统计(2012):

    Iterations: 5000000
    00:00:00.8481762, Activator.CreateInstance(string, string)
    00:00:00.8416930, Activator.CreateInstance(type)
    00:00:06.6236752, ConstructorInfo.Invoke
    00:00:00.1776255, Compiled expression
    00:00:00.0462197, new

统计数据(2015,.net 4.5,x64):

    Iterations: 5000000
    00:00:00.2659981, Activator.CreateInstance(string, string)
    00:00:00.2603770, Activator.CreateInstance(type)
    00:00:00.7478936, ConstructorInfo.Invoke
    00:00:00.0700757, Compiled expression
    00:00:00.0286710, new

统计数据(2015,.net 4.5x86):

    Iterations: 5000000
    00:00:00.3541501, Activator.CreateInstance(string, string)
    00:00:00.3686861, Activator.CreateInstance(type)
    00:00:00.9492354, ConstructorInfo.Invoke
    00:00:00.0719072, Compiled expression
    00:00:00.0229387, new

完整代码:

public static X CreateY_New()
{
  return new Y();
}
public static X CreateY_CreateInstance()
{
  return (X)Activator.CreateInstance(typeof(Y));
}
public static X CreateY_CreateInstance_String()
{
  return (X)Activator.CreateInstance("Program", "Y").Unwrap();
}
static readonly System.Reflection.ConstructorInfo YConstructor = 
    typeof(Y).GetConstructor(Type.EmptyTypes);
static readonly object[] Empty = new object[] { };
public static X CreateY_Invoke()
{
  return (X)YConstructor.Invoke(Empty);
}
static readonly Func<X> YCreator = Expression.Lambda<Func<X>>(
   Expression.New(typeof(Y).GetConstructor(Type.EmptyTypes))
 ).Compile();
public static X CreateY_CompiledExpression()
{
  return YCreator();
}
static void Main(string[] args)
{
  const int iterations = 5000000;
  Console.WriteLine("Iterations: {0}", iterations);
  foreach (var creatorInfo in new [] 
    { 
      new {Name = "Activator.CreateInstance(string, string)", Creator = (Func<X>)CreateY_CreateInstance},
      new {Name = "Activator.CreateInstance(type)", Creator = (Func<X>)CreateY_CreateInstance},
      new {Name = "ConstructorInfo.Invoke", Creator = (Func<X>)CreateY_Invoke},
      new {Name = "Compiled expression", Creator = (Func<X>)CreateY_CompiledExpression},
      new {Name = "new", Creator = (Func<X>)CreateY_New},
    })
  {
    var creator = creatorInfo.Creator;
    var sum = 0;
    for (var i = 0; i < 1000; i++)
      sum += creator().Z;
    var stopwatch = new Stopwatch();
    stopwatch.Start();
    for (var i = 0; i < iterations; ++i)
    {
      var x = creator();
      sum += x.Z;
    }
    stopwatch.Stop();
    Console.WriteLine("{0}, {1}", stopwatch.Elapsed, creatorInfo.Name);
  }
}
public class X
{
  public X() { }
  public X(int z) { this.Z = z; }
  public int Z;
}
public class Y : X { }

您正在寻找Activator.CreateInstance(还有其他重载,比如这个接受构造函数参数的重载)。所以你可以写

var anotherOneLikeMe = Activator.CreateInstance(this.GetType());

这里可能有一个问题,因为anotherOneLikeMe将被类型化为object,所以除非您打算将其强制转换为一个公共基类(例如,您的示例中的BaseClass),否则您无法使用它

我知道这被标记为反射,但出于性能和复杂性的原因,我通常认为反射是最后的手段。在某些情况下,您的设计/使用需要反思;然而,我会提供一些替代方案供考虑:

使用工厂Func:

public void SomeMethod(Func<BaseClass> createDerived)
{
    BaseClass d = createDerived();
}

使您的方法使用受约束的泛型类型:

public void SomeMethod<TDerived>() where TDerived : BaseClass, new()
{
    TDerived d = new TDerived();
}

在这种情况下,最后一种选择使用了Activator.CreateInstance,正如其他人所建议的那样。我更喜欢最后一个而不是反射,因为它们都需要一个无参数的构造函数,但编译器强制要求派生类型必须具有无参数构造函数,而反射方法会导致运行时异常。

这里的问题是在编译时永远无法知道DerivedClass的类型。

然而,你可以做这种类型的事情:

BaseClass obj = new DerivedClass();

这是这样实现的:

BaseClass obj = (BaseClass)Activator.CreateInstance(this.GetType());

但是,如果DerivedClass没有无参数构造函数,则此调用将失败。

这实际上取决于你所说的"运行时"是什么意思以及目标是什么。例如,Jon和Bas都想到了使用Reflection来延迟绑定特定类,并在Runtime中将其实例化。这当然是一个想法,如果你的目标是,你甚至可以在运行时发现对象上的方法

如果您使用的是接口,那么您有几个额外的选项。Microsoft可扩展性框架(或MEF)可能是您想要查看的选项,因为它包括运行时的可发现性和实例化。缺点是发现的类必须遵守正确的接口,但在大多数情况下这并不是一个真正的问题。

如果您知道正在加载的类,并且它们具有相同的接口(公共主题),但希望在运行时实例化不同的类,IoC容器是一个选项。这不是你特别想问的问题。

动态关键字不是您想要的。它确实在运行时加载,但dyanmic更多的是编译器不检查您调用的方法是否真的存在。如果操作不正确,当您调用一个不存在的方法时,可能会出现有趣的爆炸。我看到的dyanamic的主要动机是与动态语言的交互,比如IronPython。

public void SomeMethod()
{
    object x =Activator.CreateInstance(this.GetType());
}

这应该会创建一个新的实例,另一方面,我想知道你为什么要这样做。

虽然确实需要使用Activator.CreateInstance,但您可能需要专门研究Activator.CCreateInstance(String,String),它可以使用运行时可能知道的类名调用。

如果您在基类型上实例化派生类型,这将是非常有益的。如果要从派生类型本身调用SomeMethod,那么前面使用this.GetType()的回答就足够了。