创建在运行时确定的类型实例的最佳方法(在.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
}
}
我已经阅读了一些关于反射或使用动态关键字的内容,但我没有这方面的经验。
答案 0 :(得分:22)
在运行时重复创建实例的性能的最佳方式是编译表达式:
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 { }
答案 1 :(得分:10)
您正在寻找Activator.CreateInstance
(还有其他重载,例如接受构造函数参数的this one)。所以你可以写
var anotherOneLikeMe = Activator.CreateInstance(this.GetType());
这里可能存在一个问题,即anotherOneLikeMe
将被输入为object
,因此除非您打算将其强制转换为公共基类(例如,在您的示例中为BaseClass
你用它做的并不多。
答案 2 :(得分:4)
我知道这被标记为反射,但我通常认为反射是出于性能和复杂性原因的最后手段。在某些情况下,您的设计/使用需要反思;但是,我会提供一些备选方案供考虑:
使用工厂Func
:
public void SomeMethod(Func<BaseClass> createDerived)
{
BaseClass d = createDerived();
}
使您的方法使用约束泛型类型:
public void SomeMethod<TDerived>() where TDerived : BaseClass, new()
{
TDerived d = new TDerived();
}
在最后一个替代方案中,正如其他人所建议的那样,使用Activator.CreateInstance
。我更喜欢最后一个反射,因为它们都需要一个无参数构造函数,但是编译器强制执行约束,派生类型必须具有无参数构造函数,而反射方法会导致运行时异常。
答案 3 :(得分:2)
这里的问题是你在编译时永远不会知道DerivedClass
的类型。
但你可以做这类事情:
BaseClass obj = new DerivedClass();
这是这样实现的:
BaseClass obj = (BaseClass)Activator.CreateInstance(this.GetType());
如果DerivedClass没有无参数构造函数,则此调用将失败。
答案 4 :(得分:1)
这实际上取决于“运行时”的含义以及目标是什么。例如,Jon和Bas都想到使用Reflection来延迟绑定特定类并在运行时实例化它。这当然是一个想法,如果这是你的目标,你甚至可以在运行时发现对象上的方法
如果您使用的是界面,则还有其他几个选项。 Microsoft可扩展性框架(或MEF)可能是您想要查看的选项,因为它包括运行时的可发现性和实例化。缺点是发现的类必须遵循正确的接口,但在大多数情况下这不是一个真正的问题。
如果您知道要加载的类,并且它们具有相同的接口(通用主题),但想要在运行时实例化不同的类,则可以选择IoC容器。这不是你要问的特别。
动态关键字不是您想要的。它确实在运行时加载,但dyanmic更多的是编译器没有检查你调用的方法是否确实存在。如果做得不正确,当你调用一个不存在的方法时,你最终会得到一个有趣的爆发。我见过的关于动态的主要动机是与动态语言的交互,比如IronPython。
答案 5 :(得分:0)
public void SomeMethod()
{
object x =Activator.CreateInstance(this.GetType());
}
这应该创建一个新实例,另一方面我想知道你为什么要这样做。
答案 6 :(得分:0)
虽然您确实需要使用Activator.CreateInstance,但您可能希望专门查看可以使用您在运行时可能知道的类名称调用的Activator.CreateInstance(String, String)。
如果要在基本类型上实例化派生类型,这将非常有用。如果您要从派生类型本身调用SomeMethod
,那么使用this.GetType()
的先前答案就足够了。