代码之家  ›  专栏  ›  技术社区  ›  Jack Ryan

是否可以将泛型参数约束为当前对象的子类型?

  •  4
  • Jack Ryan  · 技术社区  · 16 年前

    我刚刚遇到了一个有趣的问题。使用扩展方法可以做我想做的事情,但似乎不可能处理类本身的成员。

    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)
    

    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 DoStuff MyType Action<T> 我将无法调用的方法 MySubType

    5 回复  |  直到 16 年前
        1
  •  10
  •   Eric Lippert    16 年前

    有趣的是,这些规则允许您使用扩展方法而不是常规实例方法来实现这一点。

    您的“typeof(this)”约束实际上应该是“this.GetType()。“typeof(this)”没有任何意义;typeof接受类型,而不是任意表达式。

    编译器始终检查约束

    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中仔细设计新的方差特性,以确保它们始终是类型安全的。(除非数组上的现有变量转换不是类型安全的,并且将继续不是类型安全的。)我们希望确保编译器可以在编译时检查所有约束是否存在冲突,而不是必须抛出新代码来执行可能意外失败的运行时检查。

        2
  •  3
  •   Brett Bim    16 年前

    我认为您可以通过在末尾指定类型来实现这一点。

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

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

        3
  •  1
  •   tvanfosson    16 年前

    只需使用基类名称作为 constraint .

    public void DoStuff<T>( T arg1 ) where T : BaseClass
    
        4
  •  1
  •   LukeH    16 年前

    根据您的说明,我认为实现这一点的唯一方法是使基类通用。这有点笨拙,但应该做你需要的。

    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
        }
    }
    
        5
  •  0
  •   bytebender    16 年前

    将其更改为:

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

    这样,您只能使用从基类型继承的类型T