防止用于泛型参数的特定类型的层次结构

时间:2011-05-18 10:14:14

标签: c# .net architecture

我用一大堆有问题的背景来开始这个问题;架构背后的界面和基本原理。

然后我意识到 - '这就是 - 保持简单并且达到目的'。

所以这里。

我有一个这样的课程:

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 Hilgarth的回答+评论,以便对此进行一些探索)。编译器黑客当然不能重复!

对于我自己的场景,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内并且可能导致混淆的问题(根据我的经验,实际上读取整个异常层次结构的开发人员实际上很薄)。 / p>

对我而言,这是IL和C#中“否定断言”一般约束的明显案例 - 但由于这不太可能发生,你会做什么?

3 个答案:

答案 0 :(得分:2)

关于Nullable<T>:约束T : struct是编译器错误的原因,因为struct的变量永远不能为null,而Nullable<T>类型的变量可以为null。
所以, 是一个编译器黑客,但只有这样,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>不执行“'否定断言'通用约束”。

答案 1 :(得分:1)

我希望我能清楚地理解你的问题,我相信你需要一个标记界面。

如何确保某些通用参数T不会从某些类继承?好吧,因为你不能这样做,你可以创建一个名为“IWhatever”的标记界面(用自己的名字改变'Whatever'以满足你的需要)并用它来区分各种A:

public class B<T> 
   where T : A, IWhatever

T继承A,但A必须实现IWhatever。

您可以根据需要实现尽可能多的标记接口,以便拥有不同的T种类。

<强>更新

这与如何约束T有什么关系不能继承A

绝对。如果我需要,我使用标记界面:IIsNotA你可以有这个约束:T : IIsNotA

这只是一个例子。在实际场景中,将使用与某个类层次结构相关的适当标识符调用IIsNotA

答案 2 :(得分:1)

最后,我在泛型类型的静态初始化器中出现异常,因为它更清楚地表明调用者使用类型而不是运行时参数有问题传递给构造函数。调用者很容易解决错误,所以我认为这是最好的妥协。