13

I am trying to find a solution to 'break into non-public methods'.

I just want to call RuntimeMethodInfo.InternalGetCurrentMethod(...), passing my own parameter (so I can implement GetCallingMethod()), or directly use RuntimeMethodInfo.InternatGetCurrentMethod(ref StackCrawlMark.LookForMyCaller) in my logging routines. GetCurrentMethod is implemented as:

[MethodImpl(MethodImplOptions.NoInlining)] 
public static MethodBase GetCurrentMethod() 
{     
    StackCrawlMark lookForMyCaller = StackCrawlMark.LookForMyCaller;     
    return RuntimeMethodInfo.InternalGetCurrentMethod(ref lookForMyCaller); 
}

where InternalGetCurrentMethod is declared: internal :-).

I have no problem calling the method using reflection, but this messes up the call stack and that is just the one thing that has to be preserved, otherwise it defeats its purpose.

What are my odds of keeping the stacktrace close to the original (at least within the distance of the allowed StackCrawlMarks, which are LookForMe, LookForMyCaller and LookForMyCallersCaller. Is there some complex way to achieve what I want?

Strigoides
  • 4,329
  • 5
  • 23
  • 25
Edwin
  • 527
  • 7
  • 15

1 Answers1

20

If there's one thing I love about C#, it's dynamic methods.

They let you bypass every goal and intention of the .NET creators. :D

Here's a (thread-safe) solution:

(Eric Lippert, please don't read this...)

enum MyStackCrawlMark { LookForMe, LookForMyCaller, LookForMyCallersCaller, LookForThread }
delegate MethodBase MyGetCurrentMethodDelegate(ref MyStackCrawlMark mark);
static MyGetCurrentMethodDelegate dynamicMethod = null;

static MethodBase MyGetCurrentMethod(ref MyStackCrawlMark mark)
{
    if (dynamicMethod == null)
    {
        var m = new DynamicMethod("GetCurrentMethod",
            typeof(MethodBase),
            new Type[] { typeof(MyStackCrawlMark).MakeByRefType() },
            true //Ignore all privilege checks :D
        );
        var gen = m.GetILGenerator();
        gen.Emit(OpCodes.Ldarg_0); //NO type checking here!
        gen.Emit(OpCodes.Call,
            Type.GetType("System.Reflection.RuntimeMethodInfo", true)
                .GetMethod("InternalGetCurrentMethod",
                    BindingFlags.Static | BindingFlags.NonPublic));
        gen.Emit(OpCodes.Ret);
        Interlocked.CompareExchange(ref dynamicMethod,
            (MyGetCurrentMethodDelegate)m.CreateDelegate(
                typeof(MyGetCurrentMethodDelegate)), null);
    }
    return dynamicMethod(ref mark);
}
[MethodImpl(MethodImplOptions.NoInlining)]
static void Test()
{
    var mark = MyStackCrawlMark.LookForMe; //"Me" is Test's _caller_, NOT Test
    var method = MyGetCurrentMethod(ref mark);
    Console.WriteLine(method.Name);
}
smartcaveman
  • 41,281
  • 29
  • 127
  • 212
user541686
  • 205,094
  • 128
  • 528
  • 886
  • 1
    This is super cool. I've got to to read up on dynamic methods more. Eric Lippert may be dispatching assassin droids to your house, though, so I wouldn't answer the door for a while. – Justin Morgan - On strike Feb 28 '11 at 16:09
  • 1
    @Justin: As long as they're programmed with C#, I'll know how to deal with them. >:] – user541686 Feb 28 '11 at 16:16
  • Awesome... Works like a charm! And so easy... So finally I can log the method without passing System.Reflection.MethodBase.GetCurrentMethod() to the logging method! I will definately try this as a reflection replacement in other places as well. Btw: for those reading the solution: the method name given to the new DynamicMethod constructor can be anything you like. – Edwin Mar 02 '11 at 10:21
  • do you know any good books or Urls about dynamically generating methods/IL? Would love to read more about it. – Edwin Mar 02 '11 at 10:25
  • @Edwin: Glad it worked! And no, not really... the best place to learn it from is (unfortunately) the MSDN documentation. :\ – user541686 Mar 02 '11 at 16:19
  • @Mehrdad, I copied your code directly but did not receive the expected result (the same as MethodBase.GetCurrentMethod()). I receive the name of a method that is several calls earlier. – smartcaveman Mar 29 '12 at 06:46
  • @smartcaveman: I just re-tested it and got back `Main` as the value, so it seems to be fine here. How are you compiling/running your code? Visual Studio or Mono? 2008 or 2010? .NET 2.0 or 3.5 or 4.0? Debug or Release? NGEN or no NGEN? 32-bit or 64-bit or AnyCPU? etc. – user541686 Mar 29 '12 at 06:50
  • I was using .NET 4.0 on LINQPad (64-bit Windows 7). i looked into it a little, and I'm guessing it's caused by LINQPad doing some rewriting prior to compilation. I'll let you know when I've had a chance to test in VS – smartcaveman Mar 29 '12 at 09:41
  • 2
    And 8 years later, this is still coming in handy. I used this to get custom attributes of the calling derived-class constructor from the base-class constructor. Sure, I could've worked around it in more conventional ways (and had been until I found this), but this fit the bill quite nicely! –  Feb 23 '19 at 03:39