防止用于泛型参数的特定类型层次结构
本文关键字:类型 层次结构 参数 用于 泛型 | 更新日期: 2023-09-27 17:57:03
我从大量相关类型的背景开始这个问题;架构背后的接口和基本原理。
然后我意识到 - '就是这样 - 保持简单并进入正题'。
所以在这里。
我有这样的课程:
public class AGenericType<T> : AGenericTypeBase
{
T Value { get; set; }
}
当然,.net 允许我这样做:
AGenericType<AGenericType<int>> v;
然而,在AGenericType<T>
的用法中,
Nullable<Nullable<double>> v;
我希望能够做的是限制这个泛型类型,以便当它T
派生自AGenericTypeBase
时,就不可能创建这样的实例,甚至无法声明对该类型的引用 - 最好是在编译时。
现在有趣的是,我在这里给出的Nullable<T>
示例确实生成了一个编译器错误。 但是我看不出Nullable<T>
如何将T
限制为非Nullable<T>
类型 - 因为Nullable<T>
是一个结构,我能找到的唯一通用约束(即使在 IL 中,它经常产生编译器机密,例如带有委托的机密)是where T:struct
。 所以我认为一个人一定是一个编译器黑客(编辑:请参阅下面的希尔加斯的答案+评论@Daniel对此进行一些探索)。 编译器黑客我当然不能重复!
对于我自己的方案,IL 和 C# 不允许像这样使用负断言约束:
public class AGenericType<T> : where !T:AGenericTypeBase
(请注意约束中的"!")
但是我可以使用什么替代方案?
我想到了两个:
1) 在AGenericType<T>
的构造函数中生成的运行时异常:
public AGenericType(){
if(typeof(AGenericBase).IsAssignableFrom(typeof(T)))
throw new InvalidOperationException();
}
不过,这并不能真正反映错误的本质 - 因为问题是泛型参数,因此是整个类型;而不仅仅是那个实例。
2)因此,相反,相同的运行时异常,但在AGenericType<T>
的静态初始值设定项中生成:
static AGenericType(){
if(typeof(AGenericBase).IsAssignableFrom(typeof(T)))
throw new InvalidOperationException();
}
但是,我面临着一个问题,即此异常将被包装在TypeInitializationException
中,并且可能会引起混淆(根据我的经验,实际读取整个异常层次结构的开发人员在地面上很薄)。
对我来说,这是 IL 和 C# 中"否定断言"泛型约束的明显案例 - 但由于这不太可能发生,你会怎么做?
关于Nullable<T>
:约束T : struct
是编译器错误的原因,因为结构的变量永远不能为空,而Nullable<T>
类型的变量可以为空。
因此,这是一个编译器黑客,但只有这样,Nullable<T>
被视为可空值类型而不是不可空值类型。
public struct Nullable2<T> where T: struct
{
}
// Both lines will generate the same error
Nullable2<Nullable<int>> v;
Nullable<Nullable<int>> v2;
我想说的是:
Nullable<T>
不会执行"'否定断言'泛型约束"。
我希望我清楚地理解你的问题,我相信你需要一个标记接口。
如何确保某些通用参数T
不会从某个类继承?好吧,既然你不能这样做,你可以创建一个名为"IWhatever"的标记接口(用适合你需求的名字更改"Whatever"),并使用它来区分A的种类:
public class B<T>
where T : A, IWhatever
T 继承 A,但 A 必须实现 IWhatever。
您可以根据需要实现任意数量的标记接口,以便具有不同的T
类型。
更新:
这与如何约束 T 不能继承 A 有关吗?
绝对。如果我需要它,我使用标记接口:IIsNotA
,你可以有这个约束:T : IIsNotA
。
这只是一个例子。在实际方案中,将使用与某些类层次结构相关的正确标识符调用IIsNotA
。
我在泛型类型的静态初始化器中出现了一个例外,因为它更清楚地表明调用者使用该类型与传递给构造函数的运行时参数相比存在问题。 调用者很容易修复该错误,因此我认为这是最好的折衷方案。