【问题标题】:Constraining class generic type to a Tuple将类泛型类型约束为元组
【发布时间】:2013-03-22 19:46:10
【问题描述】:
  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)
    
    }
    

我不太确定如何实现这一点。目标是防止自己为每个元组类实现一个类。

【问题讨论】:

  • 每个不同的Tuple&lt;&gt; 类型都有不同数量的泛型参数,因此它们是不同的类型。您不能一次对所有这些添加约束。 TKey 需要受到约束有什么原因吗?
  • @cdhowie 我在你的解决方案中回答了这个问题,但我会在这里发布“绝对有理由它必须是元组类之一,这个想法是让 TKey 充当一个有 n 个参数的键,即 T1, T2, T3, ..., Tn。"

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


【解决方案1】:

您不能像其他人所说的那样,但您几乎可以做到。

所以所有Tuple&lt;...&gt; 类都有这样的签名:

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

【讨论】:

  • 感谢这篇文章。我可能会考虑实施。
  • @MaelstromYamato 祝你好运!我还添加了一个简单粗暴的用法示例
  • 根据我的经验,这是第二个最好的解决方案,紧随其后的是不首先创建此问题。它使您可以访问这些常用接口的所有成员。
  • 同意;我只是想找出一种可以滥用类型系统的方法。 ;)
  • 在演示了您的解决方案之后,它可以完成,但这是一个非常丑陋的实现。 :)
【解决方案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);
        }
    }
}

我知道这是一个旧的,但我希望这可以帮助那些可能偶然发现它的人:)

【讨论】:

  • 我很惊讶您使用反射来访问您的 ITuple 值而不是使用索引,因为 ITuple 具有 Length 属性以及 Int32[Int32] 索引属性... +1 但是为了完整起见,您应该添加该信息。 More info
【解决方案3】:

你不能这样做,有两个原因。

首先,您不能对泛型类型进行“或”约束。您可能必须对Tuple&lt;T1, T2&gt; Tuple&lt;T1, T2, T3&gt; 进行约束。这是不可能的。

其次,您需要“通过”通用参数,如下所示:

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

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

除非有特定原因TKey绝对必须是元组类之一,否则不要限制它。

【讨论】:

  • 绝对有理由认为它必须是元组类之一,其想法是让 TKey 作为具有 n 个参数的键,即 T1、T2、T3、...、Tn .
  • @MaelstromYamato 但是为什么它必须是元组类之一?您是否打算将结果转换为特定的元组类型?为什么有人不能实现自己的类元组类然后使用它?
  • 澄清一下:由于您无法确定此处使用的 哪个 元组类型,因此您无法放心地将其转换为特定的 Tuple&lt;&gt; 类型,即使如果您可以(ab)以这种方式使用泛型。
  • TKey 基于一组参数,即 T1, T2, ..., Tn。我有几种情况,参数总数不同。那么元组是必要的吗?我会说不。然而,对于任何 n,T1,T2,...,Tn,都可以被认为是一个元组,因此我认为对 TKey 的约束是合理的。
  • 可以将任意数量的 T 视为一个元组,但该框架只有最多八个 T 的元组类型。不,甚至没有办法以这种方式指定可变数量的泛型参数。
【解决方案4】:

你不能。 Tuple 的不同泛型变体没有合理的通用基类或接口。

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

【讨论】:

    【解决方案5】:

    像这样:

    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 的泛型参数添加到你的类中'

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

    但是...您可以定义自己的由多个 Class1 继承的基本接口,然后将该接口用作约束。听起来不漂亮,但会工作

    【讨论】:

    • 这将不允许TKey 成为任何其他元组类型,例如Tuple&lt;T1, T2, T3&gt;。这是 OP 的问题所要求的。
    猜你喜欢
    • 1970-01-01
    • 2019-08-24
    • 1970-01-01
    • 1970-01-01
    • 2017-01-21
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多