查找 Func 委托的调用方法的名称

Find the name of the invoked method of a Func delegate

我有一个简单的调用程序,为了能够使用缓存库,我需要知道作为 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);
             }

            }

        }

您是否知道任何其他更好的方法来检查和获取 methodName Invoker 需要的东西?

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

检查以下代码。如果你想得到方法 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;
    }
}

以及用法(来自 Form 对象):

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

希望对你有所帮助

EDIT 我使用表达式进行了测试,没有出现性能问题,第一次大约需要 25 毫秒。您可以将其适配为 Cache class 以提取参数 Expression<Func<T, T1>>.

的方法调用表达式
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;
}

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

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

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

我尝试将您的代码改编为示例,希望它有意义。

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

你可以这样使用它:

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

namespace Whosebug
{
    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;
        }
    }
}

您可以使用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