Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Using Where to specify different generics

Tags:

c#

.net

generics

I'm writing a bijective dictionary class, but I want to ensure the two generic types are not the same type for two reasons.

Firstly, I would like it to implement the IDictionary interface in both directions, but

public class BijectiveDictionary<TKey, TValue>
    : IDictionary<TKey, TValue>, IDictionary<TValue, TKey>

gives me " 'BijectiveDictionary<TKey,TValue>' cannot implement both 'IDictionary<TKey,TValue>' and 'IDictionary<TValue,TKey>' because they may unify for some type parameter substitutions " (which is understandable, but undesirable.)

Secondly, I would like to write an optimized solution if both types are the same.

public class BijectiveDictionary<TKey, TValue>
    : IDictionary<TKey, TValue> where TValue : TKey
{
    // Optimized solution
}

public class BijectiveDictionary<TKey, TValue>
    : IDictionary<TKey, TValue>, IDictionary<TValue, TKey> where TValue : !TKey
{
    // Standard solution
}

Is this possible?

If not, I can consider not implementing IDictionary, but I couldn't guarantee TValue this[TKey key] and TKey this[TValue key] would be different, which would be unfortunate.


It looks like the problem here is that when the two types are the same, the special cases arise.

My original intent was to create a dictionary which maps exactly one key to exactly one value, and visa versa, such that for every KeyValuePair<TKey, TValue>(X, Y), a KeyValuePair<TValue, TKey>(Y, X) exists as well.

When TKey = TValue, then this can be simplified down to a single dictionary:

public T this[T key]
{
    get { return this[key]; }
    set
    {
        base.Add(key, value);
        base.Add(value, key);
    }
}

In this case, you cannot Add(2,3); Add(3,4) because Add(2,3) maps 3 to 2 as well, and [3] would return 2.

However, Jaroslav Jandek's solution proposed using a second dictionary to do this for cases when TKey != TValue. And although this works wonderfully for those cases, (and what I decided to implement, in the end) it doesn't quite follow my original intent when TKey = TValue, by allowing Add(2,3); Add(3,4) to map a single key 3 to two values (2 in one direction, and 4 in the other,) though I believe strictly speaking is still a valid bijective function.

like image 758
dlras2 Avatar asked Jul 03 '10 19:07

dlras2


People also ask

Can a generic class have multiple generic parameters?

A Generic class can have muliple type parameters.

What are generics and where do we apply it?

Generics means parameterized types. The idea is to allow type (Integer, String, … etc., and user-defined types) to be a parameter to methods, classes, and interfaces. Using Generics, it is possible to create classes that work with different data types.


1 Answers

How about this (different approach):

public class BijectiveDictionary<TKey, TValue> : Dictionary<TKey, TValue>
{
    public BijectiveDictionary<TValue, TKey> Reversed { get; protected set; }

    public BijectiveDictionary()
    {
        this.Reversed = new BijectiveDictionary<TValue,TKey>(true);
        this.Reversed.Reversed = this;
    }

    protected BijectiveDictionary(bool reversedWillBeSetFromTheCallingBiji) { }

    protected void AddRaw(TKey key, TValue value)
    {
        base.Add(key, value);
    }

    // Just for demonstration - you should implement the IDictionary interface instead.
    public new void Add(TKey key, TValue value)
    {
        base.Add(key, value);
        this.Reversed.AddRaw(value, key);
    }

    public static explicit operator BijectiveDictionary<TValue, TKey>(BijectiveDictionary<TKey, TValue> biji)
    {
        return biji.Reversed;
    }
}

and in code:

BijectiveDictionary<int, bool> a = new BijectiveDictionary<int, bool>();

a.Add(5, true);
a.Add(6, false);

Console.WriteLine(a[5]);// => True
Console.WriteLine(((BijectiveDictionary < bool, int>)a)[true]);// => 5
//or
Console.WriteLine(a.Reversed[true]);// => 5
like image 73
Jaroslav Jandek Avatar answered Sep 19 '22 05:09

Jaroslav Jandek