Search code examples
c#.netclr

Why is it possible to access a constructor parameter after it becomes out of scope?


Why is it okay to call the SomeMethod() method in my example below?

I would think that the constructor parameter loggerFactory would no longer be available when the Logger property tries to access it.

I'm using a function when I set the lazyLogger field, but I thought it would throw an exception of sorts when I call the Logger property.

But everything works just fine. This might just be my misunderstanding of how the CLR/C# works.

I would appreciate an explanation for why it works this way.

public class TestClass
{
  private readonly Lazy<ILogger> lazyLogger;

  private ILogger Logger => this.lazyLogger.Value;

  public TestClass(ILoggerFactory loggerFactory)
  {
    this.lazyLogger = new Lazy<ILogger>(() = > loggerFactory.GetLogger("TestLogger"));
  }

  public void SomeMethod()
  {
    this.Logger.Info("Test Log Message"); //Why is it okay to call this method?  The constructor parameter shouldn't be available anymore, right?
  } 
}

public interface ILoggerFactory
{
  ILogger GetLogger(string name);
}

public interface ILogger
{
  void Info(string message);
}

public class TestLoggerFactory : ILoggerFactory
{
  public ILogger GetLogger(string name)
  {
      return new TestLogger(name);
  }
}

public class TestLogger : ILogger
{
  public void Info(string message)
  {
    Console.WriteLine(message);
  }
}

Solution

  • Since you're accessing this parameter inside the lambda

    () = > loggerFactory.GetLogger("TestLogger");
    

    the compiler creates some extra code to capture that variable. It looks something like this:

    public class TestClass
    {
        /* your fields here */
    
        // created by compiler
        private ILoggerFactory anonymousField; 
        private ILogger AnonymousMethod()
        {
             return anonymousField.GetLogger("TestLogger");
        }
    
        public TestClass(ILoggerFactory loggerFactory)
        {
            // save field
            this.anonymousField = loggerFactory;
            // use instance method instead with captures anonymousField
            this.lazyLogger = new Lazy<ILogger>(AnonymousMethod); 
        }
    

    As mentioned in the comments, actually a whole new class gets generated that declares that anonymous method and takes all necessary variables as fields. But that's the basic idea.