ErnieL
ErnieL

Reputation: 5801

Getting Type T from a StackFrame

The goal is to create a generic instance based on the type that called my method.

The problem is that when called from a generic, the StackFrame only appears to contain the open definition type parameters instead of the closed definition type arguments. How do I get type arguments from a StackFrame? Similar to this question. I'd like to think my situation is different since Log.Debug is being called from a closed method.

If StackFrame isn't the right approach, any suggestions other than IoC? This code is meant to fill in the case where a reference to my Unity container isn't available.

using System;
using System.Reflection;

namespace ReflectionTest
{
    public class Logger
    {
        private readonly string loggerName;
        protected Logger(string loggerName) { this.loggerName = loggerName; }
        public void Debug(string message) { Console.WriteLine(string.Format("{0} - {1}", loggerName, message)); }
    }

    public class Logger<T> : Logger
    {
        public Logger() : base(typeof(T).FullName) { }
    }

    public static class Log
    {
        public static void Debug(string message)
        {
            // Determine the calling function, and create a Logger<T> for it.
            System.Diagnostics.StackFrame frame = new System.Diagnostics.StackFrame(1);
            MethodBase method = frame.GetMethod();

            /// When method is from a generic class, 
            /// the method.ReflectedType definintion is open: Type.ContainsGenericParameters is true
            /// How do I get the generic parameters of method.ReflectedType so 
            /// Activator.CreateInstance() will not throw?
            Type logType = typeof(Logger<>);
            Type constructed = logType.MakeGenericType(new Type[] { method.ReflectedType });

            Logger logger = (Logger)Activator.CreateInstance(constructed);

            logger.Debug(message);
        }
    }

    public class MyBase<T>
    {
        public void Run()
        {
            Log.Debug("Run Generic");   // throws on Activator.CreateInstance()
        }
    }

    class Program
    {
        static void Works()
        {
            Log.Debug("Run NonGeneric");    // works
        }

        static void DoesNotWork()
        {
            MyBase<int> b = new MyBase<int>();
            b.Run();
        }

        static void Main(string[] args)
        {
            Works();
            DoesNotWork();
        }
    }
}

Upvotes: 3

Views: 1162

Answers (1)

Eric Lippert
Eric Lippert

Reputation: 660189

The stack frame is not reliable and is for debugging purposes only. You cannot assume that anything useful is there. That's why it is in the "Diagnostics" namespace.

More generally though, your question demonstrates a fundamental misunderstanding about what the stack frame tells you. You said

The goal is to create a generic instance based on the type that called my method.

The stack frame does not actually tell you who called your method. The stack frame tells you where control is going to return to. The stack frame is the reification of continuation. The fact that who called the method and where control will return to are almost always the same thing is the source of your confusion, but I assure you that they need not be the same.

In particular, the coming "async/await" feature currently in preview release demonstrates the truth of this. Code that resumes from an await has no clue on the stack frame about who called it originally; that information is lost forever. Since the code that is going to run next is logically decoupled from the code that called the method originally, the stack frame does not contain that information.

We need not get as exotic as that though. For example, suppose a method M contains a call to a method N, and N calls a method O. If the jitter chooses to inline N inside M then the stack frame observed from O will not contain N. The stack frame tells you where control resumes when the current method returns. Control will resume inside M when O returns, not N, so the stack frame does not include any information about N.

Upvotes: 19

Related Questions