7

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

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

我试图打电话给GetGenericArguments()外面GetMethodName()并将其作为参数提供,但它没有帮助。

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

4

4 回答 4

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

这是您要求的输出:

在此处输入图像描述

于 2013-10-01T12:46:25.223 回答
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);
    }
}

输出:

在此处输入图像描述

于 2013-10-01T13:55:13.883 回答
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. Dump()不需要任何参数。
  2. ILReader是罗海波在他的博客中创建的一个类的完成版本,文章标题为从 MethodBody 中读取 IL

下面是罗的类连同卫星物体的简单补全:

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); }
}
于 2013-10-02T10:27:13.190 回答
-1

看起来你可以使用:

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

在这种ToString()情况下,可能会被忽略。

它看起来像GetCurrentMethod给了你“定义”。您将不得不像这样“制作”构造的通用方法。

这个“解决方案”仍然存在一个问题,如果通用签名Load<T>(...)更改为 eg Load<TRep, TOther>(...),并且MakeGenericMethod主体中的调用Load<,>没有更新,事情会编译得很好,但在运行时会崩溃。

更新:

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

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

通用方法有一个简短的线程堆栈跟踪 - 运行时的 T 是什么?在 MSDN 上声称不存在简单的解决方案。另请参阅在 SO 上从堆栈中的类获取通用参数。

于 2013-10-01T09:59:23.120 回答