是否可以将C#泛型方法类型参数约束为“可从”包含类的类型参数“赋值”?

时间:2012-06-29 03:09:16

标签: c# generics type-constraints

我怀疑答案是肯定的,但我想知道是否有可能做到这样的事情:

public class MyGenericClass<TSomeClass> {
    public void MyGenericMethod<TSomeInterface>() 
        // This doesn't compile.
        where TSomeClass : TSomeInterface 
    {
        //...
    }
}

我在上面(非工作)示例中指出的意思是约束TSomeInterface使得它可以是任何基类,实现的接口,或者(如果你真的想要得到幻想)隐式转换MyGenericClass

注意: 我怀疑这个从未在C#中实现的原因是泛型约束不是真的意味着是代码契约,这就是我在这里尝试使用它们的方式。我真的不在乎TSomeInterface是什么类型,只要它由TSomeClass实现。

到目前为止,我已经一起攻击了这个:

public class MyGenericClass<TSomeClass> {
    public void MyGenericMethod<TIntermediateType, TSomeInterface>() 
        where TIntermediateType : TSomeClass, TSomeInterface 
    {
        //...
    }
}

这或多或少强制执行我想要的约束(TSomeClass必须继承,或者在接口的情况下,实现,TSomeInterface),但调用它是非常笨拙的,因为我必须指定TIntermediateType(即使我真的希望它对TSomeClass进行评估):

var myGenericInstance = new MyGenericClass<TSomeClass>();
myGenericInstance.MyGenericMethod(TSomeClass, TSomeInterface);

此外,上述hack被破坏是因为调用者理论上可以指定TSomeClass的子类作为第一个类型参数,其中只有子类实现TSomeInterface

我想要这样做的原因是我正在为WCF服务编写一个流畅的工厂模式,我会喜欢来阻止调用者(在编译时)尝试使用服务类未实现的契约创建端点。我显然可以在运行时检查这个(WCF实际上是为我做的),但我是编译时检查的忠实粉丝。

有更好/更优雅的方式来实现我的目标吗?

3 个答案:

答案 0 :(得分:3)

我能够解决无法编译的原因如下:

考虑这个程序编译:

class Program {
    class Class1 { }
    class Class2 { }
    public class MyGenericClass<TSomeClass> {
        public void MyGenericMethod<TSomeInterface>() where TSomeClass : TSomeInterface {
        }
    }
    static void Main(string[] args) {
        var inst = new MyGenericClass<Class1>();
    }
}

一切都很好。编译器很高兴。现在考虑我改变Main方法:

static void Main(string[] args) {
    var inst = new MyGenericClass<Class1>();
    inst.MyGenericMethod<Class2>();
}

编译器会抱怨Class1没有实现Class2。但哪一行错误?约束是对MyGenericMethod的调用,但有问题的代码行是MyGenericClass的创建。

换句话说,哪一个得到了红色的波浪线?

答案 1 :(得分:3)

this linked question中所述,您不能在where子句的左侧使用非当前声明的类型参数。

正如w0lf在其他问题中所建议的,你可以做的是在你的接口(而不是方法)声明中提供两种类型:

public class MyGenericClass<TSomeClass, TSomeInterface> {
    where TSomeClass : TSomeInterface 
    public void MyGenericMethod() // not so generic anymore :( 
    {
        //...
    }
}

然而,这极大地限制了你的MyGenericMethod并迫使你的类在前面声明你允许的基本接口。

所以另一个选项是使用带有更多类型参数的静态方法:

public class MyGenericClass<TSomeClass> {
    public static void MyGenericMethod<TSomeClass, TSomeInterface>
                                         (MyGenericClass<TSomeClass> that) 
        where TSomeClass : TSomeInterface 
    {
        // use "that" instead of this
    }
}

可能你可以使它成为一种扩展方法,使其像实际方法一样对用户显示。

这些都不是你想要的,但可能比中间类型解决方案更好。

至于为什么不呢?的原因,我的猜测是它会在没有添加足够值的情况下使编译器复杂化。这是一个discussion by Angelika Langer of the same subject but about Java。虽然C#和Java之间存在显着差异,但我认为她的结论也适用于此:

  

底线是下限对类型的有用性   参数有点争议。它们可能会令人困惑   当用作泛型类的类型参数时,甚至会产生误导。上   另一方面,通用方法偶尔会从一种类型中获利   具有下限的参数。对于方法,一个解决方法   通常可以找到缺少下界类型参数。这样的   解决方法通常涉及静态通用方法或更低级方法   绑定的通配符。

她还提供了一个很好的用例,请参阅上面的链接。

答案 2 :(得分:1)

扩展方法提供了最佳解决方案,但它并不能完全解决您的所有问题。

public class MyGenericClass<TSomeClass>
{
}

public static class MyGenericClassExtensions
{
    public static void MyGenericMethod<TSomeClass, TSomeInterface>(this MyGenericClass<TSomeClass> self)
        where TSomeClass : TSomeInterface
    {
        //...
    }
}

在调用MyGenericMethod时仍然需要指定这两种类型,但是它会阻止调用者为TSomeClass指定不正确的类型,就像您提出的方法一样。使用这种方法,可以像这样调用方法:

var myGenericInstance = new MyGenericClass<TSomeClass>();
myGenericInstance.MyGenericMethod<TSomeClass, TSomeInterface>();

如果声明类型参数MyGenericClassMyGenericMethod的第一个类型参数不匹配,则会出现编译错误。

由于第一个类型参数可以由this参数推断,因此编译器通常可以推断出两个类型参数(如果它们是方法的附加参数)。