1

We are using Expression.Lambda to compile Delegates.

Recently we noticed that the "toplevel" method that gets baked into the target Delegate is missing in the stacktrace when an exception is thrown.

Full Example to reproduce this behaviour:

using System;
using System.Linq.Expressions;
using System.Reflection;

namespace Sandbox
{
    public class Program
    {
        private static void Main()
        {
            var methodInfo = typeof(X).GetMethod(nameof(X.Method1), BindingFlags.Static | BindingFlags.Public);
            var call = Expression.Call(methodInfo);
            var compiledDelegate = Expression.Lambda<Action>(call, null).Compile();

            try
            {
                compiledDelegate();
            }
            catch (Exception e)
            {
                Console.WriteLine(e.ToString());
            }
            Console.ReadKey();
        }

        public class X
        {
            public static void Method1()
            {
                Method2();
            }

            public static void Method2()
            {
                Console.WriteLine("Strange"); 
                throw new Exception();
            }
        }
    }
}

You will get the following StackTrace when running the .exe (releasebuild)

 System.Exception: Eine Ausnahme vom Typ "System.Exception" wurde ausgelöst.
 bei Sandbox.Program.X.Method2() in [..]\Program.cs:Zeile 38.
 bei lambda_method(Closure )
 bei Sandbox.Program.Main() in [..]\Program.cs:Zeile 18.

Notice that Method1 is missing.

My Question here is: How can I make Method1 appear and why isnt it appearing?

Method1 seems to get inlined but that shouldnt remove it from the callstack or am I wrong?

CSharpie
  • 9,195
  • 4
  • 44
  • 71
  • 2
    Inlining means the instructions in `Method1` are directly executed, which would indeed remove it from the stacktrace. What is the behaviour in Debug mode? – Maarten Dec 14 '16 at 12:18
  • Debug makes it appear. – CSharpie Dec 14 '16 at 12:18
  • Try adding a printout line to Method1 and see what happens. – Sergey Kalinichenko Dec 14 '16 at 12:19
  • That makes it appear aswell. But my goal wasnt messing arround to stop it from inlining. – CSharpie Dec 14 '16 at 12:20
  • Just apply the attribute `[System.Runtime.CompilerServices.MethodImpl(System.Runtime.CompilerServices.MethodImplOptions.NoInlining)]` to your method `Method1` and the inlining will not take place. Also see threads like [Inline functions in C#?](http://stackoverflow.com/questions/473782/) – Jeppe Stig Nielsen Dec 14 '16 at 12:25
  • @CSharpie: you can't have the method inlined by default *and* make it appear in the stack trace, if that's your question. Matching symbols retroactively for inlined code is beyond the framework's capabilities. Besides, it would make the stack trace a lie: there is really no stack frame for `Method1` in this case. – Jeroen Mostert Dec 14 '16 at 12:26
  • Note: The same thing happens if you call the method directly (`Program.X.Method1();`) instead of building an expression tree and compiling it into a delegate instance and invoking that. – Jeppe Stig Nielsen Dec 14 '16 at 12:37

1 Answers1

0

It doesn't show up because the JIT compiler is smart enough to inline the method call.

You can prevent it by forcing the compiler not to inline it, which might degrade performance a little:

[MethodImpl(MethodImplOptions.NoInlining)]
public static void Method1()
{
    Method2();
}

It already works in debug build mode since the compiler doesn't optimize those assemblies for easy debugging.

Community
  • 1
  • 1
Patrick Hofman
  • 153,850
  • 22
  • 249
  • 325
  • 2
    To clarify, when you say "forcing _the compiler_ not to inline", you are referring to the jitter, operating (just in time before) run-time, not the C# compiler. – Jeppe Stig Nielsen Dec 14 '16 at 12:38
  • I forgot to mention that i dont have acces to the target type so i iwll probably have to live with it then. @JeppeStigNielsen JIT is a compiler. – CSharpie Dec 14 '16 at 12:38
  • 1
    @JeppeStigNielsen Yes, you are right. You can of course still find the method using Reflection, so it is there, the extra step is just removed by the JITter. – Patrick Hofman Dec 14 '16 at 12:39
  • 1
    I ened up building the Methodname myself Including the parameter types and what not based on the member info and passed it into the repsecting Expression.Lambda-Overload. That did the trick but doesnt feel any better so i am accepting this as the answer. – CSharpie Dec 14 '16 at 12:41