Expanding on an property initializer

Adding additional values to a Dictionary property initializer

Home DailyDrop

Daily Knowledge Drop

A class property initializer can be expanded on in the class declaration (assuming the property type supports it, such as Dictionary) to a have additional values added.

This technique (explained further below) does have fairly limited practical application and is quite niche - but it does have its place, and practical or not, is very interesting.


Use case

In our use case, when an exception occurs, we want to capture some generic information about the PC on which the exception occurred (machine name, operating system), as well as the actual exception.

If we have an ExceptionInformation class to capture all this information:

  • Static information such as the machine name can be automatically initialized on instantiation of an ExceptionInformation instance
  • Dynamic information such as the exception must be manually supplied to our capture class

Constructor

One way of manually supplying the exception information is pass it to the ExceptionInformation instance in the constructor:

public class ExceptionInformation
{
    // string representation of the exception
    private readonly string _exceptionString;

    // set the exception in the constructor
    public ExceptionInformation(string exceptionString)
    {
        _exceptionString = exceptionString;
    }

    // A dictionary to contain all the relevent information about the exception
    // initialize it with the information on class initialization
    public Dictionary<string, object> Configuration { get; } = 
        new Dictionary<string, object>
        {
            ["MachineName"] = Environment.MachineName,
            ["OsVersion"] = Environment.OSVersion
        };

    // override method to be able to output a representation of the class
    public override string ToString()
    {
        return $"{_exceptionString}{Environment.NewLine}" +
            $"{String.Join(Environment.NewLine, Configuration.Select(d => $"{d.Key}: { d.Value}"))}";
    }
}

The usage of the class would be as follows (with an exception being forced to occur):

try
{
    // force a divide by zero exception
    var intValue = 100;
    _ = intValue / 0;
}
catch(Exception ex)
{
    // capture the exception
    var ei = new ExceptionInformation(ex.ToString());
    Console.WriteLine(ei.ToString());
}

And the output:

System.DivideByZeroException: Attempted to divide by zero.
   at Program.<Main>$(String[] args) in 
    C:\Development\Projects\InitializerExpansion\Program.cs:line 5
MachineName: T800
OsVersion: Microsoft Windows NT 10.0.22000.0

There are other options to achieve the same result - instead of a setting the private _exceptionString variable, the exception string value could have been added directly to the dictionary in the constructor:

public ExceptionInformation(string exceptionString)
{
    this.Configuration.Add("ExceptionString", exceptionString);
}

Both of the above approaches are valid and will achieve the desired result, however another interesting approach is to expand on the property initializer.


Initializer expansion

The initializer can be expanded to include adding custom information to the ExceptionInformation instance. Similar to how the exceptionString was added to the dictionary in the constructor in the above example, except this method is more dynamic and allows for any values to be added.

If we remove all references to exceptionString from the ExceptionInformation class, including from the constructor:

public class ExceptionInformation
{
    // No constructor which takes the exception
   
    // A dictionary to contain all the relevent information about the exception
    // initialize it with the information on class initialization
    public Dictionary<string, object> Configuration { get; } = 
        new Dictionary<string, object>
        {
            ["MachineName"] = Environment.MachineName,
            ["OsVersion"] = Environment.OSVersion
        };

    // override method to be able to output a representation of the class
    // Now ONLY outputs the dictionary
    public override string ToString()
    {
        return String.Join(Environment.NewLine, Configuration.Select(d => $"{d.Key}: {d.Value}"));
    }
}

This usage of the class is now as follows:

try
{
    // force a divide by zero exception
    var intValue = 100;
    _ = intValue / 0;
}
catch(Exception ex)
{
    // capture the exception by expanding the Configuration initialization
    var ei = new ExceptionInformation
    {
        Configuration =
        {
            ["ExceptionString"] = ex.ToString()
            // any other data can be added here
        }
    };
    Console.WriteLine(ei.ToString());
}

Here the Configuration values specified when an instance of ExceptionInformation is initialized, are added to the values initialized internally in the class.

The output of the above is:

MachineName: T800
OsVersion: Microsoft Windows NT 10.0.22000.0
ExceptionString: System.DivideByZeroException: Attempted to divide by zero.
   at Program.<Main>$(String[] args) in 
    C:\Development\Projects\InitializerExpansion\Program.cs:line 5

One advantage of the initializer expansion method, is that all data is now contained in a single place (the dictionary) and more values can be added dynamically.
However, on the negative side, its not apparently obvious to the developer using the ExceptionInformation class, that additional items can be added to the dictionary in this manner.


Notes

As mentioned, this has a fairly niche use case - and even though there are other methods to do the achieve the same outcome, I find this method especially appealing. Even if I never have a practical need for it in a real application - I still find it an interesting usage of the language features.


References

8 Bit Ventilator tweet


Daily Drop 114: 11-07-2022

At the start of 2022 I set myself the goal of learning one new coding related piece of knowledge a day.
It could be anything - some.NET / C# functionality I wasn't aware of, a design practice, a cool new coding technique, or just something I find interesting. It could be something I knew at one point but had forgotten, or something completely new, which I may or may never actually use.

The Daily Drop is a record of these pieces of knowledge - writing about and summarizing them helps re-enforce the information for myself, as well as potentially helps others learn something new as well.
c# .net dictionary initializer