c#中反射的有效使用



我正在用c#编写一个稍后将用于应用程序的库,我希望该库尽可能高效。不要为了提高效率而把事情弄得太复杂)。然而,我对如何最有效地使用类/方法上的反射有这个问题,为了说明这个问题,我已经将我的类简化了很多,如下所示:

class MyClass
{
    private static Dictionary<string, object> methods;
    public void Method1()
    {
        // Do something.
    }
    public void Method2()
    {
        // Do something else.
    }
}

现在,我想要的是从类(一个私有方法尚未创建)中,接受一个包含方法名的字符串,然后触发方法,就这么简单。最简单的方法是查看名称,获取具有该名称的方法,并执行它,但这迫使我多次使用反射。这个私有方法可能被调用数千次或数万次,并且它需要快速。所以我想到了两种可能的解决方案。正如您可能看到的,我已经添加了一个包含string->object的静态字典(将object替换为实际类型,只是写了object,因为我的两个示例都适用)。然后添加一个静态构造函数,它遍历类并将所有方法添加到方法字典中。接下来的问题是,在创建类的新实例时,我应该创建方法的绑定委托并将它们放在非静态私有字典中,还是应该使用方法字典中的MethodInfo简单地触发方法?

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

显然,如果没有实际尝试并进行性能测试以查看是否达到目标,则没有人可以回答这个问题。

在框架的现代版本中,反射比以前快了很多,但仍然不如简单地调用委托快。

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

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是做什么的?如果缓存中已经有一个操作,我们就完成了。如果没有,则通过反射获取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中"动态"的工作原理,非常简单。由于必须处理任意类型的接收方和参数,我们的问题变得极其复杂。相对而言,你做得很容易。

因为您将获得所有的MethodInfo实例和映射到它们的名称(大概是通过MethodInfo.Name属性),您可以更进一步,以委托的形式创建一个编译的lambda表达式,您可以执行。

首先,假设所有的方法都具有相同的签名。在本例中,它是一个Action<T>委托。这样,您的字典将看起来像这样:

// 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的引入,因为它们将被执行很多很多次)。

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

您还可以从这个类中找到灵感,它扩展了DynamicObject并演示了如何动态调用方法。

但是,如果您希望支持3.5或者保留您的选择,并且您不反对使用第三方库,那么这仍然可以相当容易地完成:

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

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

如果你喜欢自己缓存委托(我们使用ConcurrentDictionary和WeakReferences来做这件事,这意味着它可能会被垃圾收集),只需调用DelegateForCallMethod代替。

库同时支持3.5和4.0。WP7不支持反射。Emit,因此不能使用IL生成和DynamicMethod。然而,WP 7.5确实支持这个功能(但是fasterreflect,这个库的名字,还不支持)。

从你的评论:

好吧,简单地说它连接到服务器,服务器以的形式发送命令。& lt; param1>& lt; param2>…,名称决定应该执行哪些功能。我希望能够添加具有匹配名称的函数(或者更确切地说,我创建了一个属性,允许我命名方法而不是它们的方法名,因为命令名可能是数字),因为名称列表太长了,而且我不想做切换。

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

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的新类,并将相应的行添加到Processor静态构造函数中的commands表中。

public class FooCommand : ICommand {
  public void Execute() {
    // foo away!
  }
}
...
public class Processor {
  static Processor() {
    ...
    commands["foo"] = new FooCommand();
    ...
  }
}
除了性能之外,这种设计还有很多优点。您的命令彼此隔离,更改一个命令或创建新命令不会影响其他命令。它们具有更好的可测试性和更易于维护。例如,如果您可以在配置文件或数据库中维护您的表,甚至处理器也可以关闭(以OCP方式)。

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

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

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是一个结构体,或者这些方法中的任何一个是泛型虚方法,则这并不总是正确工作)。

做事最快的方法就是什么都不做。你有没有考虑过只做这样的接口:

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

这样,如果一些实现想要变得非常聪明,它可以做反射+缓存+IL生成,其他可以简单地使用if/switch。

缺点-显著减少了实现和调试的乐趣。

调用MethodInfo很慢。所以我认为为每个实例创建一个新字典就足够了。另一种选择是使用Expressions(然后将它们存储在静态字典中)创建一个接受实例(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();

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

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

那么你可以使用case语句。

之类的
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 #>();
            <#
            }}
            #>
        }
    }
}

相关内容

  • 没有找到相关文章