How to get the name of a generic method, including generic type names

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

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

Inside the Load() method, I would like to reset the full method name (for debugging purposes), including the generic type. for example: calling Load<SomeRepository>(); will write "Load<SomeRepository>"

What I have so far tried: using MethodBase.GetCurrentMethod() and GetGenericArguments() to get 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; } 

Getting the method name works, but for a generic parameter, it always returns me "T" . The method returns Load<T> instead of Load<SomeRepository> (which is useless)

I tried calling GetGenericArguments() outside of GetMethodName() and specifying it as an argument, but that does not help.

I could provide typeof(T) as the parameter to GetMethodName() (it will work), but then it will be specific to the number of common types, for example: with Load<T, U> it will no longer work if I did not provide another argument.

+6
source share
4 answers

Jeppe Stig Nielsen's answer is correct in terms of your requirements. In fact, your solution returns T , and it returns a runtime type name. If you ask for something else, try rewriting your question. Below is another solution for one common element:

 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 result you requested:

enter image description here

+1
source

If you want a general solution for extracting the name and parameters of common methods, try using expression trees, as in the following code example:

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

Output:

enter image description here

+1
source

I found a hard answer to your question that uses IL next to reflection. The idea is to get the body of the parent method, which calls the child method that we want to reset. From the reflection, we can get an array of IL bytes, which we can read and return to the corresponding method calls along with the runtime values ​​of their common parameters.

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

Note:

  • Dump () does not need any arguments.
  • ILReader is a ready-made version of the class created by Haibo Luo in its web block under the heading Read IL from MethodBody .

The following is a simple completion of the Luo class along with satellite 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); } } 
0
source

It looks like you can use:

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

In this context, there will probably be ToString() .

GetCurrentMethod seems to give you a definition. You will have to “make” the constructed general method as follows.

The problem still arises in this “solution” that if the general signature Load<T>(...) changed, for example, Load<TRep, TOther>(...) , and the call to MakeGenericMethod in the body Load<,> not updated, everything will compile fine, but explode at runtime.

UPDATE:

Found a simpler and better solution:

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

Strike>

There is a short chain Stack Trace for a generic method - what was T at runtime? at MSDN, which states that there is no simple solution that exists. See Also Getting general arguments from a class on the stack here on SO.

-1
source

Source: https://habr.com/ru/post/955007/


All Articles