如何在 Activator.CreateInstance 中传递 ctor args 或使用 IL?
我需要一个性能增强的 Activator.CreateInstance() 并遇到 Miron Abramson 的这篇文章,使用工厂在 IL 中创建实例,然后缓存它。 (我在下面添加了来自 Miron Abramson 网站的代码,以防它以某种方式消失)。我对 IL Emit 代码以及用于实例化类的 Activator.CreateInstance() 之外的任何内容都很陌生,任何帮助都将非常感激。
我的问题是我需要创建一个对象的实例,该对象采用带有参数的构造函数。我看到有一种方法可以传入参数的类型,但是有没有一种方法可以传入 ctor 参数的值?
如果可能,我想使用类似于 CreateObjectFactory
// Source: http://mironabramson.com/blog/post/2008/08/Fast-version-of-the-ActivatorCreateInstance-method-using-IL.aspx
public static class FastObjectFactory
{
private static readonly Hashtable creatorCache = Hashtable.Synchronized(new Hashtable());
private readonly static Type coType = typeof(CreateObject);
public delegate object CreateObject();
///
/// Create an object that will used as a 'factory' to the specified type T
///
public static CreateObject CreateObjectFactory() where T : class
{
Type t = typeof(T);
FastObjectFactory.CreateObject c = creatorCache[t] as FastObjectFactory.CreateObject;
if (c == null)
{
lock (creatorCache.SyncRoot)
{
c = creatorCache[t] as FastObjectFactory.CreateObject;
if (c != null)
{
return c;
}
DynamicMethod dynMethod = new DynamicMethod("DM$OBJ_FACTORY_" + t.Name, typeof(object), null, t);
ILGenerator ilGen = dynMethod.GetILGenerator();
ilGen.Emit(OpCodes.Newobj, t.GetConstructor(Type.EmptyTypes));
ilGen.Emit(OpCodes.Ret);
c = (CreateObject)dynMethod.CreateDelegate(coType);
creatorCache.Add(t, c);
}
}
return c;
}
}
更新到 Miron 的代码评论者对其帖子 2010-01-11
public static class FastObjectFactory2<T> where T : class, new()
{
public static Func<T> CreateObject { get; private set; }
static FastObjectFactory2()
{
Type objType = typeof(T);
var dynMethod = new DynamicMethod("DM$OBJ_FACTORY_" + objType.Name, objType, null, objType);
ILGenerator ilGen = dynMethod.GetILGenerator();
ilGen.Emit(OpCodes.Newobj, objType.GetConstructor(Type.EmptyTypes));
ilGen.Emit(OpCodes.Ret);
CreateObject = (Func<T>)
dynMethod.CreateDelegate(typeof(Func<T>));
}
}
I need a performance enhanced Activator.CreateInstance() and came across this article by Miron Abramson that uses a factory to create the instance in IL and then cache it. (I've included code below from Miron Abramson's site in case it somehow disappears). I'm new to IL Emit code and anything beyond Activator.CreateInstance() for instantiating a class and any help would be much appreciative.
My problem is that I need to create an instance of an object that takes a ctor with a parameter. I see there is a way to pass in the Type of the parameter, but is there a way to pass in the value of the ctor parameter as well?
If possible, I would like to use a method similar to CreateObjectFactory<T>(params object[] constructorParams)
as some objects I want to instantiate may have more than 1 ctor param.
// Source: http://mironabramson.com/blog/post/2008/08/Fast-version-of-the-ActivatorCreateInstance-method-using-IL.aspx
public static class FastObjectFactory
{
private static readonly Hashtable creatorCache = Hashtable.Synchronized(new Hashtable());
private readonly static Type coType = typeof(CreateObject);
public delegate object CreateObject();
///
/// Create an object that will used as a 'factory' to the specified type T
///
public static CreateObject CreateObjectFactory() where T : class
{
Type t = typeof(T);
FastObjectFactory.CreateObject c = creatorCache[t] as FastObjectFactory.CreateObject;
if (c == null)
{
lock (creatorCache.SyncRoot)
{
c = creatorCache[t] as FastObjectFactory.CreateObject;
if (c != null)
{
return c;
}
DynamicMethod dynMethod = new DynamicMethod("DM$OBJ_FACTORY_" + t.Name, typeof(object), null, t);
ILGenerator ilGen = dynMethod.GetILGenerator();
ilGen.Emit(OpCodes.Newobj, t.GetConstructor(Type.EmptyTypes));
ilGen.Emit(OpCodes.Ret);
c = (CreateObject)dynMethod.CreateDelegate(coType);
creatorCache.Add(t, c);
}
}
return c;
}
}
Update to Miron's code from commentor on his post 2010-01-11
public static class FastObjectFactory2<T> where T : class, new()
{
public static Func<T> CreateObject { get; private set; }
static FastObjectFactory2()
{
Type objType = typeof(T);
var dynMethod = new DynamicMethod("DM$OBJ_FACTORY_" + objType.Name, objType, null, objType);
ILGenerator ilGen = dynMethod.GetILGenerator();
ilGen.Emit(OpCodes.Newobj, objType.GetConstructor(Type.EmptyTypes));
ilGen.Emit(OpCodes.Ret);
CreateObject = (Func<T>)
dynMethod.CreateDelegate(typeof(Func<T>));
}
}
如果你对这篇内容有疑问,欢迎到本站社区发帖提问 参与讨论,获取更多帮助,或者扫码二维码加入 Web 技术交流群。
绑定邮箱获取回复消息
由于您还没有绑定你的真实邮箱,如果其他用户或者作者回复了您的评论,将不能在第一时间通知您!
发布评论
评论(8)
我一直在对此进行一些测试,并作为 Miron 原始文章的后续内容(此处),我发现 .NET 4.0 Activator 比以前快得多。他的应用程序版本的一些结果经过调整以显示以毫秒为单位的计时:
然而,这是针对无参数构造函数的,我还注意到,当使用带参数的构造函数时,激活器仍然有点慢,如下所示。
我在此处发布的原始解决方案中遇到的一个问题是,我不一定知道编译时所需的对象的类型 - 我只有一个类型引用。现在(除非我是个笨蛋)这意味着我不能使用这里的通用解决方案或其简单的变体。
所以这是我整理的一个版本,可以解决这个问题。它还显示出使用构造函数参数时 .NET 4.0 Activator 中的轻微缓慢:
下面是一些性能结果。请注意,这是为了创建 100 万个对象,并且计时再次以毫秒为单位:
I've been doing a bit of testing with this and as a follow-up to Miron's original article (here), I've discovered that the .NET 4.0 Activator is much faster than before. Some results from a version of his app tweaked to show timings in milliseconds:
However this was for a no-parameter constructor and I also noticed that the activator is still a little slow when constructors with parameters are used, as can bse seen below.
One problem I had with the original solution posted here is that I don't necessarily know the type of the objects I want at compile time - I only have a Type reference. Now (unless I'm being a duffer) that means I can't use the generic solution here or a simple variation on it.
So this is a version I've knocked together which addresses the problem. It also showed up the slight slowness in the .NET 4.0 Activator when constructor parameters are used:
Some performance results below. Note this is for creation of 1 million objects and timings in milliseconds again:
根据我的测试,我将其作为迄今为止使用当前(2010-01-11)答案的最佳性能对象创建工厂。我确实注意到,当迭代次数低于 99,999 时,使用缓存效果最佳。如果要加载超过 99,999 个,最好不要使用缓存。因为可能是这种情况,所以我创建了一些允许您使用缓存或不使用缓存的东西。我当前的项目有时会加载数百万条记录,有时只加载一条记录。不管怎样,我把这个放在那里看看你的想法是什么。请注意,下面的代码适用于具有 1 个参数的 ctor,因此必须为超过 1 个参数的 ctor 创建一个类似的工厂。
I'm putting this up as the so far best performant object creation factory so far using the current (2010-01-11) answers, according to my tests. I did notice that using cache works best when iterations are somewhere below 99,999. If you are going to load more than 99,999 it is best to not use cache. Because this could be the case I've created something that would allow you to use cache or not. My current project will sometimes load millions of records and at other times only load one. Anyways, I'm putting this out there to see what your thoughts are. Note that the code below is for ctor's that have 1 arg, one would have to create a similar factory for more than 1 arg ctor.
我不久前写过这个。它使用 .NET 3.5 Linq Expression 树而不是发出 IL,这几乎同样快且更易于维护。它最多可以使用 4 个构造函数参数。
像您想要的那样使用任何构造函数参数可能会慢一点,因为根据参数类型查找构造函数,但它仍然比使用反射快得多。我认为,对于 IL 发射,还必须进行一些查找。您必须指定要构造的确切类型,因为它不是 IOC/DI 容器。也许您可以根据您的需要扩展和调整它。
尽情享受吧! :->
I wrote this a while ago. It's using the .NET 3.5 Linq Expression trees rather than emitting IL, which is almost as fast, and more maintainable. It can take up to 4 constructor arguments.
Using any constructor arguments like you want to do might be a bit slower however due to looking up the constructor based on the argument types, but it's still much faster than with reflection. Also with IL emission there would have to be some lookup I think.You have to specify the exact type which you want to construct as it's no IOC/DI container. Maybe you can extend and adapt it to your needs.
Have teh funz with it! :->
我不知道 new T() 在泛型类中很慢。我的问题确实是别的问题 - 如果我知道 T 在编译时是什么,那就没问题了,但我想要一种快速的方法来实例化运行时由配置信息(即包含程序集/类名称的字符串)指定的类。我使用 Type 对象的缓存来加载程序集并仅在其中定位类型一次,因此最后一个障碍是快速创建实例,这是我上一篇文章的主题。
不管怎样,根据我的发现,.NET 4.0 在这类事情上更快,我用你的示例版本进行了测试,调用每个 CreateNewInstxxx 方法 1,000,000 次。计时以毫秒为单位:
所以,是的,.NET 4.0 也比以前快得多。编译器为 CreateNewInstStd() 方法生成的代码在这两种情况下看起来都是这样,因此速度似乎取决于改进的 Activator.CreateInstance() 方法:
编辑:添加当 GenericClass 通过以下方式限制为引用类型时,性能大致相同:
在这种情况下,编译器生成的代码就是这样:
I had no idea that new T() was slow in a generic class. My problem is really something else though - if I knew what T was at compile time I'd be fine but I want a quick way to instantiate a class specified by configuration information (i.e. strings holding assembly / class names) at runtime. I use a cache of Type objects to load the assembly and locate the type in it only once, so the last hurdle is to quickly create instances, which was the subject of my previous post.
Anyhow, following from my finding that .NET 4.0 is quicker at this sort of thing I tested with a version of your example, calling each CreateNewInstxxx method 1,000,000 times. Timings in milliseconds:
So yes, .NET 4.0 is much faster than previously here too. The code generated by the compiler for the CreateNewInstStd() method looks like this in both cases so it seems the speedup is down to the improved
Activator.CreateInstance<T>()
method:Edit: To add that performance is roughly the same when the GenericClass is constrained to reference types via this:
and in that case the compiler-generated code is then just this:
我通常使用以下方法来完全避免泛型类中的
Activator.CreateInstance()
。我需要将
Func
委托传递给构造函数。如果需要实例化 T 的新实例,该类将调用此方法。这将使实例化与非泛型类一样快,并且在我看来比发出 IL 的复杂类更简单、更优雅。使用此方法时的另一个好处(VS New T())是您可以使用参数化构造函数实例化一个类。编辑:我已根据 BillW 的请求使用参数化构造函数示例更新了代码。
I typically use the following method to completely avoid
Activator.CreateInstance()
in generic classes.I require a
Func<T>
delegate to be passed to it it the constructor. The class will call this if it needs to instantiate a new instance of T. This will make instantiation as quick as in a non-generic class and is more simple and elegant IMO than a complex class that emits IL. Another upside (VSNew T())
when using this method is that you can instantiate a class with a parametrized constructor.Edit: I have updated the the code with a parametrized constructor example per BillW's request.
如果您不使用简单的方法,您的探查器是否向您显示大量时间花费在 Activator.CreateInstance() (而不是它调用的任何构造函数)上。如果不是这种情况,只需使用 Activator.CreateInstance。 (似乎没有带有 ctor 参数的通用 CreateInstance() 方法,但有一个非通用方法)。
Has your profiler shown you that a large amount of time is spent in Activator.CreateInstance() (and not any constructor called by it) if you don't use the simple approach. If that is not the case, just use Activator.CreateInstance. (There seems to not be a generic CreateInstance() method with ctor arguments, but there is a non-generic one).
接下来是将 @thames 答案转换为 VB.NET。
请注意,使用 //' 进行注释可以使代码着色器正常工作。将此代码粘贴到您的项目中后,将 //' 替换为 '.
To follow is @thames answer converted to VB.NET.
Note the use //' for comments allows SO code colorizer to work correctly. After pasting this code into your project replace //' with '.
我认为您需要更改的第一件事是 CreateObject 委托。我会这样做:
然后,这是从
ConstructorInfo
生成CreateObject
委托的好方法(使用表达式树):该编译器的完整扩展版本位于我的个人存储库。随意浏览那里的任何内容。
更新:存储库链接已更改。
I think the first thing you need to change is the
CreateObject
delegate. I would do it like this:Then, here's a good way to generate a
CreateObject
delegate from aConstructorInfo
(using expression trees):A full extended version of this compiler is in my personal repository. Feel free to browse whatever is there.
Update: repository link changed.