Home > Enterprise >  C#8 interface defaults: How to implement default properties in a nice and usable way
C#8 interface defaults: How to implement default properties in a nice and usable way

Time:04-16

I really loved the idea of default implementations on interfaces in C#8. But after trying it the disappointment was big...

So here's a simple example which I've found a part of the answer to in C#8 interfaces with properties/methods defined in them - apparently not working already why this is:

public interface IHasFirstNames
{
    string? FirstName => FirstNames.FirstOrDefault();

    List<string> FirstNames { get; }
}

public class Monster : IHasFirstNames
{
    public List<string> FirstNames { get; } = new();

    public Monster(params string[] firstNames)
    {
        FirstNames.AddRange(firstNames);
    }
}

public static class Program
{
    public static void Main()
    {
        var sally = new Monster("James", "Patrick");

        var error = sally.FirstName; // Cannot resolve symbol FirstName
        var works = ((IHasFirstNames)sally).FirstName;
    }
}

But whats the point of having a default implementation of a property in an interface if you always have to cast it ugly?!

So according to the casting-solution of above I've tried this:

public interface IHasFirstNames
{
    string? FirstName => FirstNames.FirstOrDefault();

    List<string> FirstNames { get; }
}

public class Monster : IHasFirstNames
{
    // Not ideal to declare the property here
    // But at least the implementation is still in the interface
    public string? FirstName => ((IHasFirstNames)this).FirstName;

    public List<string> FirstNames { get; } = new();

    public Monster(params string[] firstNames)
    {
        FirstNames.AddRange(firstNames);
    }
}

public static class Program
{
    public static void Main()
    {
        var sally = new Monster("James", "Patrick");

        var error = sally.FirstName; // StackOverflow!
    }
}

But against expectations this leads to a stack overflow as the cast to IHasFirstName does not really call the default implementation of the interface. Even when I implement a full getter with a dedicated variable of type IHasFirstName it leads to a stack overflow.

The only ugly solution I've come up with is this with a dedicated getter method:

public interface IHasFirstNames
{
    // Default implementation of a property is no use to me!
    string? FirstName { get; }
    // So I have to implement a getter method as default
    public string? FirstNameGetter() => FirstNames.FirstOrDefault();

    List<string> FirstNames { get; }
}

public class Monster : IHasFirstNames
{
    public string? FirstName => ((IHasFirstNames)this).FirstNameGetter();

    public List<string> FirstNames { get; } = new();

    public Monster(params string[] firstNames)
    {
        FirstNames.AddRange(firstNames);
    }
}

public static class Program
{
    public static void Main()
    {
        var sally = new Monster("James", "Patrick");

        var works= sally.FirstName;
    }
}

It doesn't have to be a method. Apparently it's also OK if its a property with a different name. Once the property in the interface and in the class should have the same name it gets ugly.

Is there really no nicer solution for this?

thanks

CodePudding user response:

As others have pointed out, this isn't really the intended usage for default interface methods. As the documentation states:

The most common scenario is to safely add members to an interface already released and used by innumerable clients.

For the way you want to use it, there's another mechanism available: Static extension methods. As you're probably already aware, this mechanism is used extensively in the CLR for things such as IEnumerable<T> extension methods.

For your case, you could include the following static extension class alongside the interface:

public static class HasFirstNamesExt
{
    public static string? FirstName(this IHasFirstNames self)
    {
        return self.FirstNames.FirstOrDefault();
    }
}

If you use that instead of the declaration in the interface itself, your code will work as expected.

However, this does of course suffer from the major drawback that implementing classes cannot change the implementation! If you want to support that, you need to use abstract base classes instead:

public static class Program
{
    public static void Main()
    {
        var sally = new Monster("James", "Patrick");
        Console.WriteLine(sally.FirstName); // "James"
    }
}

public interface IHasFirstNames
{
    List<string> FirstNames { get; }
    string? FirstName { get; }
}

public abstract class HasFirstNamesBase : IHasFirstNames
{
    public abstract List<string> FirstNames { get; }
    public virtual string? FirstName => FirstNames.FirstOrDefault();
}

public class Monster : HasFirstNamesBase
{
    public sealed override List<string> FirstNames { get; } = new();

    public Monster(params string[] firstNames)
    {
        FirstNames.AddRange(firstNames);
    }
}

Note that in this usage pattern, your implementing classes always derive from HasFirstNamesBase to make use of the default implementation.

Derived classes can override the implementation:

public class DifferentFirstNameImplementation: Monster
{
    public DifferentFirstNameImplementation(params string[] firstNames)
    :base (firstNames)
    {
    }

    public override string? FirstName => FirstNames.LastOrDefault();
}

Then:

public static void Main()
{
    var sally = new DifferentFirstNameImplementation("James", "Patrick");
    Console.WriteLine(sally.FirstName); // "Patrick"
}
  • Related