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

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

 List Load(Repository repository) 

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

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

 List Load(Repository repository) { Debug.WriteLine(GetMethodName(MethodBase.GetCurrentMethod())); } string GetMethodName(MethodBase method) { Type[] arguments = method.GetGenericArguments(); if (arguments.Length > 0) return string.Format("{0}", method.Name, string.Join(", ", arguments.Select(x => x.Name))); else return method.Name; } 

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

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

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

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

 using System; using System.Collections.Generic; using System.Linq; class Program { static void Main() { Load(new Repository()); Load(new Repository()); Console.ReadLine(); } class Repository { } static List Load(Repository repository) { Console.WriteLine("Debug: List<{1}> Load<{1}>({0}<{1}> repository)", typeof(Repository).Name, typeof(Repository).GenericTypeArguments.First()); return default(List); } } 

以下是您要求的输出:

在此处输入图像描述

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

 using System; using System.Collections.Generic; using System.Linq.Expressions; class Program { static void Main() { Load(new Repository()); Load(new Repository()); Console.ReadLine(); } class Repository { } static List Load(Repository repository) { Dump(() => Load(repository)); return default(List); } static void Dump(Expression action) { var methodExpr = action.Body as MethodCallExpression; if (methodExpr == null) throw new ArgumentException(); var methodInfo = methodExpr.Method; Console.WriteLine(methodInfo); } } 

输出:

在此处输入图像描述

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

以下是基于您的示例的简化结果代码:

 using System; using System.Collections.Generic; using System.Diagnostics; using System.Linq; class Program { static void Main() { Load(new Respository()); Load(new Respository()); Console.ReadLine(); } class Respository { } static List Load(Respository repository) { Dump(); // <-- Just dump this return default(List); } 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() 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是由Haibo Luo在他的webblog中根据MethodBody的文章Read 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 { 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 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); } } 

看起来你可以使用:

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

ToString()可能在此上下文中被省略。

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

这个“解决方案” 仍然存在这样的问题,即如果Load(...)的通用签名被更改为例如Load(...) ,并且在Load<,>的主体中调用MakeGenericMethod Load<,>没有更新,事情会编译好但在运行时爆炸。

更新:

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

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

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