-1

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);
  }
}
Issa Fram
  • 2,556
  • 7
  • 33
  • 63

1 Answers1

2

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.

René Vogt
  • 43,056
  • 14
  • 77
  • 99
  • The **closing over** aspect is new to me. Might be my fault for not having this knowledge. – Issa Fram Dec 20 '17 at 17:22
  • 1
    Actually the compiler creates a new delegate class for the lambda containing a field for the captured variable. – Olivier Jacot-Descombes Dec 20 '17 at 17:22
  • 1
    @IssaFram Sorry, my english sometimes lacks the correct wording for this compiler magic. Though it's enough for a normal conversation, I never understand why it's called/what the exact meaning of "closure"/"capture" etc.. is in this context. – René Vogt Dec 20 '17 at 17:24
  • @OlivierJacot-Descombes not sure about this, a while ago I checked the decompiled IL and I think I remember that as long as possible it keeps the values in the same class. Not sure if this is an implementation detail. – René Vogt Dec 20 '17 at 17:25
  • 2
    See: https://stackoverflow.com/a/742474/880990 and for more in-depth information: [The Beauty of Closures C#](http://csharpindepth.com/Articles/Chapter5/Closures.aspx). Both by Jon Skeet. – Olivier Jacot-Descombes Dec 20 '17 at 17:27
  • Closure is a [term of art](https://en.wikipedia.org/wiki/Closure_(computer_programming)) from computer sceince. – Damien_The_Unbeliever Dec 20 '17 at 17:30
  • @OlivierJacot-Descombes re-checked, you are right. Unfortunatly I'm running out of time now, will update the answer tomorrow if noone finds a good dup til then. And I guess I described the basic idea. – René Vogt Dec 20 '17 at 17:31