Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Constraining class generic type to a Tuple

  1. I'd like do create a class with one generic TKey, where TKey is one of System.Tuple types that can be created.

    public class Class1<TKey> where TKey : System.Tuple
    { 
           /// Class Stuff Goes Here where TKey is one of the 8 tuple 
               types found in the link in (1)
    
    }
    

I am not so sure how to implement this. The goal is to prevent myself from implementing a class for each tuple class.

like image 388
Maelstrom Yamato Avatar asked Mar 22 '13 19:03

Maelstrom Yamato


4 Answers

You can't as others have stated, but you can almost do it.

So all the Tuple<...> classes have a signature like this:

public class Tuple<T1, ...> : 
    IStructuralEquatable, 
    IStructuralComparable, 
    IComparable, 
    ITuple

All of those interfaces save ITuple are public (ITuple is an internal interface), so you can try crafting something like so:

public interface ITupleKey<TKey>
    where TKey : IStructuralEquatable, IStructuralComparable, IComparable
{
}

"But wait!", you say, "How can I be sure nothing else is implementing those interfaces?"

Well, you can't. But like I said, this is just an almost way - luckily, IStructuralEquatable and IStructuralComparable are only (at the framework level, naturally) used in the following types:

System.Array
System.Tuple<T1>
System.Tuple<T1,T2>
System.Tuple<T1,T2,T3>
System.Tuple<T1,T2,T3,T4>
System.Tuple<T1,T2,T3,T4,T5>
System.Tuple<T1,T2,T3,T4,T5,T6>
System.Tuple<T1,T2,T3,T4,T5,T6,T7>
System.Tuple<T1,T2,T3,T4,T5,T6,T7,TRest>

So it's pretty close. Combine this with a runtime check that TKey actually is some variant of Tuple, and you might have what you need.

EDIT:

Some basic usage:

public class Class1<TKey> 
    where TKey : IStructuralEquatable, IStructuralComparable, IComparable
{ 
}

// will compile
var classTup1 = new Class1<Tuple<int>>();
var classTup2 = new Class1<Tuple<int,int>>();
var classTup3 = new Class1<Tuple<int,int,int>>();
var classTup4 = new Class1<Tuple<int,int,int,int>>();
var classTup5 = new Class1<Tuple<int,int,int,int,int>>();

// won't compile
var badclassTup1 = new Class1<int>();
var badclassTup2 = new Class1<string>();
var badclassTup3 = new Class1<object>();

And, because I've clearly gone insane, let's see what's possible here:

public class Class1<TKey> 
    where TKey : IStructuralEquatable, IStructuralComparable, IComparable
{ 
    public Class1(TKey key)
    {
        Key = key;
        TupleRank = typeof(TKey).GetGenericArguments().Count();
        TupleSubtypes = typeof(TKey).GetGenericArguments();
        Console.WriteLine("Key type is a Tuple (I think) with {0} elements", TupleRank);
        TupleGetters = 
            Enumerable.Range(1, TupleRank)
                .Select(i => typeof(TKey).GetProperty(string.Concat("Item",i.ToString())))
                .Select(pi => pi.GetGetMethod())
                .Select(getter => Delegate.CreateDelegate(
                            typeof(Func<>).MakeGenericType(getter.ReturnType), 
                            this.Key, 
                            getter))
                .ToList();
    }

    public int TupleRank {get; private set;}
    public IEnumerable<Type> TupleSubtypes {get; private set;}
    public IList<Delegate> TupleGetters {get; private set;}
    public TKey Key {get; private set;}

    public object this[int rank]
    {
        get { return TupleGetters[rank].DynamicInvoke(null);}
    }
    public void DoSomethingUseful()
    {
        for(int i=0; i<TupleRank; i++)
        {
            Console.WriteLine("Key value for {0}:{1}", string.Concat("Item", i+1), this[i]);
        }
    }
}

Test rig:

var classTup1 = new Class1<Tuple<int>>(Tuple.Create(1));
var classTup2 = new Class1<Tuple<int,int>>(Tuple.Create(1,2));
var classTup3 = new Class1<Tuple<int,int,int>>(Tuple.Create(1,2,3));
var classTup4 = new Class1<Tuple<int,int,int,int>>(Tuple.Create(1,2,3,4));
var classTup5 = new Class1<Tuple<int,int,int,int,int>>(Tuple.Create(1,2,3,4,5));

classTup1.DoSomethingUseful();
classTup2.DoSomethingUseful();
classTup3.DoSomethingUseful();
classTup4.DoSomethingUseful();
classTup5.DoSomethingUseful();

Output:

Key type is a Tuple (I think) with 1 elements
Key type is a Tuple (I think) with 2 elements
Key type is a Tuple (I think) with 3 elements
Key type is a Tuple (I think) with 4 elements
Key type is a Tuple (I think) with 5 elements
Key value for Item1:1
Key value for Item1:1
Key value for Item2:2
Key value for Item1:1
Key value for Item2:2
Key value for Item3:3
Key value for Item1:1
Key value for Item2:2
Key value for Item3:3
Key value for Item4:4
Key value for Item1:1
Key value for Item2:2
Key value for Item3:3
Key value for Item4:4
Key value for Item5:5
like image 102
JerKimball Avatar answered Nov 07 '22 10:11

JerKimball


I'm surprised no one suggested that you constrain your class (or in my case a method) based on the ITuple interface. To illustrate your example:

public class Class1<T> where T : ITuple

You need to reference the System.Runtime.CompilerServices assembly.

My particular use case is one where I wanted to iterate though a collection of tuples of unknown shape. My solution was along the lines of:

public static void ProcessTupleCollection<T>(this IEnumerable<T> collection) where T: ITuple
{
    var type = typeof(T);
    var fields = type.GetFields();
    foreach (var item in collection)
    {
        foreach (var field in fields)
        {
            field.GetValue(item);
        }
    }
}

I know this is an old one but I hope this helps those who might stumble upon it :)

like image 44
Yosef Bernal Avatar answered Nov 07 '22 10:11

Yosef Bernal


You cannot do this, for two reasons.

First, you can't do "or" constraints on generic types. You could have to have a constraint on Tuple<T1, T2> or Tuple<T1, T2, T3>. This is not possible.

Second, you would need to "pass through" generic arguments, like so:

public class Class1<TKey, T1, T2> where TKey : System.Tuple<T1, T2>

So, you would need a variable number of generic type arguments for your class, and this is not supported either.

Unless there is a specific reason why TKey absolutely has to be one of the tuple classes, leave it unconstrained.

like image 38
cdhowie Avatar answered Nov 07 '22 10:11

cdhowie


You can't. The different generic variants of Tuple does not have a reasonable common base class or interface.

The purpose of generic constraint is to tell the compiler what members the generic type is expected to have.

like image 39
Albin Sunnanbo Avatar answered Nov 07 '22 09:11

Albin Sunnanbo