Search code examples
c#loggingstatic-methods

Setting Static Field in Non-static Constructor | Why does this work? | C#


In this application, we are using JUST.net with custom functions. Custom functions in JUST.net must be static methods. I created a class that defines all static functions; these functions are registered to the JUST.net context via reflection. I need to include logging in these static functions in case one of the mappings fails.

To accomplish this, we have code like this:

public class Transformations
{
  private static ILogger<Transformation> _logger;

  public Transformations(ILogger<Transformations> logger)
  {
     _logger = logger;
  }

  public static string MapAddress(string? streetNumber, string? streetName)
  {
    if(string.IsNullOrEmpty(streetNumber)
    { 
      _logger.LogError("The property was not provided.");
    }
    // etc...
  }
  // etc...
}

When I call it from my tests, the logger works if I instantiate the class and reference the functions statically. If I do not instantiate the class and reference the methods statically, the logger does not work.

I.e.:

  public class AddressUnitTest
  {
    private readonly Transformations _transformations;

    public AddressUnitTest()
    {
      var serilog = new LoggerConfiguration().WriteTo.Console().CreateLogger();
      var loggerFactory = new LoggerFactory().AddSerilog(serilog);
      var logger = loggerFactory.CreateLogger<Transformations>();
      _transformations = new Transformations(logger);
    }

    [Fact]
    public void Address(string? streetNumber, string? streetName)
    {
      var actualValue = Transformations.MapAddress(streetNumber, streetName);
      // etc...
    }
  }

Given you cannot use _transformations.MapAddress() and must reference the methods statically, i.e.: Transformation.MapAddress(), why does the logger work when I instantiate a seemingly unrelated version of it?

That is, why does setting _transformations = new Transformations(logger); give Transformations.MapAddress() access to the logger?

It seems to work; I just don't understand why. Is there a better way to accomplish this?


Solution

  • The reason for this is that the only time that the _logger field is initialised to anything is in the class constructor.

    It would be better to write a static method to initialise the logger, and make your entire class static (so that all its members must be static) so that this confusion doesn't arise:

    public static class Transformations
    {
      private static ILogger<Transformation> _logger;
    
      public static void InitialiseLogger(ILogger<Transformations> logger)
      {
         _logger = logger;
      }
    
      public static string MapAddress(string? streetNumber, string? streetName)
      {
        if(string.IsNullOrEmpty(streetNumber)
        { 
          _logger.LogError("The property was not provided.");
        }
        // etc...
      }
      // etc...
    }
    

    In your unit tests, it should be possible to have a startup method which calls Transformations.InitialiseLogger() just once for all the unit tests, so you don't have to repeat it in each test method. (Or some other way - I'm not familiar with how xUnit does this.)