5

I've seen answers on constructor chaining but they don't apply for my problem.

I have a the following constructor that requires a couple of parameters:

public SerilogHelper(string conString, int minLevel)
    {
        var levelSwitch = new LoggingLevelSwitch();
        levelSwitch.MinimumLevel = (Serilog.Events.LogEventLevel)(Convert.ToInt32(minLevel));

        _logger = new LoggerConfiguration()
            .MinimumLevel.ControlledBy(levelSwitch)
            .WriteTo.MSSqlServer(connectionString: conString, 
                                 tableName: "Logs", 
                                 autoCreateSqlTable: true)
            .CreateLogger();
    }

One particular client of this constructor won't have the values required for the parameters so I'd like to be able to call this simple constructor which would get the required values then call the 1st constructor:

public SerilogHelper()
    {
        string minLevel = SSOSettingsFileManager.SSOSettingsFileReader.ReadString(
          "LCC.Common", "serilog.level");
        string conString = SSOSettingsFileManager.SSOSettingsFileReader.ReadString(
          "LCC.Common", "serilog.connectionstring");

        SerilogHelper(conString, minLevel);
    }

Problem is, I get a red squiggly on the call to the 2nd constructor with the message SerilogHelper is a 'type' but used like a 'variable'

svick
  • 236,525
  • 50
  • 385
  • 514
Rob Bowman
  • 7,632
  • 22
  • 93
  • 200
  • Possible duplicate of [C# constructors overloading](https://stackoverflow.com/questions/5555715/c-sharp-constructors-overloading) – Liam Jun 20 '17 at 10:31

3 Answers3

7

You cannot do that. The best option you have it so move the initialization code to a separate method which you can call from both the constructors. That is allowed.

public SerilogHelper()
{
    string minLevel = SSOSettingsFileManager.SSOSettingsFileReader.ReadString(
      "LCC.Common", "serilog.level");
    string conString = SSOSettingsFileManager.SSOSettingsFileReader.ReadString(
      "LCC.Common", "serilog.connectionstring");

    this.Initialize(conString, minLevel);
}

public SerilogHelper(string conString, int minLevel)
{
    this.Initialize(conString, minLevel);
}

protected void Initialize(string conString, int minLevel)
{
    var levelSwitch = new LoggingLevelSwitch();
    levelSwitch.MinimumLevel = (Serilog.Events.LogEventLevel)(Convert.ToInt32(minLevel));

    _logger = new LoggerConfiguration()
        .MinimumLevel.ControlledBy(levelSwitch)
        .WriteTo.MSSqlServer(connectionString: conString, 
                             tableName: "Logs", 
                             autoCreateSqlTable: true)
        .CreateLogger();
}
Patrick Hofman
  • 153,850
  • 22
  • 249
  • 325
7

Why not just simply add these parameters?

// this assumes that SSOSettingsFileManager is static class
// this will automatically call these methods before passing 
// values to another ( non parameterless ) constructor
public SerilogHelper()
    : this ( 
        SSOSettingsFileManager.SSOSettingsFileReader.ReadString(
            "LCC.Common", "serilog.connectionstring"
        ),
        SSOSettingsFileManager.SSOSettingsFileReader.ReadString(
            "LCC.Common", "serilog.level"
        )
    )
{

}

// This will be called from default ( parameterless )
// constructor with values retrieved from methods
// called in previous constructor.
public SerilogHelper(string conString, int minLevel)
{
        var levelSwitch = new LoggingLevelSwitch();
        levelSwitch.MinimumLevel = (Serilog.Events.LogEventLevel)(Convert.ToInt32(minLevel));

        _logger = new LoggerConfiguration()
            .MinimumLevel.ControlledBy(levelSwitch)
            .WriteTo.MSSqlServer(connectionString: conString, 
                                 tableName: "Logs", 
                                 autoCreateSqlTable: true)
            .CreateLogger();
}

Test online

mrogal.ski
  • 5,828
  • 1
  • 21
  • 30
2

I suggest using default values:

public SerilogHelper(string conString = null, int minLevel = -1)
{
    if (minLevel == -1)
      minLevel = SSOSettingsFileManager.SSOSettingsFileReader.ReadString(
      "LCC.Common", "serilog.level");

    if (conString == null)
      conString = SSOSettingsFileManager.SSOSettingsFileReader.ReadString(
      "LCC.Common", "serilog.connectionstring");

    var levelSwitch = new LoggingLevelSwitch();
    levelSwitch.MinimumLevel = (Serilog.Events.LogEventLevel)(Convert.ToInt32(minLevel));

    _logger = new LoggerConfiguration()
        .MinimumLevel.ControlledBy(levelSwitch)
        .WriteTo.MSSqlServer(connectionString: conString, 
                             tableName: "Logs", 
                             autoCreateSqlTable: true)
        .CreateLogger();
}

....

SerilogHelper myInstance = new SerilogHelper();

Edit: I've assumed that minLevel = -1 being invalid can be used a as default value, if it's not the case (any minLevel values are allowed) int? is a possible way out:

public SerilogHelper(string conString = null, int? minLevel = null)
{
    if (!minLevel.HasValue)
      minLevel = SSOSettingsFileManager.SSOSettingsFileReader.ReadString(
      "LCC.Common", "serilog.level");

    if (conString == null)
      conString = SSOSettingsFileManager.SSOSettingsFileReader.ReadString(
      "LCC.Common", "serilog.connectionstring");

    var levelSwitch = new LoggingLevelSwitch();
    levelSwitch.MinimumLevel = (Serilog.Events.LogEventLevel)(Convert.ToInt32(minLevel));

    _logger = new LoggerConfiguration()
        .MinimumLevel.ControlledBy(levelSwitch)
        .WriteTo.MSSqlServer(connectionString: conString, 
                             tableName: "Logs", 
                             autoCreateSqlTable: true)
        .CreateLogger();
}
Dmitry Bychenko
  • 180,369
  • 20
  • 160
  • 215
  • 2
    That can only be used when `conString == null` and `minLevel == -1` are not valid values. – Patrick Hofman Jun 20 '17 at 10:29
  • 1
    @Patrick Hofman: quite right; if *arbitrary* `minLevel` is allowed then `int? minLevel = null` is a possible way out. – Dmitry Bychenko Jun 20 '17 at 10:30
  • Optional Parameters have quite a few gotchas. One is "ambiguity" ... given "public Test() { Console.WriteLine("Default"); }" and "public Test(int i = 0) { Console.WriteLine("Optional"); }", what does "new Test()" call? Another is the default value gets "Baked in" to the calling code. So, if you change a default value, you have to recompile all calling code. e.g. If ProjectA has a constructor with optional parameters, and ProjectB calls that constructor, if you recompile ProjectA, but NOT ProjectB then ProjectB will still call the constructor with the old default value. –  Jun 20 '17 at 10:53
  • @AndyJ: You are quite right, optional values should be used with care; but here we are not going to be trapped with either antipattern 1 (all we want is possibility not to provide connection string and level) or antipettern 2 (we have no magic values which we'll want to change in future, but `null` and `-1` (`null` if any level is allowed)) – Dmitry Bychenko Jun 20 '17 at 10:59