Search code examples
c#dictionarygenericssubclasssystem.numerics

How do you subclass a C# generic container that implements System.Numerics interfaces which require a TSelf type argument?


I made a dictionary-like container that supports key-wise arithmetic operations. I want it to "act" like a value type in the sense that it can be added, subtracted, and multiplied by other instances of the same container.

I'm making a set of stats containers for a game. The aforementioned arithmetic dictionary is to be the base class for all of the different stats containers (i.e. stats, stat multipliers, equipment stat multipliers, level up bonuses, etc.) using a common enum for their keys and a variety of data types for their values (i.e. integers, doubles, and number-like custom classes).

public class ArithmeticDictionary<TKey,TValue> : 
    IEnumerable<KeyValuePair<TKey,TValue>>,
    System.Numerics.IAdditionOperators<
        ArithmeticDictionary<TKey,TValue>,
        ArithmeticDictionary<TKey,TValue>,
        ArithmeticDictionary<TKey,TValue>>,
    System.Numerics.ISubtractionOperators<
        ArithmeticDictionary<TKey,TValue>,
        ArithmeticDictionary<TKey,TValue>,
        ArithmeticDictionary<TKey,TValue>>,
    System.Numerics.IMultiplyOperators<
        ArithmeticDictionary<TKey,TValue>,
        ArithmeticDictionary<TKey,TValue>,
        ArithmeticDictionary<TKey,TValue>>,
    System.Numerics.IComparisonOperators<
        ArithmeticDictionary<TKey,TValue>,
        ArithmeticDictionary<TKey,TValue>,
        bool>,
    System.Numerics.IMinMaxValue<
        ArithmeticDictionary<TKey,TValue>>,
    System.Numerics.IAdditiveIdentity<
        ArithmeticDictionary<TKey,TValue>,
        ArithmeticDictionary<TKey,TValue>>
where TKey : notnull
where TValue : 
    System.Numerics.IAdditionOperators<TValue,TValue,TValue>,
    System.Numerics.ISubtractionOperators<TValue,TValue,TValue>,
    System.Numerics.IMultiplyOperators<TValue,TValue,TValue>,
    System.Numerics.IComparisonOperators<TValue,TValue,bool>,
    System.Numerics.IMinMaxValue<TValue>,
    System.Numerics.IAdditiveIdentity<TValue,TValue>
{}

The functionality is all there, but every time I subclass the generic base class, I get a whole bunch of the same warning.

Here, Stat is an enum.

public class Stats : 
    ArithmeticDictionary<Stat, uint>,
    IEnumerable<KeyValuePair<Stat,uint>>,
    System.Numerics.IAdditionOperators<Stats,Stats,Stats>,
    System.Numerics.ISubtractionOperators<Stats,Stats,Stats>,
    System.Numerics.IMultiplyOperators<Stats,Stats,Stats>,
    System.Numerics.IComparisonOperators<Stats,Stats,bool>,
    System.Numerics.IMinMaxValue<Stats>,
    System.Numerics.IAdditiveIdentity<Stats,Stats>
{}

warning CA2260: The 'ArithmeticDictionary<TKey, TValue>' requires the 'TKey' type parameter to be filled with the derived type 'Stats'

From the warning, it seems to be due to ArithmeticDictionary implementing a number of interfaces that have a TSelf type parameter, but child classes are no longer of the same type.

My code still works, but 5 subclasses with 6 warnings each is a bit much.


Solution

  • Add a TSelf type parameter to IArithmeticDictionary. Inline with the answer to this question. Adding operator support to interfaces (Preview Feature in .NET 6)

    I put some example code below that compiles, but ommits simmilar cases for brevity.

    public interface IArithmeticDictionary<TSelf, TKey, TValue> :
        IEnumerable<KeyValuePair<TKey, TValue>>,
        System.Numerics.IAdditionOperators<TSelf,
            TSelf,
            TSelf>
    where TSelf : IArithmeticDictionary<TSelf, TKey, TValue>
    where TKey : notnull
    where TValue :
        System.Numerics.IAdditionOperators<TValue, TValue, TValue>
    {
       // Optional
        public static TSelf operator +(IArithmeticDictionary<TSelf, TKey, TValue> left, TSelf right)
        {
            throw new NotImplementedException();
        }
        // Optional
        static TSelf operator checked +(IArithmeticDictionary<TSelf, TKey, TValue> left, TSelf right)
        {
            throw new NotImplementedException();
        }
    }
    
    public class Stats :
        IArithmeticDictionary<Stats, Stat, uint>
    {
        public IEnumerator<KeyValuePair<Stat, uint>> GetEnumerator()
        {
            throw new NotImplementedException();
        }
    
        IEnumerator IEnumerable.GetEnumerator()
        {
            throw new NotImplementedException();
        }
    
        public static Stats operator +(Stats left, Stats right)
        {
            throw new NotImplementedException();
        }
    }
    
    public enum Stat
    {
        A,
        B
    }