在C#中有效使用反射

时间:2011-10-28 16:47:10

标签: c# algorithm reflection

我正在用C#编写一个库,我稍后将其用于一个应用程序,我希望该库尽可能高效(即不要过多地复杂化以使其更有效) 。但是,我有一个关于如何最有效地在类/方法上使用反射的问题,并说明我已经简化了我的课程的问题:

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

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

    public void Method2()
    {
        // Do something else.
    }
}

现在,我想要的是来自类(一个尚未创建的私有方法),取一个包含method-name的字符串,然后触发方法,就这么简单。最简单的方法是查看名称,获取具有该名称的方法并执行它,但这迫使我多次使用反射。这种私有方法可能被称为数千或数万次,并且需要快速。所以我想出了两个可能的解决方案。您可能已经看到我添加了一个包含string-&gt;对象的静态字典(用实际类型替换对象,只写了对象,因为它适用于我的两个示例)。然后我将添加一个静态构造函数,该构造函数通过类并将所有methodinfos添加到methods-dictionary中。然后问题是,在创建类的新实例时,我应该创建方法的绑定委托并将它们放在非静态私有字典中,或者我应该只使用方法字典中的MethodInfo来触发方法?

平均用例将创建此类的10个实例,并且对该方法有1000多个调用,应根据它的字符串参数触发Method1或Method2(并且不,switch-case不是一个选项,因为如上所述,该类的可扩展性,这是一个简化版本)。实现这一目标的最有效方法是什么?

8 个答案:

答案 0 :(得分:12)

显然没有人可以在没有实际尝试并进行性能测试的情况下回答这个问题,看看你的目标是否得到满足。

现代版本的框架中的反射比以前快得多,但它仍然没有简单地调用代理那么快。

我的建议是从你提出的解决方案开始:一次构建方法信息的缓存:

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

当要求调用由特定实例上的字符串标识的方法作为接收方时,按名称查找方法信息,然后使用给定的接收方调用它。衡量它的表现,看看它是否符合你的目标。如果确实如此,很棒;不要浪费你宝贵的时间来尝试更快速地创造足够快的东西。

如果这还不够快,那就是我要做的事情:

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

那么GetActionFromCache有什么作用?如果缓存中已有操作,我们就完成了。如果没有,则通过Reflection获取MethodInfo。然后使用表达式树库来构建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();

现在你手头有一个可以用实例调用的动作。把那东西放在缓存中。

顺便提一下,这是一个非常简化的层面,C#4中的“动态”是如何工作的。我们的问题非常复杂,因为我们必须处理接收器和任何类型。你比较容易。

答案 1 :(得分:4)

由于您将获得所有MethodInfo个实例以及要将它们映射到的名称(可能是通过MethodInfo.Name属性,因此您可以更进一步,以形式创建一个已编译的lambda表达式你可以执行的代表。

首先,假设您的所有方法都具有相同的签名。在这种情况下,它是Action<T> delegate。有了它,你的字典将如下所示:

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

然后,在静态构造函数中,您将使用反射来获取所有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);
    }
}

然后,您的私有方法将如下所示:

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

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

答案 2 :(得分:2)

如果我在这里有选择,我可能会选择Henk的建议并使用动态。方法调用非常快(比普通反射快得多,几乎和普通的方法调用一样)。

您还可以通过查看扩展DynamicObject的this class找到灵感,并说明如何动态调用方法。

但是,如果您希望支持3.5或保持您的选项开放,并且您不反对使用3rd party library,那么这仍然可以很容易地实现:

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

CallMethod创建一个DynamicMethod委托,用于调用该特定方法并缓存它,以防再次调用相同的方法。还有用于调用带参数的方法和大量其他有用的反射助手的扩展。

如果您更喜欢自己缓存委托(我们使用ConcurrentDictionary和WeakReferences来执行此操作,这意味着它可能会收集垃圾),只需调用DelegateForCallMethod

该库支持3.5和4.0。 WP7不支持Reflection.Emit,因此不能使用IL生成和DynamicMethod。但是,WP 7.5支持这一点(但是Fasterflect,因为调用了库,但还不支持它)。

答案 3 :(得分:2)

来自你的评论:

  
    

好吧,只需将其连接到服务器,服务器就会以&lt; name&gt;形式发送命令。 &LT;参数1&GT; &LT; param2的&GT; ...&lt; paramN&gt;,名称决定了应该执行的功能。我希望能够添加具有匹配名称的函数(或者更确切地说,我创建了一个属性,让我可以命名方法名称以外的方法,因为命令名称可能是数字),因为名称列表是looooong,而且我不想做一个转换案例。

  

您可以使用简单的命令接口和表驱动的工厂来解决此问题,以匹配实例(或者,如果命令实例不可重用,则为类型)。

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();
  }
}

不涉及反思。

要创建新命令,只需创建一个实现ICommand的新类,并将相应的行添加到commands静态构造函数中的Processor表中。

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

...

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

除了性能之外,这种设计还有许多优点。您的命令彼此隔离,对一个命令的更改或新命令的创建不会影响其他命令。它们更易于测试且易于维护。例如,如果您可以将表维护在配置文件或数据库中,甚至可以关闭处理器(以OCP方式)。

您当然可以找到将参数传递给命令的替代设计和方法,但我希望这能为您提供基本的想法。

答案 4 :(得分:1)

在这种特殊情况下,您可以稍微不同地声明您的字典,并获得您在::

之后的结果
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>);
        }
    }
}

此代码的优点是不使用代码生成。但是,如果您有不同签名的方法,则需要采用不同的方法。这里我们创建开放实例委托。 (注意,如果MyClass是结构或者这些方法中的任何一个是通用虚方法,则这并不总是正常工作)。

答案 5 :(得分:0)

最快的做事方法就是不要这样做。你有没有考虑过如下界面:

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

这样一来,如果某些设备想要非常智能,它可以进行反射+缓存+ IL生成,其他人可以简单地使用if / switch。

下行 - 实施和调试的乐趣明显减少。

答案 6 :(得分:0)

调用MethodInfo的速度很慢。所以我认为为每个实例创建一个新的字典应该足够好了。另一种选择是使用表达式创建一个接受实例(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();

答案 7 :(得分:0)

您是否考虑过使用代码生成和T4文本模板

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

然后你可以使用案例陈述。

这样的东西
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 #>();
            <#
            }}
            #>
        }
    }
}