【问题标题】:Best way to create an instance of run-time determined type [duplicate]创建运行时确定类型的实例的最佳方法[重复]
【发布时间】:2012-04-05 00:55:54
【问题描述】:

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

我有一个实例方法,虽然作用于 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
     }
}

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

【问题讨论】:

标签: c# performance dynamic reflection runtime


【解决方案1】:

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

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.5,x86):

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

【讨论】:

  • 很高兴看到构造函数调用比激活器更快,正如这里blogs.msdn.com/b/haibo_luo/archive/2005/11/17/494009.aspx 和这里bloggingabout.net/blogs/vagif/archive/2010/04/02/… 所证实的那样
  • 6.6236752 Invoke 选项中的前 6 个是错字吗?
  • @Jeff,我还认为“这不可能!”。但它似乎是:我将代码粘贴到 Linqpad 中,启用了优化,结果或多或少是一致的(假设 3 年过去了)。与 DarkGray 示例的顺序相同:00:00:00.4287503、00:00:00.4431353、00:00:01.0507991、00:00:00.0809354 和 00:00:00.0231360。
  • 嗯,我将不得不尝试运行他的代码,但在我自己的实验中,ConstructorInfo.Invoke 似乎相当快,尤其是与使用 Activator 相比。
  • @easuter 是的,你是对的。刚刚确认它真的那么慢。真烦人。
【解决方案2】:

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

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

这里可能存在一个问题,anotherOneLikeMe 将被键入为 object,因此除非您打算将其转换为公共基类(例如您的示例中的 BaseClass),否则您不会可以的。

【讨论】:

    【解决方案3】:

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

    使用工厂Func

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

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

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

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

    【讨论】:

    • 还要考虑工厂Func 方法对可以在派生类上调用参数化构造函数的 Lambda 表达式开放。例如:SomeMethod(() =&gt; new MyDerived(DateTime.Now);
    【解决方案4】:

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

    但是你可以做这种事情:

    BaseClass obj = new DerivedClass();
    

    这是这样实现的:

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

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

    【讨论】:

      【解决方案5】:

      这实际上取决于您所说的“运行时”是什么意思以及目标是什么。例如,Jon 和 Bas 都想到了使用反射来后期绑定特定类并在运行时实例化它的想法。这当然是一个想法,如果这是您的目标,您甚至可以在运行时发现对象上的方法

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

      如果您知道要加载的类,并且它们具有相同的接口(通用主题),但希望在运行时实例化不同的类,则可以选择 IoC 容器。这不是您特别要问的问题。

      动态关键字不是您要查找的内容。它确实在运行时加载,但动态更多的是编译器不检查您调用的方法是否实际存在。如果操作不正确,当您调用一个不存在的方法时,最终可能会发生有趣的爆炸。我看到动态语言的主要动机是与动态语言(如 IronPython)进行交互。

      【讨论】:

        【解决方案6】:
        public void SomeMethod()
        {
            object x =Activator.CreateInstance(this.GetType());
        }
        

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

        【讨论】:

          【解决方案7】:

          虽然您确实需要使用Activator.CreateInstance,但您可能想专门查看Activator.CreateInstance(String, String),它可以使用您在运行时可能知道的类的名称来调用。

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

          【讨论】:

            猜你喜欢
            • 1970-01-01
            • 1970-01-01
            • 2012-10-26
            • 1970-01-01
            • 1970-01-01
            • 1970-01-01
            • 1970-01-01
            • 1970-01-01
            • 1970-01-01
            相关资源
            最近更新 更多