9

我怀疑答案是否定的,但我想知道是否可以这样做:

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);

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

我想这样做的原因是我正在为 WCF 服务编写流利的工厂模式,并且我想防止调用者(在编译时)尝试使用服务类没有的合同创建端点' t 实施。我显然可以在运行时检查这个(WCF 实际上是为我做的),但我是编译时检查的忠实粉丝。

有没有更好/更优雅的方式来实现我在这里追求的目标?

4

3 回答 3

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.

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

于 2012-06-29T04:43:17.327 回答
3

正如在这个链接的问题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
    }
}

可能您可以将其作为扩展方法,使其在用户看来就像一个实际方法。

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

至于为什么不这样做的原因?,我的猜测是它会使编译器复杂化而没有增加足够的价值。这是Angelika Langer 关于同一主题但关于Java的讨论。尽管 C# 和 Java 之间存在显着差异,但我认为她的结论也可能适用于这里:

底线是类型参数下限的有用性是有争议的。当用作泛型类的类型参数时,它们会令人困惑甚至误导。另一方面,泛型方法有时会从具有下限的类型参数中受益。对于方法,通常可以找到缺少下限类型参数的解决方法。这种变通方法通常涉及静态泛型方法或下限通配符。

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

于 2013-12-08T04:36:47.080 回答
2

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

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>();

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

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

于 2015-06-25T11:57:37.000 回答