2009-11-17 70 views
4

这是我刚才遇到的一个有趣的问题。使用扩展方法可以做我想做的事,但似乎不可能对类本身的成员做任何事情。是否可以将泛型参数约束为当前对象的子类型?

随着扩展方法,可以写出具有看起来像这样一个签名的方法:

public static void DoStuff<T>(this T arg1, T arg2) 

这个强制执行,这两种观点是任何类型的照顾你调用它的。与代表一起使用时,这变得更有用。

public static void DoStuff<T>(this T arg1, Action<T> arg2) 

但是我不能让这与成员的工作。有没有这样的限制,因为这:

public void DoStuff<T>(T arg1) where T : typeof(this) 

如果没有工作,那么你可以在你的基类中定义的方法是这样的(我用流,因为他们是一个内置的层次结构.NET):

class Stream 
{ 
    public void DoStuff<T>(T stream) where T : this 
    { 
    } 
} 

,然后在子类中,将不可能这样称呼它:

ByteStream bs = new ByteStream() 
bs.DoStuff(new Stream()) // Error! DoStuff() should be inferred as DoStuff<ByteStream>() 

是否有这样做的方法吗?我相信从参数自动推断类型,扩展方法是语法糖。这可能就是为什么它有效。因为扩展方法被静态调用替换,然后允许推断类型。

我问,因为我试图将一个扩展方法移动到一个公共的基类,并不能得到它没有添加类型信息编译。

澄清。这不是仅仅增加where T : MyType,因为如果我创建一个名为MySubType类型,从MyType继承我将能够调用DoStuffMySubType一个实例,并传递一个MyType作为参数的情况下。这也意味着,在需要Action<T>的情况下,我将无法先调用MySubType的方法。

+0

我想说,你实际上想限制参数派生类型应该让你考虑执行。这是一个打破Liskov替代原则 –

回答

9

规则允许您使用扩展方法执行此操作,但不使用常规实例方法有趣。

你的“typeof(this)”约束确实应该是“this.GetType()”。 “typeof(this)”没有任何意义; typeof需要一个类型,而不是一个任意的表达式。

一旦你意识到这一点,那么我们不能做这样一个约束的原因就会变得更加清晰。 约束总是由​​编译器检查,但很明显,“this.GetType()”直到运行时才能确定。这意味着,如果我们有这样的功能,那么我们会在运行时引入故障点的类型系统:

abstract class Animal 
{ 
    public void Mate<T>(T t) where T : this { ... CENSORED ... } 
} 
... 
Animal x1 = new Giraffe(); 
Mammal x2 = new Tiger(); 
x1.Mate<Mammal>(x2); 

不能配合的老虎有长颈鹿,但如果在程序编译器检测那?无处。直到运行时才知道x1和x2的运行时类型,因此直到此时才能检测到约束违规。

我们讨厌这个。它真的很糟糕,即使经过编译器彻底检查之后,有一个程序没有在任何地方投射,但仍然可能会因类型系统违规而失败。数组协变就是这种情况,因为我们支持数组协变,所以我们不仅有时会通过编译器传递一个破坏的程序,然后崩溃,我们必须减慢对每个引用类型数组的每次写入操作,以便仔细检查我们并没有违反类型系统。这很糟糕,我们不希望在类型系统中增加更多的运行时失败点。

这就是为什么我们要仔细设计C#4中的新差异功能以确保它们始终是类型安全的。 (除非数组上的现有变体转换不是类型安全的并且将继续不是类型安全的。)我们希望确保编译器可以在编译时检查违反的所有约束,而不是必须吐出执行运行时的新代码检查可能会意外失败。

+0

喜欢那个实现:) –

+0

“这个”c#编译器家伙发现规则的结果是多么有趣:)(很高兴知道你是人类) –

+0

仅仅因为我是编译器家伙并不意味着我已经想到了每个功能的每个可能的疯狂使用。有很多功能,很多人为它们提供了疯狂的用途。 –

0

它改成这样:

public void DoStuff<T>(T arg1) where T : BaseType 

,从您的BASETYPE继承这样,你只能使用T型

+0

DoStuff不是一个类,它是一个方法的名称。 –

+0

谢谢......我意识到,只要我发布它。 – bytebender

3

我想你可能能够通过只指定在年底的类型做。

public void DoStuff<T>(T arg1) where T: YourType 

我这样做目前在解决方案,但YourType是一个接口。我想你可以用一个具体的课来做。

+0

+1:我可以确认它适用于具体类型。 –

+0

对不起。这并没有回答这个问题。看看我的分类,如果你愿意,我可以发布一个完整的代码示例。 –

+0

@Jack Ryan:只需在每个子类中覆盖它,更新签名以将子类放入通用约束中(并且如果这是您想要的,则调用'base.DoStuff(arg)')。 –

1

只需使用基类名作为constraint即可。

public void DoStuff<T>(T arg1) where T : BaseClass 
1

经过您的澄清,我认为实现这一目标的唯一方法是让您的基类变得通用。这有点笨拙,但应该做你需要的。

MySubType foo = new MySubType(); 
MySubType bar = new MySubType(); 
foo.DoStuff(bar); 

// ... 

public class MySubType : MyBaseType<MySubType> 
{ 
} 

public class MyBaseType<T> 
{ 
    public void DoStuff(T arg1) 
    { 
     // do stuff 
    } 
} 
相关问题