C# 中反射的高效使用
我正在用 C# 编写一个库,稍后将其用于应用程序,并且我希望该库尽可能高效(即不要使事情过于复杂以使其更高效)。但是,我有一个关于如何最有效地使用类/方法上的反射的问题,并说明我已经将我的类简化了很多的问题:
class MyClass
{
private static Dictionary<string, object> methods;
public void Method1()
{
// Do something.
}
public void Method2()
{
// Do something else.
}
}
现在,我想要的是来自类内部的(一个私有方法)来创建),获取一个包含方法名称的字符串,然后触发该方法,就这么简单。最简单的方法是只查看名称,获取具有该名称的方法,然后执行它,但这迫使我多次使用反射。这个私有方法可能会被调用数千或数万次,并且需要很快。所以我想出了两种可能的解决方案。正如您可能看到的那样,我添加了一个包含 string->object 的静态字典(用实际类型替换对象,只是编写了适用于我的两个示例的对象原因)。然后我添加一个静态构造函数,该构造函数遍历类并将所有方法信息添加到方法字典中。然后出现的问题是,在创建类的新实例时,我应该创建方法的绑定委托并将其放入非静态私有字典中,还是应该简单地使用方法字典中的 MethodInfo 来触发方法?
平均用例将创建此类的 10 个实例,并且对方法进行 1000 多次调用,这些方法应该根据其字符串参数触发 Method1 或 Method2(不,switch-case 不是一个选项,因为类,正如所说,这是一个简化版本)。实现这一目标最有效的方法是什么?
I'm writing a library in C# that I will later use for an application, and I want the library to be as efficient as sanely possible (ie. don't over-complicate things too much to make it more efficient). However, I have this question about how to most efficiently use reflection on a class/methods and to illustrate the question I've simplified my class a lot down to this:
class MyClass
{
private static Dictionary<string, object> methods;
public void Method1()
{
// Do something.
}
public void Method2()
{
// Do something else.
}
}
Now, what I want is from within the class (a private method yet to be created), take a string containing the method-name, then fire the method, simple as that. The easiest way of doing this would be to just look at the name, get a method with that name, and execute it, but that forces me to use reflection a lot of times. This private method can potentially be called thousands or tens of thousands of times, and it need to be quick. So I came up with two possible solutions. As you can probably see I've added a static dictionary containing string->object (replacing object with actual type, just wrote object cause that works with both my examples). Then I'd add a static constructor that goes trough the class and adds all the methodinfos to the methods-dictionary. And then comes the question, on creation of a new instance of the class, should I create bound delegates to the methods and put those in a non-static private dict, or should I simply fire the methods using the MethodInfo in the methods-dictionary?
The average use-case will create 10 instances of this class, and have 1000+ calls to the method that should fire either Method1 or Method2 depending on it's string-argument (and no, switch-case is not an option because of the extensibility of the class, as said, this was a simplified version). What would be the most efficient way of achieving this?
如果你对这篇内容有疑问,欢迎到本站社区发帖提问 参与讨论,获取更多帮助,或者扫码二维码加入 Web 技术交流群。

绑定邮箱获取回复消息
由于您还没有绑定你的真实邮箱,如果其他用户或者作者回复了您的评论,将不能在第一时间通知您!
发布评论
评论(8)
显然,如果没有实际尝试并进行性能测试来看看您的目标是否达到,没有人可以回答这个问题。
在框架的现代版本中,反射比以前快得多,但它仍然不如简单调用委托那么快。
我的建议是从您提出的解决方案开始:构建方法信息的缓存一次:
当被要求调用由特定实例上的字符串标识为接收者的方法时,按名称查找方法信息,然后使用给定的接收者。衡量其性能,看看它是否满足您的目标。如果是的话,那就太好了;不要再浪费宝贵的时间试图让已经足够快的东西变得更快。
如果这还不够快,那么我会这样做:
那么 GetActionFromCache 是做什么的?如果缓存中已经有一个操作,那么我们就完成了。如果没有,则通过Reflection获取MethodInfo。然后使用表达式树库构建 Lambda:
现在您手头有一个可以使用实例调用的操作。把那个东西放在缓存里。
顺便说一句,这就是 C# 4 中“动态”工作方式的极其简化的程度。我们的问题非常复杂,因为我们必须处理 任何 类型的接收者和参数。相对来说你很容易。
Obviously no one can answer the question without actually trying it and doing performance tests to see if your goal is met or not.
Reflection is a lot faster in modern versions of the framework than it used to be, but it still is not as fast as simply invoking a delegate.
My suggestion would be to start with your proposed solution: build the cache of method infos once:
When asked to invoke a method identified by a string on a particular instance as the receiver, look up the method info by name and then invoke it with the given receiver. Measure the performance of that and see if it meets your goal. If it does, great; don't waste any more of your valuable time trying to make something faster that is already fast enough.
If that is not fast enough then here's what I'd do:
So what does GetActionFromCache do? If there is already an action in the cache, we're done. If there is not, then obtain the MethodInfo via Reflection. Then use the Expression Tree library to build up a Lambda:
And now you have an action in hand that you can invoke with the instance. Stick that thing in the cache.
This is, incidentally, at an incredibly simplified level, how "dynamic" works in C# 4. Our problem is enormously complicated by the fact that we have to deal with the receiver and arguments being of any type. You have it very easy comparatively.
因为您将获得所有
MethodInfo
实例以及将它们映射到的名称(大概通过MethodInfo.Name
属性,您可以更进一步,以委托的形式创建一个可以执行的已编译 lambda 表达式。您的所有方法都将具有相同的签名,在这种情况下,它是
Action
delegate 这样,您的字典将如下所示:然后,在静态构造函数中,您将使用反射来获取所有 < code>MethodInfo 实例:
然后,您的私有方法将如下所示:
这里的好处是您可以获得编译代码的性能,同时在代码复杂性(创建委托时)上付出很小的代价(IMO) 。诚然,通过委托调用代码会产生一点开销,但这已经得到了很大的改进(这必须通过引入 LINQ 来实现,因为它们会被执行很多很多次)。
Since you will obtain all of the
MethodInfo
instances and the name to map them to (presumably through theMethodInfo.Name
property, you can go one step further and create a compiled lambda expression in the form of a delegate which you can execute.First, it's assumed that all of your methods will have the same signature. In this case, it's an
Action<T>
delegate. With that, your dictionary will look like this:Then, in your static constructor, you would use reflection to get all of the
MethodInfo
instances:Then, your private method would look like this:
The benefit here is that you get the performance of compiled code, while paying a very small price (IMO) in code complexity (in creating the delegate). Granted, there is a slight overhead in calling the code through a delegate, but that's been vastly improved (it had to be with the introduction of LINQ, since they would be executed many, many times).
如果我可以选择,我可能会采纳 Henk 的建议并使用动态。方法调用速度非常快(比普通反射快得多,几乎与普通方法调用一样)。
您还可以通过查看此类< /a>,它扩展了 DynamicObject 并说明了如何动态调用方法。
但是,如果您希望支持 3.5 或保持选择余地,并且不反对使用第 3 方库,那么这仍然可以相当容易地完成:
CallMethod 创建一个 DynamicMethod 委托来调用该特定方法并缓存它,以防您再次调用相同的方法。还有用于调用带有参数的方法的扩展以及大量其他有用的反射助手。
如果您更喜欢自己缓存委托(我们使用 ConcurrentDictionary 和 WeakReferences 来执行此操作,这意味着它可能会被垃圾收集),只需调用
DelegateForCallMethod
即可。该库支持 3.5 和 4.0。 WP7 不支持 Reflection.Emit,因此无法使用 IL 生成和 DynamicMethod。但是,WP 7.5 确实支持此功能(但该库的名称为 Fasterflect,尚不支持)。
If I had the choice here, I would probably go with Henk's suggestion and use dynamic. Method invocations are blazingly fast (much faster than ordinary reflection and almost like normal method calls).
You may also find inspiration by looking at this class, which extends DynamicObject and illustrates how you could do the dynamic invocation of methods.
However, if you wish to support 3.5 or keep your options open and you have no objections to using a 3rd party library, then this can still be fairly easily accomplished:
CallMethod creates a DynamicMethod delegate for invoking that particular method and caches it in case you call the same method again. There are also extensions for invoking methods with parameters and a ton of other useful reflection helpers.
If you prefer to cache the delegate yourself (we use a ConcurrentDictionary and WeakReferences to do this, which means it might get garbage collected), just call
DelegateForCallMethod
instead.The library supports both 3.5 and 4.0. WP7 does not support Reflection.Emit and therefore cannot use IL generation and DynamicMethod. However, WP 7.5 does support this (but Fasterflect, as the library is called, does not yet support it).
从你的评论来看:
您可以使用简单的命令界面和匹配实例(或类型,如果命令实例不可重用)的表驱动工厂来解决此问题。
不涉及反射。
要创建新命令,只需创建一个实现
ICommand
的新类,并将相应的行添加到Processor
静态构造函数内的commands
表中。除了性能之外,这种设计还有很多优点。您的命令是相互隔离的,对一个命令的更改或创建新命令不会影响其他命令。它们更易于测试且更易于维护。如果您可以将表维护为例如,配置文件或数据库。
您当然可以找到替代设计和将参数传递给命令的方法,但我希望这能给您基本的想法。
From your comment:
You can solve this problem with a simple command interface and a table-driven factory for a matching instance (or a type, if the command instances are not reusable).
No reflection involved.
To create a new command, just create a new class that implements
ICommand
and add the corresponding line to thecommands
table inside theProcessor
static constructor.There are many advantages to this design, aside from performace. Your commands are isolated from each other, changes to one command or the creation of new commands won't impact other commands. They're better testable and easier to maintain. Even the processor can be closed (in an OCP way) if you can maintain your table in a config file or a database, for example.
You can certainly find alternative designs and ways to pass parameters to the commands, but I hope this gives you the basic idea.
在这种特殊情况下,您可以稍微不同地声明您的字典并获得您想要的结果::
此代码的优点是不使用代码生成。但是,如果您有不同签名的方法,则需要不同的方法。在这里,我们正在创建开放实例委托。 (请注意,如果 MyClass 是结构体或者这些方法中的任何一个是通用虚拟方法,则这并不总是能正常工作)。
In this particular case you can declare your dictionary slightly differently and get the result you are after::
This code has the advantage of not using code generation. However if you have methods of different signatures then a different approach will be needed. Here we are creating open instance delegates. (Note this doesn't always work correctly if MyClass is a struct or if any of these methods are generic virtual methods).
做某事最快的方法就是根本不做。您是否考虑过制作这样的接口:
这样,如果某些实现想要变得非常智能,它可以进行反射+缓存+IL生成,其他实现可以简单地使用if/switch。
缺点 - 实施和调试的乐趣明显减少。
The fastest way of doing something is not to do it at all. Have you considerer just making interface like:
This way if some of the implemetations wants to be insanely smart it can do reflection+caching+IL generation, the others can simply use if/switch.
Downside - significantly less fun to implement and debug.
调用
MethodInfo
速度很慢。所以我认为为每个实例创建一个新字典应该足够好了。另一种选择是使用表达式创建接受实例 (Action
) 的委托(然后将它们存储在静态字典中):Invoking a
MethodInfo
is slow. So I think creating a new dictionary for each instance should be good enough. Another option is to create a delegate that accepts the instance (Action<MyClass>
) using Expressions (and then store them in the static dictionary):您是否考虑过使用代码生成和 T4 文本模板?
http://msdn.microsoft.com/en-us/library/bb126445.aspx
http://msdn.microsoft.com/en-us/library/bb126478.aspx
然后你可以使用 case 语句。
像这样的东西
Have you considered using Code Generation and T4 Text Templates?
http://msdn.microsoft.com/en-us/library/bb126445.aspx
http://msdn.microsoft.com/en-us/library/bb126478.aspx
Then you could use a case statement.
Something like