How to retrieve name of a generic method, includin

2019-06-20 07:10发布

问题:

In C#, I have a method with the following signature :

List<T> Load<T>(Repository<T> repository) 

Inside Load() method, i'd like to dump full method name (for debugging purposes), including the generic type. eg : calling Load<SomeRepository>(); would write "Load<SomeRepository>"

What i have try so far : using MethodBase.GetCurrentMethod() and GetGenericArguments() to retrieve information.

List<T> Load<T>(Repository<T> repository) 
{
   Debug.WriteLine(GetMethodName(MethodBase.GetCurrentMethod()));
}

string GetMethodName(MethodBase method)
{
     Type[] arguments = method.GetGenericArguments();
     if (arguments.Length > 0)
        return string.Format("{0}<{1}>", 
          method.Name, string.Join(", ", arguments.Select(x => x.Name)));
     else
        return method.Name;
}

Retrieving method name works, but for generic parameter it always return me "T". Method returns Load<T> instead of Load<SomeRepository> (which is useless)

I have tried to call GetGenericArguments() outside GetMethodName() and provide it as argument but it doesn't help.

I could provide typeof(T) as a parameter of GetMethodName() (it will works) but then it will be specific to number of generic types eg : with Load<T, U> it would not work anymore, unless I provide the other argument.

回答1:

The answer of Jeppe Stig Nielsen is correct in terms of your requirements. In fact, your solution returns T and his returns the runtime type name. If you ask for something different, then try to rewrite your question. The below is another solution for one generic item:

using System;
using System.Collections.Generic;
using System.Linq;

class Program
{
    static void Main()
    {
        Load(new Repository<int>());
        Load(new Repository<string>());
        Console.ReadLine();
    }

    class Repository<T> { }

    static List<T> Load<T>(Repository<T> repository)
    {
        Console.WriteLine("Debug: List<{1}> Load<{1}>({0}<{1}> repository)", typeof(Repository<T>).Name, typeof(Repository<T>).GenericTypeArguments.First());
        return default(List<T>);
    }
}

Here is the output that you asked for:



回答2:

In case if you want to have a generic solution for retrieving name and parameters of generic methods, try to use expression trees as in the following code sample:

using System;
using System.Collections.Generic;
using System.Linq.Expressions;

class Program
{
    static void Main()
    {
        Load(new Repository<int>());
        Load(new Repository<string>());
        Console.ReadLine();
    }

    class Repository<T> { }

    static List<T> Load<T>(Repository<T> repository)
    {
        Dump(() => Load(repository));

        return default(List<T>);
    }

    static void Dump(Expression<Action> action)
    {
        var methodExpr = action.Body as MethodCallExpression;

        if (methodExpr == null)
            throw new ArgumentException();

        var methodInfo = methodExpr.Method;

        Console.WriteLine(methodInfo);
    }
}

The output:



回答3:

I found a heavy weight answer to your question that uses IL beside reflection. The idea is to obtain body of parent method, that invokes child method, that we want to dump. From reflection we are able to obtain array of IL bytes, which we can read and turn back into appropriate method invocations along with the runtime values of their generic parameters.

The below is a simplified result code based on your sample:

using System;
using System.Collections.Generic;
using System.Diagnostics;
using System.Linq;

class Program
{
    static void Main()
    {
        Load(new Respository<int>());
        Load(new Respository<string>());

        Console.ReadLine();
    }

    class Respository<T> { }

    static List<T> Load<T>(Respository<T> repository)
    {
        Dump(); // <-- Just dump this

        return default(List<T>);
    }

    static void Dump()
    {
        // Get the method that invoked the method being dumped
        var callerFrame = new StackFrame(2);
        var callerMethod = callerFrame.GetMethod();

        // Get the method that is being dumped
        var calleeFrame = new StackFrame(1);
        var calleeMethod = calleeFrame.GetMethod();

        // Should return one value
        var callees = from il in new ILReader(callerMethod).OfType<InlineMethodInstruction>()
                      let callee = callerMethod.Module.ResolveMember(il.Token)
                      where callee.Name == calleeMethod.Name && il.Offset == callerFrame.GetILOffset()
                      select callee;

        Console.WriteLine(callees.First());
    }
}

Notice:

  1. Dump() doesn't need any arguments.
  2. ILReader is a finished version of a class created by Haibo Luo in his webblog under the article titled Read IL from MethodBody.

The below is a simple completion of Luo's class along with satelite objects:

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

abstract class ILInstruction
{
}

class SimpleInstruction : ILInstruction
{
    public string Name { get; private set; }

    public SimpleInstruction(string name)
    {
        Name = name;
    }

    public override string ToString()
    {
        return GetType().Name + " " + Name;
    }
}

abstract class MethodBaseInstruction : ILInstruction
{
    public MethodBase Method { get; private set; }

    public MethodBaseInstruction(MethodBase method)
    {
        Method = method;
    }

    public override string ToString()
    {
        return GetType().Name + " " + Method.Name;
    }
}

class InlineNoneInstruction : MethodBaseInstruction
{
    public int Offset { get; private set; }
    public OpCode OpCode { get; private set; }

    public InlineNoneInstruction(MethodBase method, int offset, OpCode opCode)
        : base(method)
    {
        Offset = offset;
        OpCode = opCode;
    }

    public override string ToString()
    {
        return base.ToString() + " " + Offset + " " + OpCode;
    }
}

class ShortInlineBrTargetInstruction : InlineNoneInstruction
{
    public sbyte ShortDelta { get; private set; }

    public ShortInlineBrTargetInstruction(MethodBase method, int offset, OpCode opCode, sbyte shortDelta)
        : base(method, offset, opCode)
    {
        ShortDelta = shortDelta;
    }

    public override string ToString()
    {
        return base.ToString() + " " + ShortDelta;
    }
}

class InlineMethodInstruction : InlineNoneInstruction
{
    public int Token { get; private set; }

    public InlineMethodInstruction(MethodBase method, int offset, OpCode opCode, int token)
        : base(method, offset, opCode)
    {
        Token = token;
    }

    public override string ToString()
    {
        return base.ToString() + " " + Token;
    }
}

class InlineSwitchInstruction : InlineNoneInstruction
{
    public int[] Deltas { get; private set; }

    public InlineSwitchInstruction(MethodBase method, int offset, OpCode opCode, int[] deltas)
        : base(method, offset, opCode)
    {
        Deltas = deltas;
    }

    public override string ToString()
    {
        return base.ToString() + " " + string.Join(", ", Deltas);
    }
}

class ILReader : IEnumerable<ILInstruction>
{
    Byte[] m_byteArray;
    Int32 m_position;
    MethodBase m_enclosingMethod;

    static OpCode[] s_OneByteOpCodes = new OpCode[0x100];
    static OpCode[] s_TwoByteOpCodes = new OpCode[0x100];

    static ILReader()
    {
        foreach (FieldInfo fi in typeof(OpCodes).GetFields(BindingFlags.Public | BindingFlags.Static))
        {
            OpCode opCode = (OpCode)fi.GetValue(null);
            UInt16 value = (UInt16)opCode.Value;
            if (value < 0x100)
                s_OneByteOpCodes[value] = opCode;
            else if ((value & 0xff00) == 0xfe00)
                s_TwoByteOpCodes[value & 0xff] = opCode;
        }
    }

    public ILReader(MethodBase enclosingMethod)
    {
        this.m_enclosingMethod = enclosingMethod;
        MethodBody methodBody = m_enclosingMethod.GetMethodBody();
        this.m_byteArray = (methodBody == null) ? new Byte[0] : methodBody.GetILAsByteArray();
        this.m_position = 0;
    }

    public IEnumerator<ILInstruction> GetEnumerator()
    {
        while (m_position < m_byteArray.Length)
            yield return Next();

        m_position = 0;
        yield break;
    }

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

    ILInstruction Next()
    {
        Int32 offset = m_position;
        OpCode opCode = OpCodes.Nop;
        Int32 token = 0;

        // read first 1 or 2 bytes as opCode
        Byte code = ReadByte();
        if (code != 0xFE)
            opCode = s_OneByteOpCodes[code];
        else
        {
            code = ReadByte();
            opCode = s_TwoByteOpCodes[code];
        }

        switch (opCode.OperandType)
        {
            case OperandType.InlineNone:
                return new InlineNoneInstruction(m_enclosingMethod, offset, opCode);

            case OperandType.ShortInlineBrTarget:
                SByte shortDelta = ReadSByte();
                return new ShortInlineBrTargetInstruction(m_enclosingMethod, offset, opCode, shortDelta);

            case OperandType.InlineBrTarget: Int32 delta = ReadInt32(); return new SimpleInstruction(delta.ToString());
            case OperandType.ShortInlineI: Byte int8 = ReadByte(); return new SimpleInstruction(int8.ToString());
            case OperandType.InlineI: Int32 int32 = ReadInt32(); return new SimpleInstruction(int32.ToString());
            case OperandType.InlineI8: Int64 int64 = ReadInt64(); return new SimpleInstruction(int64.ToString());
            case OperandType.ShortInlineR: Single float32 = ReadSingle(); return new SimpleInstruction(float32.ToString());
            case OperandType.InlineR: Double float64 = ReadDouble(); return new SimpleInstruction(float64.ToString());
            case OperandType.ShortInlineVar: Byte index8 = ReadByte(); return new SimpleInstruction(index8.ToString());
            case OperandType.InlineVar: UInt16 index16 = ReadUInt16(); return new SimpleInstruction(index16.ToString());
            case OperandType.InlineString: token = ReadInt32(); return new SimpleInstruction("InlineString" + token.ToString());
            case OperandType.InlineSig: token = ReadInt32(); return new SimpleInstruction("InlineSig" + token.ToString());
            case OperandType.InlineField: token = ReadInt32(); return new SimpleInstruction("InlineField" + token.ToString());
            case OperandType.InlineType: token = ReadInt32(); return new SimpleInstruction("InlineType" + token.ToString());
            case OperandType.InlineTok: token = ReadInt32(); return new SimpleInstruction("InlineTok" + token.ToString());

            case OperandType.InlineMethod:
                token = ReadInt32();
                return new InlineMethodInstruction(m_enclosingMethod, offset, opCode, token);

            case OperandType.InlineSwitch:
                Int32 cases = ReadInt32();
                Int32[] deltas = new Int32[cases];
                for (Int32 i = 0; i < cases; i++) deltas[i] = ReadInt32();
                return new InlineSwitchInstruction(m_enclosingMethod, offset, opCode, deltas);

            default:
                throw new BadImageFormatException("unexpected OperandType " + opCode.OperandType);
        }
    }

    Byte ReadByte() { return (Byte)m_byteArray[m_position++]; }
    SByte ReadSByte() { return (SByte)ReadByte(); }

    UInt16 ReadUInt16() { m_position += 2; return BitConverter.ToUInt16(m_byteArray, m_position - 2); }
    UInt32 ReadUInt32() { m_position += 4; return BitConverter.ToUInt32(m_byteArray, m_position - 4); }
    UInt64 ReadUInt64() { m_position += 8; return BitConverter.ToUInt64(m_byteArray, m_position - 8); }

    Int32 ReadInt32() { m_position += 4; return BitConverter.ToInt32(m_byteArray, m_position - 4); }
    Int64 ReadInt64() { m_position += 8; return BitConverter.ToInt64(m_byteArray, m_position - 8); }

    Single ReadSingle() { m_position += 4; return BitConverter.ToSingle(m_byteArray, m_position - 4); }
    Double ReadDouble() { m_position += 8; return BitConverter.ToDouble(m_byteArray, m_position - 8); }
}


回答4:

It looks like you can use:

List<T> Load<T>(Repository<T> repository) 
{
  Debug.WriteLine(
    ((MethodInfo)MethodBase.GetCurrentMethod()).MakeGenericMethod(typeof(T)).ToString()
    );
}

The ToString() can probably be left out in this context.

It looks like GetCurrentMethod gives you the "definition". You will have to "make" the constructed generic method like this.

This "solution" still has the problem that if the generic signature of Load<T>(...) is changed to e.g. Load<TRep, TOther>(...), and the MakeGenericMethod call in the body of Load<,> is not updated, things will compile fine but blow up at runtime.

UPDATE:

Found a simpler and better solution:

public static MethodBase GetCurrentMethod()
{
  var sf = new StackFrame(1);
  return sf.GetMethod();
}

There is a short thread Stack trace for generic method - what was T at runtime? on MSDN where it is claimed that no easy solution exists. See also Getting generic arguments from a class in the stack here on SO.