【问题标题】:Find the name of the invoked method of a Func delegate查找 Func 委托的调用方法的名称
【发布时间】:2015-02-20 16:45:54
【问题描述】:

我有一个简单的调用程序,为了能够使用缓存库,我需要知道作为Func 委托参数的对象的调用方法的名称。

 class Program
        {
            static void Main(string[] args)
            {
                var proxy = new Proxy();        
                Invoker.invoke(proxy, p => p.formatSomething("Dumb test"));       
            }
        }

        public class Proxy
        {
            public string formatSomething(string input){

                return String.Format("-===={0}====-", input);
            }
        }


        public static class Invoker
        {        
            public static void invoke(Proxy proxy, Func<Proxy,string> online){                       

             //Some caching logic that require the name of the method 
             //invoked on the proxy (in this specific case "formatSomething")    
             var methodName = ??; 
             if (IsCached(proxyName, methodName)){
                output = GetFromCache(proxyName, methodName);
             }else{       
                output = online(proxy);
             }
            }

        }        

这些是一些可能的(坏的)解决方案:

解决方案一:添加一个字符串参数传递方法名(容易出错)

public static class Invoker
            {        
                public static void invoke(Proxy proxy, Func<Proxy,string> online, string methodName){                       

                 if (IsCached(proxyName, methodName)){
                    output = GetFromCache(proxyName, methodName);
                 }else{       
                    output = online(proxy);
                 }

                }

            } 

解决方案 2: 使用 Expression 可能会出现性能问题。

 public static class Invoker
        {        
            public static void invoke(Proxy proxy, Expression<Func<Proxy,string>> online){                       

             var methodName = ((MethodCallExpression)online.Body).Method.Name;
             if (IsCached(proxyName, methodName)){
                output = GetFromCache(proxyName, methodName);
             }else{       
                output = online.Compile()(proxy);
             }

            }

        } 

解决方案 3: 使用 Expression 作为另一个参数(容易出错)。

 public static class Invoker
        {        
            public static void invoke(Proxy proxy,Func<Proxy,string> online, Expression<Func<Proxy,string>> online2){                       

             var methodName = ((MethodCallExpression)online2.Body).Method.Name;
             if (IsCached(proxyName, methodName)){
                output = GetFromCache(proxyName, methodName);
             }else{       
                output = online(proxy);
             }

            }

        }

您知道其他更好的方法来检查和获取 Invoker 所需的 methodName 吗?

注意:
我没有为在线函数结果搜索缓存机制,因为我已经有了它。
唯一的问题是这个缓存需要在Func委托中调用的代理methodName

【问题讨论】:

  • 我很确定这不会像书面的那样工作,一旦你得到委托就结束了。但是,您可以改为传入Expression&lt;&gt;,并在调用程序中处理编译。然后你可以自己解析Expression&lt;&gt;树,得到函数名。
  • 除了使用Expression&lt;Func&lt;T&gt;&gt;之外,这是无法做到的,只能反编译代码并尝试弄清楚它的作用。如果委托调用两个方法怎么办?你怎么知道你想要哪一个?
  • 给定您的示例(没有捕获值),委托实例将被缓存并且始终相同。您可能应该为此使用一个哈希表,如果没有,仍然使用一个但构造一个更好的哈希/等于实现。
  • 为什么要调用一些缓存逻辑然后调用func呢?

标签: c# reflection delegates func


【解决方案1】:

你需要一个表达式来解析方法的调用名称,但是你可以引入某种两级缓存:一个用于实际的方法调用(不会过期),一个用于方法的调用结果(可能会过期) )。

我认为,您的第二个解决方案朝着正确的方向发展;只编译一次表达式。

public static class Invoker {
    public static void Invoke(Proxy proxy, Expression<Func<Proxy,string>> online) {
        var methodName = ((MethodCallExpression)online.Body).Method.Name;

        if (IsCached(proxyName, methodName)) {
            output = GetFromCache(proxyName, methodName);
        } else {
            if (IsFuncCached(methodName)) {
                func = GetFuncFromCache(methodName);
            } else {
                func = online.Compile();
                // add func to "func cache"...
            }
            output = func(proxy);
        }
    }
}

我试图以你的代码为例,希望它有意义。

【讨论】:

  • @systempuntoout 有帮助吗?如果不是,为什么?
【解决方案2】:

我最近实现了一个用于检查 CLR 方法的 IL 指令的解决方案。

你可以这样使用它:

using System;
using System.Linq;
using Reflection.IL;

namespace StackOverflow
{
    class Program
    {
        static void Main(string[] args)
        {
            var proxy = new Proxy();
            Invoker.invoke(proxy, p => p.formatSomething("Dumb test"));  
        }
    }

    public class Proxy
    {
        public string formatSomething(string input)
        {

            return String.Format("-===={0}====-", input);
        }
    }

    public static class Invoker
    {
        public static void invoke(Proxy proxy, Func<Proxy, string> online)
        {
            //Some caching logic that require the name of the method 
            //invoked on the proxy (in this specific case "formatSomething")    
            var methodName = online.GetCalledMethods().First().Name;

            Console.WriteLine(methodName);
        }
    }
}

请注意,代码没有经过彻底测试或记录,但我认为它应该可以满足您的需求。这里是:

using System;
using System.Collections;
using System.Collections.Generic;
using System.Globalization;
using System.Linq;
using System.Reflection;
using System.Reflection.Emit;

namespace Reflection.IL
{
    public struct ILInstruction
    {
        public OpCode Code { get; private set; }
        public object Operand { get; private set; }

        internal ILInstruction(OpCode code, object operand)
            : this()
        {
            this.Code = code;
            this.Operand = operand;
        }

        public int Size
        {
            get { return this.Code.Size + GetOperandSize(this.Code.OperandType); }
        }

        public override string ToString()
        {
            return this.Operand == null ? this.Code.ToString() : string.Format(CultureInfo.InvariantCulture, "{0} {1}", this.Code, this.Operand);
        }

        private static int GetOperandSize(OperandType operandType)
        {
            switch (operandType)
            {
                case OperandType.InlineBrTarget:
                case OperandType.InlineField:
                case OperandType.InlineI:
                case OperandType.InlineMethod:
                case OperandType.InlineSig:
                case OperandType.InlineString:
                case OperandType.InlineSwitch:
                case OperandType.InlineTok:
                case OperandType.InlineType:
                    return sizeof(int);

                case OperandType.InlineI8:
                    return sizeof(long);

                case OperandType.InlineNone:
                    return 0;

                case OperandType.InlineR:
                    return sizeof(double);

                case OperandType.InlineVar:
                    return sizeof(short);

                case OperandType.ShortInlineBrTarget:
                case OperandType.ShortInlineI:
                case OperandType.ShortInlineVar:
                    return sizeof(byte);

                case OperandType.ShortInlineR:
                    return sizeof(float);

                default:
                    throw new InvalidOperationException();
            }
        }
    }

    public sealed class MethodBodyIL : IEnumerable<ILInstruction>
    {
        private readonly MethodBase method;

        public MethodBodyIL(MethodBase method)
        {
            if (method == null)
                throw new ArgumentNullException("method");

            this.method = method;
        }

        public Enumerator GetEnumerator()
        {
            var body = this.method.GetMethodBody();

            return new Enumerator(this.method.Module, this.method.DeclaringType.GetGenericArguments(), this.method.GetGenericArguments(), body.GetILAsByteArray(), body.LocalVariables);
        }

        IEnumerator<ILInstruction> IEnumerable<ILInstruction>.GetEnumerator()
        {
            return this.GetEnumerator();
        }

        IEnumerator IEnumerable.GetEnumerator()
        {
            return this.GetEnumerator();
        }

        public struct Enumerator : IEnumerator<ILInstruction>
        {
            private static readonly IDictionary<short, OpCode> codes = typeof(OpCodes).FindMembers(MemberTypes.Field, BindingFlags.Public | BindingFlags.Static, (m, criteria) => ((FieldInfo)m).FieldType == typeof(OpCode), null).Cast<FieldInfo>().Select(f => (OpCode)f.GetValue(null)).ToDictionary(c => c.Value);

            private readonly Module module;
            private readonly Type[] genericTypeArguments, genericMethodArguments;
            private readonly byte[] il;
            private readonly IList<LocalVariableInfo> localVariables;

            private int offset;
            private ILInstruction current;

            internal Enumerator(Module module, Type[] genericTypeArguments, Type[] genericMethodArguments, byte[] il, IList<LocalVariableInfo> localVariables)
            {
                this.module = module;
                this.genericTypeArguments = genericTypeArguments;
                this.genericMethodArguments = genericMethodArguments;
                this.il = il;
                this.localVariables = localVariables;

                this.offset = 0;
                this.current = default(ILInstruction);
            }

            public ILInstruction Current
            {
                get { return this.current; }
            }

            public bool MoveNext()
            {
                if (this.offset < this.il.Length)
                {
                    this.current = this.ReadInstruction();
                    return true;
                }
                else
                {
                    this.current = default(ILInstruction);
                    return false;
                }
            }

            public void Reset()
            {
                this.offset = 0;
                this.current = default(ILInstruction);
            }

            public void Dispose()
            {
                this.offset = this.il.Length;
                this.current = default(ILInstruction);
            }

            private ILInstruction ReadInstruction()
            {
                var code = this.ReadCode();

                return new ILInstruction(code, this.ReadOperand(code.OperandType));
            }

            private OpCode ReadCode()
            {
                var code = codes[this.ReadByte()];

                if (code.OpCodeType == OpCodeType.Prefix)
                    code = codes[(short)(code.Value << 8 | this.ReadByte())];

                return code;
            }

            private object ReadOperand(OperandType operandType)
            {
                switch (operandType)
                {
                    case OperandType.InlineBrTarget:
                    case OperandType.InlineI:
                    case OperandType.InlineSwitch:
                        return this.ReadInt32();

                    case OperandType.InlineField:
                    case OperandType.InlineMethod:
                    case OperandType.InlineTok:
                    case OperandType.InlineType:
                        return this.ReadMember();

                    case OperandType.InlineI8:
                        return this.ReadInt64();

                    case OperandType.InlineNone:
                        return null;

                    case OperandType.InlineR:
                        return this.ReadDouble();

                    case OperandType.InlineSig:
                        return this.ReadSignature();

                    case OperandType.InlineString:
                        return this.ReadString();

                    case OperandType.InlineVar:
                        return this.ReadLocalVariable();

                    case OperandType.ShortInlineBrTarget:
                    case OperandType.ShortInlineI:
                        return this.ReadByte();

                    case OperandType.ShortInlineR:
                        return this.ReadSingle();

                    case OperandType.ShortInlineVar:
                        return this.ReadLocalVariableShort();

                    default:
                        throw new InvalidOperationException();
                }
            }

            private byte ReadByte()
            {
                var value = this.il[this.offset];
                ++this.offset;

                return value;
            }

            private short ReadInt16()
            {
                var value = BitConverter.ToInt16(this.il, this.offset);
                this.offset += sizeof(short);

                return value;
            }

            private int ReadInt32()
            {
                var value = BitConverter.ToInt32(this.il, this.offset);
                this.offset += sizeof(int);

                return value;
            }

            private long ReadInt64()
            {
                var value = BitConverter.ToInt64(this.il, this.offset);
                this.offset += sizeof(long);

                return value;
            }

            private float ReadSingle()
            {
                var value = BitConverter.ToSingle(this.il, this.offset);
                this.offset += sizeof(float);

                return value;
            }

            private double ReadDouble()
            {
                var value = BitConverter.ToDouble(this.il, this.offset);
                this.offset += sizeof(double);

                return value;
            }

            private MemberInfo ReadMember()
            {
                return this.module.ResolveMember(this.ReadInt32(), this.genericTypeArguments, this.genericMethodArguments);
            }

            private byte[] ReadSignature()
            {
                return this.module.ResolveSignature(this.ReadInt32());
            }

            private string ReadString()
            {
                return this.module.ResolveString(this.ReadInt32());
            }

            private LocalVariableInfo ReadLocalVariable()
            {
                return this.localVariables[this.ReadInt16()];
            }

            private LocalVariableInfo ReadLocalVariableShort()
            {
                return this.localVariables[this.ReadByte()];
            }

            object IEnumerator.Current
            {
                get { return this.Current; }
            }
        }
    }

    public static class ILHelper
    {
        public static MethodBodyIL GetIL(this MethodBase method)
        {
            return new MethodBodyIL(method);
        }

        public static IEnumerable<MethodBase> GetCalledMethods(this Delegate methodPtr)
        {
            if (methodPtr == null)
                throw new ArgumentNullException("methodPtr");

            foreach (var instruction in methodPtr.Method.GetIL())
                if (IsMethodCall(instruction.Code))
                    yield return (MethodBase)instruction.Operand;
        }

        private static bool IsMethodCall(OpCode code)
        {
            return code == OpCodes.Call || code == OpCodes.Calli || code == OpCodes.Callvirt;
        }
    }
}

【讨论】:

  • 这个(或类似的代码)是对所问问题的准确回答。可能不是应该如何解决整个问题(但这有点超出了问题的范围)。如其他答案所示,我会选择表达式+缓存。
【解决方案3】:

您可以使用method.Name来获取调用方法的名称。

public static class Invoker
{
    public static void invoke(Proxy proxy, Func<Proxy, string> online)
    {
       //Some caching logic that require the name of the method 
       //invoked on the proxy (in this specific case "formatSomething")    
       var methodName = online.Method.Name;
    }
}

https://msdn.microsoft.com/en-us/library/system.multicastdelegate%28v=vs.110%29.aspx

【讨论】:

  • 委托名称与委托内部调用的函数名称完全无关(即,您的示例给出类似“
    b__0”而不是“formatString”)
  • 是的,你是对的。使用问题中的示例代码,它不起作用。如果方法名称被分配为委托引用,这将起作用。感谢您指出这一点。
【解决方案4】:

检查以下代码。如果要获取方法 FULL_NAME 则在第一行写入以下内容#define FULL_NAME

public class Cache
{
    private const uint DefaultCacheSize = 100;

    private readonly Dictionary<string, object> _cache = new Dictionary<string, object>();
    private readonly object _cacheLocker = new object();
    private readonly uint _cacheSize;

    public Cache(uint cacheSize = DefaultCacheSize)
    {
        _cacheSize = cacheSize;
    }

    public uint CacheSize
    {
        get { return _cacheSize; }
    }

    public TValue Resolve<TObj, TValue>(TObj item, Func<TObj, TValue> func, [CallerMemberName] string key = "")
    {
#if FULL_NAME
        var stackTrace = new StackTrace();
        var method = stackTrace.GetFrame(1).GetMethod();
        key = string.Format("{0}_{1}",
            method.DeclaringType == null ? string.Empty : method.DeclaringType.FullName,
            method.Name);
#endif
        return CacheResolver(item, func, key);
    }

    private TValue CacheResolver<TObj, TValue>(TObj item, Func<TObj, TValue> func, string key)
    {
        object res;
        if (_cache.TryGetValue(key, out res) && res is TValue)
        {
            return (TValue) res;
        }

        TValue result = func(item);

        lock (_cacheLocker)
        {
            _cache[key] = result;

            if (_cache.Keys.Count > DefaultCacheSize)
            {
                _cache.Remove(_cache.Keys.First());
            }
        }

        return result;
    }
}

以及用法(来自表单对象):

private void CacheTest()
{
    var cache = new Cache();
    var text = cache.Resolve<Form, string>(this, f => f.Text);
}

希望对你有帮助。

编辑我使用没有性能问题的表达式进行了测试,第一次大约需要 25 毫秒。您可以将其适配为Cache类,以便提取参数Expression&lt;Func&lt;T, T1&gt;&gt;的方法调用表达式。

private string GetExpressionMethodCallName<T, T1>(Expression<Func<T, T1>> exp)
{
    var mce = exp.Body as MethodCallExpression;
    if (mce == null)
        throw new InvalidOperationException("invalid expression");

    return mce.Method.Name;
}

【讨论】:

  • 这得到了错误方法的全名,恐怕。 func 在他想要它的名字时还没有被调用。
猜你喜欢
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2011-07-23
  • 1970-01-01
  • 1970-01-01
相关资源
最近更新 更多