7

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.

tigrou
  • 4,236
  • 5
  • 33
  • 59
  • The `GetMethodName` method you wrote does not have enough information to give the answer. It is a shame `MethodBase.GetCurrentMethod` doesn't give the answer automatically. – Jeppe Stig Nielsen Oct 01 '13 at 10:09
  • It looks like this this has already been asked in [this question](http://stackoverflow.com/questions/14707298/get-generic-argument-type-and-value-supplied-to-a-generic-method). The answer indicates that it's impossible. – Sam Oct 01 '13 at 10:09
  • Also: [How to distinguish MethodBase in generics](http://stackoverflow.com/questions/1940436/how-to-distinguish-methodbase-in-generics) – sloth Oct 01 '13 at 10:10
  • So it looks like your best bet may be to make the constructed generic method using `MakeGenericMethod` before passing it into `GetMethodName`. – Sam Oct 01 '13 at 10:11

4 Answers4

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:

enter image description here

Community
  • 1
  • 1
Ryszard Dżegan
  • 24,366
  • 6
  • 38
  • 56
1

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:

enter image description here

Ryszard Dżegan
  • 24,366
  • 6
  • 38
  • 56
  • Looks like this could be a step in the right direction, but the problem of having to pass in the types has now been replaced with a new problem: having to pass in the arguments! – Sam Oct 02 '13 at 06:00
  • @Sam see another proposition that overcomes that problem: http://stackoverflow.com/a/19134840/2042090 – Ryszard Dżegan Oct 03 '13 at 14:14
0

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); }
}
Ryszard Dżegan
  • 24,366
  • 6
  • 38
  • 56
-1

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.

Community
  • 1
  • 1
Jeppe Stig Nielsen
  • 60,409
  • 11
  • 110
  • 181
  • He did say that he didn't want to have to pass in the types himself. – Sam Oct 01 '13 at 10:00
  • @Sam he is not passing type of `T`, he uses it to create generic method definition and **then** passes it. I don't understand why all the downvotes. Answer is correct – Ilya Ivanov Oct 01 '13 at 10:01
  • 1
    @IlyaIvanov The answer is not useful IMHO; as the OP already knows that he could simply use `typeof(T)` to get the type of `T` in `Load`. What the OP wants is a single method that he can call from everywhere without worrying about the type parameters. If you use the approach in this answer, you'll have to provide each type of the method to `MakeGenericMethod`, e.g. `...MakeGenericMethod(typeof(T), typeof(U))` when called from `Load`; which is what the OP wants to avoid, as stated in the last paragraph of his question. But I don't know if it's worth a downvote... – sloth Oct 01 '13 at 10:10
  • I updated my answer to acknowledge the problem. Like Ilya I do not think a better solution exists, unfortunately. – Jeppe Stig Nielsen Oct 01 '13 at 10:15
  • 1
    I just tried your second solution, and the `MethodBase` still didn't contain the generic type arguments. Its `IsGenericMethodDefinition` was `true`, too. – Sam Oct 01 '13 at 10:35
  • @Sam You are right. I thought I saw it work, but it did and does not. I have struck out that useless update. – Jeppe Stig Nielsen Oct 01 '13 at 11:56