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#中是否可能实现吗?
谢谢。
你的挑战目标是无用的,因为。net的泛型既是一个编译时结构,也是一个运行时结构。即使你的程序编译没有错误,仍然可以通过反射扩展你的泛型,传递一个"未经批准的"类型。
我明白你是从哪里来的(我也非常喜欢Andrei Alexandrescu的那本书),但是关于c#泛型,有一件重要的事情要理解,那就是泛型不是c++模板。除了语法上的一些相似之处,它们甚至没有那么接近:"不是同一个棒球场,不是同一个联赛,甚至不是同一个运动"。需要对排序(int型可以,double型不行)进行编译时验证的主要原因是,可以隐式地访问操作:如果在模板扩展时提供的a
和b
类型允许+
操作,那么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
约束。
- 如果A, B和C没有任何共同之处,那么GenericClass没有目的:你会用
这不起作用,因为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
。