3

I have a simple invoker where, in order to be able to use a cache library , I need to know the name of the invoked method of an object that is a parameter of a Func delegate.

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

        }        

These are some possible (bad) solutions:

Solution 1: Add a string parameter passing the method name (error prone)

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

                }

            } 

Solution 2: using Expression with possible performance issues.

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

            }

        } 

Solution 3: using Expression as another parameter (error prone).

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

            }

        }

Do you know any other better way to inspect and get the methodName the Invoker needs?

NOTE:
I'm not searching a caching mechanism for the online function result because I already have it.
The only problem is that this cache requires the proxy methodName invoked in the Func delegate.

systempuntoout
  • 71,966
  • 47
  • 171
  • 241
  • 4
    I'm fairly certain that won't work as written, once you get the delegate it's over. You can however pass in an `Expression<>` instead, and handle the compilation in the invoker. Then you can parse the `Expression<>` tree yourself to get the function name out. – Blindy Feb 20 '15 at 16:49
  • 2
    Other than using an `Expression>`, this can't be done, other than to decompile the code and try to figure out what it does. What if the delegate calls *two* methods? How would you know which one you want? – Lasse V. Karlsen Feb 20 '15 at 20:40
  • 1
    Given your example (no capture values) the delegate instance will be cached and will always be the same. You should probably use a hashtable for that, if not, still use one but construct a better hash/equals implementation. – leppie Feb 20 '15 at 20:43
  • 2
    Why do you invoke some cache logic and then call the func anyway? – Axel Heer Feb 21 '15 at 11:44
  • 1
    http://stackoverflow.com/a/20544642/29407 – Darin Dimitrov Feb 22 '15 at 09:09
  • It's a XY problem. You want to cache `Func<>` results, and you ask for caching method name inside the `Func<>` (regardless of the param). I think the answer you really search is not related to the question you asked. – rducom Feb 22 '15 at 21:31
  • @Sharped see my note; I do not want cache `Func<>` results; just want to know the invoked method name of the delegate that I need to pass to a cache library I already have. – systempuntoout Feb 22 '15 at 21:49
  • Perhaps you should describe *why* you want this, it may be that some other solution presents itself. – Lasse V. Karlsen Feb 22 '15 at 21:56
  • So the code you give is not clear... With `output = online.Compile()(proxy);` or `output = GetFromCache(proxyName, methodName);`), we might guess you want to get cached results of `Func<>` invocation... If it's not the case, what do you put in cache ? What is this cache for ? Maybe you should be more precise about the goal you have beyond "get the invoked method name to pass it to a cache" ... And maybe you should update your code too... – rducom Feb 22 '15 at 21:56
  • This is just a sample, the real code is more complex but I believe you have all the details. 1. The invoker has a `Func` delegate as input 2. The caching mechanism I need to use (I can't modify it) requires the `methodName` of the func invoked method. What is not clear? I don't get the downvotes guys :/ . – systempuntoout Feb 22 '15 at 22:13
  • It's unclear because of a difference between the code you posted, and the question you asked. Both don't do the same thing : the possible solutions you posted are caching results of Func<> the question you asked is about caching the result associated with a method name inside a Func<> (it's really not the same, and does not seems a good thing to do)... Given that, without more details about what are you trying to achieve, it's obscure... Moreover, your Solution 2 is the right one, I think you just don't have tested it since exploring an expression is really fast. – rducom Feb 22 '15 at 22:43
  • @Sharped Sorry but I do not agree. I've not asked anything related on how to cache something, neither in the question's title and neither in the question itself. The solutions are pretty clear: I'm searching a way to get the methodName of the Func passed as parameter to the Invoker. – systempuntoout Feb 22 '15 at 23:04
  • @Sharped Side note, as far as I know, Compiling an Expression is not fast at all. – systempuntoout Feb 22 '15 at 23:07
  • Compile is ten time slower than a direct Func<> invocation, it's not a bottleneck. If once compiled, all the next calls are cached, where's the problem ? – rducom Feb 22 '15 at 23:23

4 Answers4

4

You need an expression to parse the method's call name, but you can introduce some kind of two-level cache: one for the actual method call (which does not expire), and one for the method's call result (which may expire).

I think, your second solution goes into the right direction; just compile the expression only once.

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

I tried to adapt your code as an example, I hope it makes sense.

Axel Heer
  • 1,863
  • 16
  • 22
1

I have recently implemented a solution for inspecting IL instructions of a CLR method.

You can use it like this:

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

Note that the code is not thoroughly tested nor documented, but I think it should serve your needs. Here it is:

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;
        }
    }
}
Stipo
  • 4,566
  • 1
  • 21
  • 37
  • This (or similar code) is good exact answer to the question as asked. Likely not how whole problem should be solved (but that is somewhat outside of the question). I'd go for Expression+caching as shown in other answer. – Alexei Levenkov Feb 25 '15 at 15:50
1

You can use method.Name to get the calling 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

Parthasarathy
  • 2,698
  • 1
  • 12
  • 14
  • 2
    Name of delegate have absolutely nothing to do with name of finction called inside of the delegate (i.e. your sample gives something like "
    b__0" instead of "formatString")
    – Alexei Levenkov Feb 25 '15 at 15:41
  • Yup you are right. With the sample code from the question, it does not work. This will work if the method name is assigned as the delegate reference. Thanks for pointing that. – Parthasarathy Feb 26 '15 at 08:13
-1

Check the following code. If you want to get the method FULL_NAME then write the following in the first line #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;
    }
}

And the usage(from a Form object):

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

I hope it helps you.

EDIT I tested using expressions without performance issues, takes about ~25ms the first time. You can adapt it to Cache class in order to extract the method call expression of the param 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;
}

GetExpressionMethodCallName Performance

denys-vega
  • 3,522
  • 1
  • 19
  • 24
  • 3
    This gets the full name of the wrong method, I'm afraid. `func` hasn't been called yet when he wants its name. – Ben Voigt Feb 20 '15 at 21:30