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

本文关键字:类型 约束 编译 用户 定义 | 更新日期: 2023-09-27 18:18:55

我目前正在尝试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' to"ContainsType"

我有点理解为什么这不起作用,但到目前为止,我还没能想出一个可行的替代方案。有人知道我想要的东西在c#中是否可能实现吗?

谢谢。

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

你的挑战目标是无用的,因为。net的泛型既是一个编译时结构,也是一个运行时结构。即使你的程序编译没有错误,仍然可以通过反射扩展你的泛型,传递一个"未经批准的"类型。

我明白你是从哪里来的(我也非常喜欢Andrei Alexandrescu的那本书),但是关于c#泛型,有一件重要的事情要理解,那就是泛型不是c++模板。除了语法上的一些相似之处,它们甚至没有那么接近:"不是同一个棒球场,不是同一个联赛,甚至不是同一个运动"。需要对排序(int型可以,double型不行)进行编译时验证的主要原因是,可以隐式地访问操作:如果在模板扩展时提供的ab类型允许+操作,那么a+b将成功。在c#中就不一样了:如果你想对传递给泛型的值进行操作,你必须通过类型约束显式地规定该操作的存在,或者提供一种显式的方式来执行该操作(委托、接口等)。无论哪种方式,告诉编译器你的模板适用于int型而不是double型,都不会给你带来任何好处。

我不明白你想做的事情怎么可能有泛型。

这样一个泛型类的目的是什么(不编译,它在这里作为示例):

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;
    }
}
  • 如果它的意思是"T必须是A, B和C",那么它是不可能的,因为c#中不存在多重继承。
  • 如果表示"T必须是A或B或C",则:
    • 如果A, B和C没有任何共同之处,那么GenericClass没有目的:你会用MyMember实现什么逻辑,因为这3个类没有任何共同之处?
    • 如果A, B和C有一些共同点,那么所有的类实现一个特定的接口或继承自相同的基类,你将使用这个接口或基类where约束。

这不起作用,因为c#泛型函数只需要根据约束通过类型检查。没有考虑实际的类型参数,因为泛型没有特化。

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

编译器会检查

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

和this(正确地)编译失败。

另一方面,你可以写:

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

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