如何检索泛型方法的名称,包括泛型类型名称

时间:2013-10-01 09:44:15

标签: c# generics reflection

C#中,我有一个带有以下签名的方法:

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

Load()方法内部,我想转储完整的方法名称(用于调试目的),包括泛型类型。例如:调用Load<SomeRepository>();会写"Load<SomeRepository>"

到目前为止我尝试了:使用MethodBase.GetCurrentMethod()GetGenericArguments()检索信息。

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

检索方法名称有效,但对于通用参数,它总是返回"T"。方法返回Load<T>而不是Load<SomeRepository>(这是无用的)

我试图在GetGenericArguments()之外调用GetMethodName()并将其作为参数提供,但它没有帮助。

我可以提供typeof(T)作为GetMethodName()的参数(它会起作用)但是它会特定于通用类型的数量,例如:使用Load<T, U>它将不再起作用,除非我提供另一个论点。

4 个答案:

答案 0 :(得分:1)

根据您的要求,Jeppe Stig Nielsen的答案是正确的。实际上,您的解决方案返回 T ,并返回运行时类型名称。如果你要求不同的东西,那么试着重写你的问题。以下是一个通用项目的另一种解决方案:

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

以下是您要求的输出:

enter image description here

答案 1 :(得分:1)

如果您想要一个通用的解决方案来检索泛型方法的名称和参数,请尝试使用表达式树,如下面的代码示例所示:

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

输出:

enter image description here

答案 2 :(得分:0)

我找到了一个重量级的答案,你的问题在反射之外使用IL。我们的想法是获取我们要转储的调用子方法的父方法体。从反射中我们可以获得IL字节数组,我们可以读取它们并将其转回适当的方法调用以及它们的通用参数的运行时值。

以下是基于您的样本的简化结果代码:

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

注意:

  1. 转储()不需要任何参数。
  2. ILReader 是由Haibo Luo在其网络博客中根据标题为Read IL from MethodBody的文章创建的类的完成版本。
  3. 下面简单完成了罗的课程以及卫星对象:

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

答案 3 :(得分:-1)

看起来你可以使用:

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

ToString()可能会在此背景下被遗漏。

看起来GetCurrentMethod为您提供了“定义”。你必须像这样“制作”构造的通用方法。

此“解决方案”仍然的问题是,如果Load<T>(...)的通用签名更改为例如Load<TRep, TOther>(...),而MakeGenericMethod正文中的Load<,>调用未更新,事情会正常编译,但会在运行时爆炸。

<击> 的 更新:

<击>

找到一个更简单,更好的解决方案:

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

<击>

MSDN上有一个短线程Stack trace for generic method - what was T at runtime?,声称没有简单的解决方案。另请参阅此处的Getting generic arguments from a class in the stack