C#中用户定义的编译时类型约束

时间:2011-12-29 02:15:58

标签: c# generics constraints

我目前正在使用C#进行泛型试验,并对自己提出了以下挑战:

给定泛型函数f<T>,在编译期间验证T是来自给定集合[T1,T2,...,Tn]的类型。例如,如果在f<T>我们有

CompileTimeAssert<T>.isContainedIn<TypeList<string, int, bool>>();

然后f<int>应该编译,而f<double>不应该编译。

我还没到那儿。这就是我到目前为止所做的:

interface ContainsType<T> {}

class TypeList<T1>: ContainsType<T1> {} 
class TypeList<T1, T2>: TypeList<T2>, ContainsType<T1>  {}
class TypeList<T1, T2, T3>: TypeList<T2, T3>, ContainsType<T1> {}
class TypeList<T1, T2, T3, T4>: TypeList<T2, T3, T4>, ContainsType<T1> {}
// add longer type lists to taste

class CompileTimeAssert<T>
{
    public static void isContainedIn<TypeList>()
        where TypeList: ContainsType<T> {}
    public static void isContainedIn<TypeList>(TypeList tl)
        where TypeList: ContainsType<T> {}
}

鉴于上面的代码,以下编译(如预期的那样):

 // uses first overload
CompileTimeAssert<int>.isContainedIn<TypeList<string, int, bool>>();

var myTypeList = new TypeList<string, bool>();
CompileTimeAssert<string>.isContainedIn(myTypeList); // uses second overload

以下内容未按预期编译:

CompileTimeAssert<short>.isContainedIn<TypeList<string, int, bool>>();

var myTypeList = new TypeList<string, bool>();
CompileTimeAssert<double>.isContainedIn(myTypeList);

这一切都非常可爱,但也没用。如果可以做到以下几点会更有用:

void f<T>()
{
    CompileTimeAssert<T>.isContainedIn<TypeList<string, int, bool>>();
}

然后编译f<int>f<double>导致编译错误。

唉,上面给出的f<T>无法编译(无论具有具体类型的任何调用)。 我收到以下错误(在Mac OS X上使用MonoDevelop):

  

错误CS0311:类型TypeList<string,int,bool>' cannot be used as type parameter 'TypeList' in the generic type or method 'CompileTimeAssert<T>.isContainedIn<TypeList>()'. There is no implicit reference conversion from TypeList'到   'ContainsType'

我有点理解为什么这不起作用,但到目前为止,我还没有找到一个可行的替代方案。有没有人对C#中是否有我想要的东西有任何想法?

感谢。

3 个答案:

答案 0 :(得分:3)

您的挑战的目标是无用的,因为.NET的泛型与编译时构造一样是运行时构造。即使您的程序编译没有错误,仍然可以通过反射扩展您的泛型,传递“未批准”类型。

我理解你的来源(我非常喜欢Andrei Alexandrescu的那本书)但是要理解C#泛型的重要一点是泛型不是C ++模板。除了小的句法相似之外,它们甚至不是那么接近:“不是同一个球场,不是同一个联赛,甚至不是同一项运动”。需要进行编译时验证排序“整数是正常的,双打不正确”的主要驱动力是你可以隐式访问操作:a+b如果模板扩展时提供的ab类型允许+操作。它在C#中不一样:如果您希望对传递给泛型的值执行操作,你必须通过类型约束明确地规定该操作的存在,或者提供一种执行该操作的明确方式(委托,接口等)。无论哪种方式,告诉编译器你的模板适用于int nut而不是for dubles什么都不买你。

答案 1 :(得分:0)

我不知道你是如何通过泛型来做的。

这样一个通用类的目的是什么(没有编译,它就是这里的例子):

public class GenericClass<T>
    where T : A
    where T : B
    where T : C
{
    public T MyMember { get; set; }

    public GenericClass(T myMember)
    {
        this.MyMember = myMember;
    }
}
  • 如果它意味着&#34; T必须是A和B以及C&#34;,那么它是不可能的,因为C#中不存在多重继承。
  • 如果它意味着&#34; T必须是A或B或C&#34;,那么:
    • 如果A,B和C没有任何共同点,那么GenericClass没有任何目的:你会用MyMember实现什么逻辑,因为3个类没有任何共同点?
    • 如果A,B和C具有共同点,那么所有类都实现特定接口或从同一基类继承,并且您将where约束使用此接口或基类。

答案 2 :(得分:0)

这不起作用,因为C#泛型函数只需要根据约束传递类型检查。不考虑实际类型参数,因为泛型不是专门的。

你唯一承诺的是(隐含地)T : object

所以编译器类型检查

CompileTimeAssert<object>.isContainedIn<TypeList<string, int, bool>>();

并且这(正确)无法编译。

另一方面,你可以写:

void f<T>() where T : Form
{
    CompileTimeAssert<T>.isContainedIn<TypeList<string, Control>>();
}

它会编译,因为每个Form都是一个Control