C# Interface revisit old feature and some new features

C# Interface revisit old feature and some new features

Before I jump into some detail into C# Interface feature some old and some new, I would like to share how it is all started. I personally feel that this is so obvious and experience people who has been working in C# have knowledge around this. Unfortunately ratio is quite poor so I decided to write those information over here.

Since C# 1.0 comes, there is no major change in interface at least in terms of how it get implemented.

Basically Interface is one type of contract so when it get implemented to the class then it is class responsibility to provide implementing behavior.

Let's consider following example.

public interface ILogger
{
    void LogInformation(string message);   
}

This is the first version of ILogger interface and if any concreate type of Logger implement this then they must have to provide implementation.

public class ConsoleLogger
    : ILogger
{
    public void LogInformation(string message)
    {
        Console.WriteLine(message);
    }
}

Upto this point everything is good and there is no problem so far. Now consider that above code is being shipped and as per business requirement there are few other class implemented by other developer who is using above code as their base code. Let's consider they decided to write FileLogger.

public class FileLogger
    : ILogger
{
    public void LogInformation(string message)
    {
        System.IO.File.AppendAllText("C:\\temp\\log.txt",message);
    }
}

Now after sometime in base version someone decided to add new method LogException.

public interface ILogger
{
    void LogInformation(string message);   
    void LogException(Exception exception);
}

As you have control over ConsoleLogger , LogException can be implemented over there.

public class ConsoleLogger
    : ILogger
{
    public void LogException(Exception exception)
    {
        Console.WriteLine($"Error {exception.Message}");
    }

    public void LogInformation(string message)
    {
        Console.WriteLine(message);
    }
}

This works fine but as soon as this new version is being used by application that developed FileLogger , their code will break without their fault due to this breaking change. This can be control using interface versioning.

public interface ILogger
{
    void LogInformation(string message);   

}

public interface ILoggerWithException : ILogger
{
    void LogException(Exception exception);
}

public class ConsoleLogger
    : ILoggerWithException
{
    public void LogException(Exception exception)
    {
        Console.WriteLine($"Error {exception.Message}");
    }

    public void LogInformation(string message)
    {
        Console.WriteLine(message);
    }
}

In above code instead of adding new method to ILogger , we create new interface that inherit ILogger and add new functionality over there. Also local implementation implement that new interface and change around that. Also this will not break external application that using ILogger.

Now instead of interface if you are using Abstract class then it is good as it can contain concreate method as well as abstract but it is not good as C# has limitation of inheritance. Also Inheritance relation is not based on one method instead it has to do with related classes.

Feature : C#8.0 Concreate Method In Interface

C# 8.0 has new feature, that is some what confusing as it may seems like violating basic contract definition of interface but it is good considering the help it provide with versioning of interface.

public interface ILogger
{
    void LogInformation(string message);   

    void LogException(Exception exception)
    {
        // Do nothing.
    }   
}

You can see that LogException added as concreate method so which ever class implement this method it can call that method otherwise Interface default implementation get considered so it is good. It is not good considering that now external application will never know that there is new method is interface but it will not break the code.

Feature : C# 10.0 (Still In Preview) support new feature called static abstract member.

For this I am going to take different example. Let's consider that we have one interface and that is being implemented by multiple type. Now if we need instance wise count for each type then it will be helpful.

public interface IInstanceCounter
{
    public static abstract int Counter { get;  }
}

Now let's say this implemented by different class.

public class Bird : IInstanceCounter
{
    public static int Counter { get; private set; }

    public Bird()
    {
        Counter++;
    }

}

public class Animal : IInstanceCounter
{
    public static int Counter { get; private set; }

    public Animal()
    {
        Counter++;
    }
}

We will write one helper method. Here you can see that it is being working on type rather then instance and that is why extension method also not possible.

public static class ExtensionHelper
{
    public static void PrintCounter<T>(T t) where T : IInstanceCounter
    {
        Console.WriteLine(T.Counter);
    }
}

Now if we use it like.

var temp = new Bird();
var temp1 = new Bird();
var temp2 = new Animal();
ExtensionHelper.PrintCounter(temp);
ExtensionHelper.PrintCounter(temp2);

output is

2
1

This is because Counter is static and it shared by instance type so we have two instance of Bird and one instance of Animal.

Note: Above feature is in preview mode in C# 10.0 so you have to add following line into csproj.

<EnablePreviewFeatures>True</EnablePreviewFeatures>

It will look like following

image.png

Hope this helps.