简体   繁体   English

在C#中有效使用反射

[英]Efficient use of reflection in C#

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). 我正在用C#编写一个库,我稍后将其用于一个应用程序,我希望该库尽可能高效(即不要过于复杂化,以使其更高效)。 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). 你可能会看到我添加了一个包含string-> object的静态字典(用实际类型替换对象,只写了对象,因为它适用于我的两个例子)。 Then I'd add a static constructor that goes trough the class and adds all the methodinfos to the methods-dictionary. 然后我将添加一个静态构造函数,该构造函数通过类并将所有methodinfos添加到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? 然后问题是,在创建类的新实例时,我应该创建方法的绑定委托并将它们放在非静态私有字典中,或者我应该只使用方法字典中的MethodInfo来触发方法?

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). 平均用例将创建此类的10个实例,并且对该方法进行1000次以上的调用,该方法应根据其字符串参数触发Method1或Method2(并且不,因为可扩展性,switch-case不是一个选项如上所述,这是一个简化的版本)。 What would be the most efficient way of achieving this? 实现这一目标的最有效方法是什么?

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: 我的建议是从你提出的解决方案开始:一次构建方法信息的缓存:

class MyClass
{
    static Dictionary<string, MethodInfo> cache = new ...
    public void InvokeByName(string name)
    {
        MethodInfo methodInfo = GetMethodInfoFromCache(name);
        methodInfo.Invoke(this, new object[] {});
    }

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. 当要求调用由特定实例上的字符串标识的方法作为接收器时,按名称查找方法info,然后使用给定的接收器调用它。 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: 如果这还不够快那么这就是我要做的事情:

class MyClass
{
    static Dictionary<string, Action<MyClass>> cache = new ...
    public void InvokeByName(string name)
    {
        GetActionFromCache(name).Invoke(this);            
    }

So what does GetActionFromCache do? 那么GetActionFromCache有什么作用呢? If there is already an action in the cache, we're done. 如果缓存中已有操作,我们就完成了。 If there is not, then obtain the MethodInfo via Reflection. 如果没有,则通过Reflection获取MethodInfo。 Then use the Expression Tree library to build up a Lambda: 然后使用表达式树库来构建Lambda:

var methodInfo = SomehowGetTheMethodInfo(name);
// We're going to build the lambda (MyType p)=>p.<named method here>()    
var p = Expression.Parameter(typeof(MyType), "p"));
var call = Expression.Call(p, methodInfo);
var lambda = Expression.Lambda<Action<MyType>>(call, p);
var action = lambda.Compile();

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. 顺便提一下,这是一个非常简化的层面,C#4中的“动态”是如何工作的。我们的问题非常复杂,因为我们必须处理接收器和任何类型的参数。 You have it very easy comparatively. 你比较容易。

Since you will obtain all of the MethodInfo instances and the name to map them to (presumably through the MethodInfo.Name property, you can go one step further and create a compiled lambda expression in the form of a delegate which you can execute. 因为您将获得所有MethodInfo实例和要映射它们的名称(可能通过MethodInfo.Name属性,您可以更进一步,以委托的形式创建一个可以执行的已编译的lambda表达式。

First, it's assumed that all of your methods will have the same signature. 首先,假设您的所有方法都具有相同的签名。 In this case, it's an Action<T> delegate . 在这种情况下,它是一个Action<T>委托 With that, your dictionary will look like this: 有了它,你的字典将如下所示:

// No need to have the dictionary **not** readonly
private static readonly IDictionary<string, Action<MyClass>> methods =
    new Dictionary<string, Action<MyClass>>;

Then, in your static constructor, you would use reflection to get all of the MethodInfo instances: 然后,在静态构造函数中,您将使用反射来获取所有MethodInfo实例:

static MyClass()
{
    // Cycle through all the public instance methods.
    // Should filter down to make sure signatures match.
    foreach (MethodInfo methodInfo in typeof(MyClass).
        GetMethods(BindingFlags.Public | BindingFlags.Instance))
    {
        // Create the parameter expression.
        ParameterExpression parameter = Expression.
            Parameter(typeof(MyClass), "mc");

        // Call the method.
        MethodCallExpression body = Expression.Call(pe, methodInfo);

        // Compile into a lambda.
        Action<MyClass> action = Expression.Lambda<Action<MyClass>>(
            body, parameter).Compile();

        // Add to the dictionary.
        methods.Add(methodInfo.Name, action);
    }
}

Then, your private method would look like this: 然后,您的私有方法将如下所示:

private void ExecuteMethod(string method)
{
    // Add error handling.
    methods[method]();
}

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). 这样做的好处是,您可以获得编译代码的性能,同时在代码复杂性(创建委托)中支付非常小的价格(IMO)。 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). 当然,通过委托调用代码会有一些轻微的开销,但是已经大大改进了(它必须与LINQ的引入一起,因为它们将被执行很多次)。

If I had the choice here, I would probably go with Henk's suggestion and use dynamic. 如果我在这里有选择,我可能会选择Henk的建议并使用动态。 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. 您还可以通过查看此类来寻找灵感, 该类扩展了DynamicObject并说明了如何动态调用方法。

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: 但是,如果您希望支持3.5或保持您的选项打开并且您不反对使用第三方库 ,那么这仍然可以很容易地完成:

void Invoke( string methodName )
{
    this.CallMethod( methodName );
}

CallMethod creates a DynamicMethod delegate for invoking that particular method and caches it in case you call the same method again. CallMethod创建一个DynamicMethod委托,用于调用该特定方法并缓存它,以防您再次调用相同的方法。 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. 如果您更喜欢自己缓存委托(我们使用ConcurrentDictionary和WeakReferences来执行此操作,这意味着它可能会收集垃圾),只需调用DelegateForCallMethod

The library supports both 3.5 and 4.0. 该库支持3.5和4.0。 WP7 does not support Reflection.Emit and therefore cannot use IL generation and DynamicMethod. WP7不支持Reflection.Emit,因此不能使用IL生成和DynamicMethod。 However, WP 7.5 does support this (but Fasterflect, as the library is called, does not yet support it). 但是,WP 7.5确实支持这一点(但是Fasterflect,因为调用了库,还不支持它)。

From your comment: 来自你的评论:

Well, simply put it's connected to a server, the server sends commands in the form of <name> <param1> <param2> ... <paramN>, and the name determines what functionality should be performed. 好吧,简单地说它连接到服务器,服务器以<name> <param1> <param2> ... <paramN>的形式发送命令,名称决定了应该执行哪些功能。 I want to be able to just add functions with matching names (or rather, I've created an attribute that lets me name methods other than their method-name cause command-names might be numeric), cause the list of names is looooong, and I don't want to do a switch-case. 我希望能够添加具有匹配名称的函数(或者更确切地说,我创建了一个属性,让我可以命名方法名称以外的方法,因为命令名称可能是数字),因为名称列表是looooong,我不想做一个转换案例。

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). 您可以使用简单的命令接口和用于匹配实例的表驱动工厂来解决此问题(如果命令实例不可重用,则可以使用类型)。

public interface ICommand {
  void Execute();
}

public class Processor {
  private static Dictionary<string, ICommand> commands;
  static Processor() {
    // create and populate the table
  }
  public void ExecuteCommand(string name) {
    // some validation...
    commands[name].Execute();
  }
}

No reflection involved. 没有反思。

To create a new command, just create a new class that implements ICommand and add the corresponding line to the commands table inside the Processor static constructor. 要创建新命令,只需创建一个实现ICommand的新类,并将相应的行添加到Processor静态构造函数内的commands表中。

public class FooCommand : ICommand {
  public void Execute() {
    // foo away!
  }
}

...

public class Processor {
  static Processor() {
    ...
    commands["foo"] = new FooCommand();
    ...
  }
}

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. 例如,如果您可以在配置文件或数据库中维护表,则甚至可以关闭处理器(以OCP方式)。

You can certainly find alternative designs and ways to pass parameters to the commands, but I hope this gives you the basic idea. 您当然可以找到将参数传递给命令的替代设计和方法,但我希望这能为您提供基本的想法。

In this particular case you can declare your dictionary slightly differently and get the result you are after:: 在这种特殊情况下,您可以略微区别地声明您的字典,并获得您之后的结果::

class MyClass
{
    private static Dictionary<string, Action<MyClass>> methods;

    public void Method1()
    {
        // Do something.
    }

    public void Method2()
    {
        // Do something else.
    }
    static MyClass(){
       methods = new Dictionary<string, Action<MyClass>>();
       foreach(var method in typeof(MyClass).GetMethods(
               BindingFlags.Public | BindingFlags.Instance)
       )
        {
            methods.Add(
                method.Name,
                Delegate.CreateDelegate(typeof(Action<MyClass>),method) 
                  as Action<MyClass>);
        }
    }
}

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). (注意,如果MyClass是结构或者这些方法中的任何一个是通用虚方法,则这并不总是正常工作)。

The fastest way of doing something is not to do it at all. 做事的最快方法就是不要这样做。 Have you considerer just making interface like: 你有没有考虑过如下界面:

interface ICallMe 
{
 void CallByName(string name, object args);
}

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. 这样一来,如果某些实现想要疯狂智能它可以做反射+缓存+ IL生成,其他人可以简单地使用if / switch。

Downside - significantly less fun to implement and debug. 缺点 - 实施和调试的乐趣明显减少。

Invoking a MethodInfo is slow. 调用MethodInfo很慢。 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): 另一种选择是使用表达式创建一个接受实例( Action<MyClass> )的委托(然后将它们存储在静态字典中):

MethodInfo method = typeof(MyClass).GetMethod("Method1");

var parameter = Expression.Parameter(typeof(MyClass));

var call = Expression.Call(parameter, method);

var lambda = Expression.Lambda<Action<MyClass>>(call, parameter);

Action<MyClass> del = lambda.Compile();

Have you considered using Code Generation and T4 Text Templates ? 您是否考虑过使用代码生成和T4文本模板

http://msdn.microsoft.com/en-us/library/bb126445.aspx http://msdn.microsoft.com/en-us/library/bb126445.aspx
http://msdn.microsoft.com/en-us/library/bb126478.aspx http://msdn.microsoft.com/en-us/library/bb126478.aspx

Then you could use a case statement. 然后你可以使用case语句。

Something like 就像是

partial Class MyClass
{
    public void Exec(string funcToExec)
    {
        swtich(funcToExec)
        {
            <#
            foreach(MethodInfo mi in 
                typeof(MyClass).GetMethods(BindingFlags.Public | BindingFlags.Static)
            { 
                if(mi.Name != "Exec"){
            #>
            case : "<#= mi.Name #>"
                <#= mi.Name #>();
            <#
            }}
            #>
        }
    }
}

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM