Is there a way to "implement" an interface within a different interface?

Viewed 87

I have an interface IStat which inherits IEquatable and IComparable

various stats will primarily be referred to as IStat as opposed to their actual type, and the implementation of IEquatable and IComparable will be identical regardless of the actual type.

IStat.cs

public interface IStat : IEquatable<IStat>, IComparable<IStat>{
    string GetKey();
    string GetValue();
    public new bool Equals(IStat stat) { // I'm aware I shouldn't use new
        return stat is not null && GetKey() == stat.GetKey();
    }
    public new int CompareTo(IStat stat) {
        return stat is null?1:GetKey.CompareTo(stat.GetKey());
    }
}

public interface IStat<TKey, TValue> : IStat {
    TKey Key{get;}
    TValue Value{get;}
}

ExampleStat.cs

// still requires implementation of Equals and CompareTo, and ExampleStat must be a struct
public struct ExampleStat : IStat<string, double> {
    private string m_Key;
    private double m_Value;
    public string Key=>m_Key;
    public double Value=>m_Value;
    public ExampleStat(string key, double value) {
        m_Key = key;
        m_Value = value;
    }
 }

is there a way to properly implement the IEquatable and IComparable methods inside of IStat? And if there isn't is there a way around this without having to write identical methods in every implementation?

EDIT

I added the implementations for Equals, CompareTo and the Constructor. I'm aware that using classes is the best way to handle my problem, but due to software constraints, for this I need to use a struct, which is where the issue of default behavior in my inheriting classes comes in.

2 Answers

As @pm100 points out you can do this in C# >= 8.0, but in case you are using an older API and are forced to use a language version that does not have this feature yet, you need to use a base class, the virtual and override keywords and then implement the default method definitions in the base class as @AliK suggested.

List<IStat> stats = new();
stats.Add(new StatBase());
stats.Add(new StatDerived());
foreach(var stat in stats)
{
    Console.WriteLine($"{stat.GetKey()}: {stat.CompareTo(stat)}");
}

public interface IStat : IComparable<IStat>
{
    public string GetKey();
}

public class StatBase : IStat
{
    public virtual string GetKey()
    { // default definition
        return "Base";
    }

    public virtual int CompareTo(IStat? stat)
    {
        return 0;
    }
}

public class StatDerived : StatBase
{
    public override string GetKey()
    { // derived definition
        return "Derived";
    }

    public override int CompareTo(IStat? stat)
    {
        return 1;
    }
}

Output:

Base: 0
Derived: 1

If you really truly absolutely have to use a struct, you can make it a composition of your classes:

AStruct aStruct = new AStruct();
aStruct.PrintStuff();

public struct AStruct
{
    private List<IStat> stats = new();
    public AStruct()
    {
        stats.Add(new StatBase());
        stats.Add(new StatDerived());
    }
    public void PrintStuff()
    {
        foreach (var stat in stats)
        {
            Console.WriteLine($"{stat.GetKey()}: {stat.CompareTo(stat)}");
        }
    }
}

You cannot use new to shadow methods on the interface without creating the situation where you could have two implicit implementations of methods. You should almost always avoid new where possible.

The best alternative, when you can't use base classes, is to do this:

public interface IStat : IEquatable<IStat>, IComparable<IStat>
{
    public static class Default
    {
        public static bool Equals(IStat lhs, IStat rhs) => lhs is not null && lhs.GetKey() == rhs.GetKey();
        public static int CompareTo(IStat lhs, IStat rhs) => lhs is null ? 1 : lhs.GetKey().CompareTo(rhs.GetKey());
    }

    string GetKey();
    string GetValue();
}

Now your implementation can be written with a simple bit of repeated boiler-plate code.

public struct ExampleStat : IStat<string, double>
{
    private string m_Key;
    private double m_Value;
    public string Key => m_Key;
    public double Value => m_Value;

    public ExampleStat(string key, double value)
    {
        m_Key = key;
        m_Value = value;
    }

    string IStat.GetKey() => this.Key;
    string IStat.GetValue() => this.Value.ToString();

    public bool Equals(IStat other) => IStat.Default.Equals(this, other);
    public int CompareTo(IStat other) => IStat.Default.CompareTo(this, other);

}

This code is still in question, in my mind, as new ExampleStat("Hello", 1.0).Equals(new ExampleStat("Hello", 2.0)) == true. That shouldn't be true.

Related