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'

1

3 Answers 3

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

Sign up to request clarification or add additional context in comments.

Comments

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();
}

Comments

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();
}

4 Comments

That can only be used when conString == null and minLevel == -1 are not valid values.
@Patrick Hofman: quite right; if arbitrary minLevel is allowed then int? minLevel = null is a possible way out.
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.
@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))

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.