C# .NET Generics – How to Add Numbers of a Generic Type

.netc++generics

I have a collection of objects that have a unique key. That key could be numbers or a string, it's generic and most of the class doesn't care because it's stored in a Dictionary<TKey, TItem>.

Now the class should provide a method to return a new unique key for an item to be added. That's where I can't find a solution. I've tried to read up on the new generic math feature of C# but that doesn't make any sense to me.

I'm looking for something like the GetUniqueKey method below:

// Restrict TKey to numbers or strings: https://stackoverflow.com/a/30660880
class MyCollection<TKey, TObject>
    where TObject : class
    where TKey : notnull, IComparable, IConvertible, IEquatable<TKey>
{
    private Dictionary<TKey, TObject> items;

    public TKey GetUniqueKey()
    {
        if (TKey is INumber)
            return items.Keys.Max() + 1;
        if (TKey is string)
            return Guid.NewGuid().ToString();
        throw new NotSupportedException("Key type not supported.");
    }
}

Can this be done at all?

Best Answer

It seems to me that the trick is to use a Create factory, which passes in an appropriate GetUniqueKey method as a delegate

public MyCollection<TKey, TObject> CreateNumberCollection<TKey, TObject>()
        where TKey : INumberBase<TKey> =>
    new MyCollection<TKey, TObject>(lastKey => lastKey++);

public MyCollection<string, TObject> CreateStringCollection<TObject>() =>
    new MyCollection<string, TObject>(lastKey => Guid.NewGuid().ToString());

public MyCollection<Guid, TObject> CreateGuidCollection<TObject>() =>
    new MyCollection<Guid, TObject>(lastKey => Guid.NewGuid());


public class MyCollection<TKey, TObject>
    where TObject : class
    where TKey : notnull, IComparable, IConvertible, IEquatable<TKey>
{
    private Dictionary<TKey, TObject> _items = new();

    private TKey _lastKey;

    private Func<TKey, TKey> _uniqueKeyFactory;

    public MyCollection(Func<TKey, TKey> uniqueKeyFactory)
    {
        _uniqueKeyFactory = uniqueKeyFactory;
    }

    public TKey GetUniqueKey() =>
        _lastKey = _uniqueKeyFactory(_lastKey);
}

If you want to do this entirely from a single function then you could store those delegates in a dictionary.

private static Dictionary<Type, Delegate> _factories = new(){
    { typeof(int), new Func<int, int>(lastKey => lastKey++) },
    { typeof(byte), new Func<int, byte>(lastKey => lastKey++) },
    { typeof(double), new Func<int, double>(lastKey => lastKey++) },
    { typeof(Guid), new Func<int, Guid>(lastKey => Guid.NewGuid().ToString()) },
    { typeof(string), new Func<int, string>(lastKey => Guid.NewGuid()) },
};

And then

    public MyCollection()
    {
        if (!_factories.TryGetValue(typeof(TKey), out var uniqueKeyFactory))
            throw new Exception("Invaid type");

        _uniqueKeyFactory = (Func<TKey, TKey>)uniqueKeyFactory;
    }