C# 为什么';泛型约束是否有助于编译器在具有可选参数的多态方法之间进行选择?

C# 为什么';泛型约束是否有助于编译器在具有可选参数的多态方法之间进行选择?,c#,polymorphism,generic-constraints,C#,Polymorphism,Generic Constraints,给定两个静态重载方法,一个为泛型,另一个为非泛型: public static T? NullIf<T>(this T value, T equalsThis) where T : struct // value types including enums { return EqualityComparer<T>.Default.Equals(value, equalsThis) ? (T?

给定两个静态重载方法,一个为泛型,另一个为非泛型:

public static T? NullIf<T>(this T value, T equalsThis) 
   where T : struct  // value types including enums
{                        
    return EqualityComparer<T>.Default.Equals(value, equalsThis)
        ? (T?)null
        : value;
}

public static string NullIf(this string value, string equalsThis, bool ignoreCase = false)
{
    return String.Compare(value, equalsThis, ignoreCase) == 0 ? null : value;
}
生成编译错误,因为它首选泛型:

错误CS0453:类型“string”必须是中不可为空的值类型 将其用作泛型类型或方法中的参数“T” 'ExtensionMethods.NullIf(T,T)'

如果调用方提供了可选的ignoreCase参数,或者从方法中删除了可选的ignoreCase参数,或者删除了受约束的泛型方法,那么它将编译


为什么编译器不使用where约束来消除泛型,因为它可以识别不兼容?

Jon Skeet和Eric Lippert都非常详细地介绍了编译器的行为,以及它为什么以这种方式工作等等,但是我真的不知道在所有的用例中是否有一个解决方案

我在自己的一个类库中有非常类似的扩展方法(但我很少使用它们)。我所做的一件不同的事情是区分可空到可空(
NullIf
)和不可空到可空(
ToNullIf
)。值类型的
NullIf
就是我所说的
ToNullIf

假设您想从一个
NullIf
开始,它一般适用于任何可为null的类型。不能在同一个类中同时使用这两个约束,因为约束不是方法签名的一部分。为了解决这个问题,你可以把它们分为不同的类

valuetypes的公共静态部分类扩展方法
{
//可为空到可为空
公共静态T?NullIf(此T?值,T?其他)
其中T:struct
{
返回值==null | | EqualityComparer.Default.Equals((T)值,其他)?null:value;
}
}
引用类型的公共静态部分类扩展方法
{
//可为空到可为空
公共静态T NullIf(此T值,T其他)
T:在哪里上课
{
返回EqualityComparer.Default.Equals(值,其他)?null:值;
}
}
编译器将按照Jon Skeet和Eric Lippert在各自博客中描述的方式为引用类型和可空值类型选择正确的方法

我上面提到的区别包括一个
ToNullIf
扩展方法,它采用(不可为null的)值类型。它可以与接受可空值类型的
NullIf
处于同一类中。但是,它不能同时被称为
NullIf
。我会再次听从大师们的解释

不过,幸运的是,通过不同的方法名称指示提升为nullable实际上在更清楚地传达意图以及在IDE中向您传达启示方面非常有用,例如IntelliSense对于纯值类型不显示
NullIf
,对于nullable值类型不显示
ToNullIf
。然而,多亏了IntelliSense在VS 2017中的部分匹配,输入“NullIf”将显示
ToNullIf
如果这是可用的

ValueTypes的分部类扩展方法
{
//不可为空到可为空
公共静态T?T努利夫(此T值,T其他)
其中T:struct
{
返回EqualityComparer.Default.Equals(值,其他)?(T?)null:value;
}
}
如果要在采用引用类型的
NullIf
之上添加字符串专门化,可以,但如果没有至少一个非默认参数,则不能使用默认参数来将其与调用站点的通用版本区分开来。在您的情况下,需要提供两个重载。没有
ignoreCase
参数的重载会阻止选择
NullIf
,因为前者是更具体的类型匹配。一个带有
ignoreCase
参数的函数可以提供所需的大小写不敏感度

引用类型的部分类扩展方法 { 公共静态字符串NullIf(此字符串值,字符串其他)=>NullIf(值,其他,false); 公共静态字符串NullIf(此字符串值、字符串其他、bool ignoreCase) { 返回字符串.Compare(value,equalthis,ignoreCase)==0?null:value } } 如果您对nullable to nullable案例的方法名称中引用类型和可空值类型之间的奇偶校验不感兴趣,那么您没有理由不删除上面的
ExtensionMethodsForValueTypes.NullIf
扩展方法,并将
ToNullIf
重命名为
NullIf
。最终,将不同的类分离出来解决了最初的问题


最后一点注意:所有这些都没有考虑C#8.0中的可空引用类型和不可空引用类型,部分原因是它是新的,部分原因是无法进行区分,或者,如果可以,它需要一种完全不同的技术。

如果将
bool ignoreCase=false
添加到泛型方法中,它也会起作用。这是因为控制重载解析的规则,而且它并不漂亮。阅读Jon的回答。我本来打算粘贴一篇巨大的规范文章,但我认为@JohnWu和erics博客的链接问题可以回答这个问题completely@MichaelRandall-Skeet和Lippert对此进行了很好的解释,但他们是否为该用例提供了解决方案?如果是这样,很难确定。我希望我的答案能填补这个空白。
string s = "None";
string result = s.NullIf("None");