将类泛型类型约束为元组

时间:2013-03-22 19:46:10

标签: c# .net c#-4.0 generics

  1. 我想用一个通用的TKey创建一个类,其中TKey是可以创建的System.Tuple类型之一。

    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)
    
    }
    
  2. 我不太确定如何实现这一点。目标是防止自己为每个元组类实现一个类。

5 个答案:

答案 0 :(得分:15)

你不能像其他人所说的那样,但你可以差不多这样做。

所以所有Tuple<...>类都有这样的签名:

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

所有这些接口保存ITuple都是公共的(ITuple是一个内部接口),因此您可以尝试制作类似的东西:

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

“但是等等!”,你说,“我怎么能确定没有别的东西在实现这些接口?”

嗯,你不能。但就像我说的那样,这只是几乎的方式 - 幸运的是,IStructuralEquatableIStructuralComparable仅在(在框架级别,自然地)在以下类型中使用:

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>

所以它非常接近。将此与TKey实际上是Tuple的某种变体的运行时检查相结合,您可能拥有所需的内容。

编辑:

一些基本用法:

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>();

而且,因为我显然已经疯了,让我们看看这里有什么可能:

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]);
        }
    }
}

试验台:

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();

输出:

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

答案 1 :(得分:3)

由于两个原因,你无法做到这一点。

首先,您不能对泛型类型执行“或”约束。您可能必须对Tuple<T1, T2> Tuple<T1, T2, T3>设置约束。这是不可能的。

其次,您需要“传递”泛型参数,如下所示:

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

因此,您需要为您的类提供可变数量的泛型类型参数,这也不受支持。

除非有{... 1}} 绝对必须其中一个元组类的特定原因,否则请保持不受约束。

答案 2 :(得分:2)

你做不到。 Tuple的不同泛型变体没有合理的公共基类或接口。

泛型约束的目的是告诉编译器泛型类型应该具有哪些成员。

答案 3 :(得分:2)

令我惊讶的是,没有人建议您基于ITuple接口来限制类(在我的情况下为方法)。为了说明您的示例:

public class Class1<T> where T : ITuple

您需要引用System.Runtime.CompilerServices程序集。

我的特定用例是我要遍历一组形状未知的元组的用例。我的解决方案是:

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);
        }
    }
}

我知道这是一个古老的东西,但我希望这对那些可能偶然发现它的人有所帮助:)

答案 4 :(得分:-1)

像这样:

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

}

将Tuple的通用参数添加到您的班级

根据下面的评论,如果没有接口或反射,就无法将其合并为一个类。如果有没有多个元组类

但是......你可以定义自己的基接口,它由多个Class1继承,然后使用该接口作为约束。听起来不错但会起作用