c# - 为什么泛型约束不能帮助编译器在具有可选参数的多态方法中做出决定?
问题描述
给定 2 个静态重载方法,一个是通用的,一个不是:
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;
}
测试代码:
string s = "None";
string result = s.NullIf("None");
生成编译错误,因为它更喜欢泛型:
错误 CS0453:类型“字符串”必须是不可为空的值类型,才能将其用作泛型类型或方法“ExtensionMethods.NullIf(T, T)”中的参数“T”
如果可选的 ignoreCase 参数由调用者提供或从方法中删除,或者如果受约束的泛型方法被删除,它会编译。
为什么编译器不使用 where 约束来消除泛型,因为它认识到不兼容?
解决方案
Jon Skeet 和 Eric Lippert 都非常详细地介绍了编译器的行为方式、为什么它会以这种方式工作等等,但我无法确定是否有针对此用例的解决方案。
我在自己的一个类库中有非常相似的扩展方法(然而,我很少使用它们)。我做的一件不同的事情是区分可空到可空(NullIf
)与不可空到可空(ToNullIf
)。你NullIf
的 for 值类型就是我所说ToNullIf
的。
假设您想从NullIf
一般适用于任何可为空的类型的 a 开始。您不能将两者都放在同一个类中,因为约束不是方法签名的一部分。为了解决这个问题,您可以将它们放在单独的类中。
public static partial class ExtensionMethodsForValueTypes
{
// Nullable to nullable
public static T? NullIf<T>(this T? value, T? other)
where T : struct
{
return value == null || EqualityComparer<T>.Default.Equals((T)value, other) ? null : value;
}
}
public static partial class ExtensionMethodsForReferenceTypes
{
// Nullable to nullable
public static T NullIf<T>(this T value, T other)
where T : class
{
return EqualityComparer<T>.Default.Equals(value, other) ? null : value;
}
}
编译器将按照 Jon Skeet 和 Eric Lippert 在各自博客中描述的方式为引用类型和可空值类型选择正确的方法。
我上面提到的区别包括一个ToNullIf
扩展方法,它采用(不可为空的)值类型。它可以与NullIf
采用可为空值类型的类在同一个类中。但是,它也不能称为NullIf
. 出于这个原因,我将再次推迟到大师赛。
不过幸运的是,通过不同的方法名称指示提升为 nullable 实际上对于更清楚地传达意图以及在 IDE 中向您传达启示非常有用,例如当 IntelliSense 不显示NullIf
纯值类型或ToNullIf
对于可空值类型。然而,由于 IntelliSense 在 VS 2017 中的部分匹配,键入“NullIf”将显示ToNullIf
这是否可用。
partial class ExtensionMethodsForValueTypes
{
// Non-nullable to nullable
public static T? ToNullIf<T>(this T value, T other)
where T : struct
{
return EqualityComparer<T>.Default.Equals(value, other) ? (T?)null : value;
}
}
如果您想在NullIf
采用引用类型的顶部添加字符串特化,您可以,但您不能有一个没有至少一个非默认参数的默认参数,以将其与调用站点的通用版本区分开来。在您的情况下,您需要提供两个重载。没有参数的重载会ignoreCase
阻止NullIf<string>
选择,因为前者是更具体的类型匹配。带有参数的一个为您提供所需ignoreCase
的不区分大小写。
partial class ExtensionMethodsForReferenceTypes
{
public static string NullIf(this string value, string other) => NullIf(value, other, false);
public static string NullIf(this string value, string other, bool ignoreCase)
{
return String.Compare(value, equalsThis, ignoreCase) == 0 ? null : value
}
}
如果您对可空到可空情况的方法名称中引用类型和可空值类型之间的奇偶校验不感兴趣,那么您没有理由不能放弃ExtensionMethodsForValueTypes.NullIf
上面的扩展方法并重命名ToNullIf
为NullIf
. 最终,解决原始问题的是分成不同的类。
最后一点:这些都没有考虑到 C# 8.0 中的可空和不可空引用类型,部分是因为它是新的,部分是因为根本无法进行区分,或者,如果可以,它需要不同的技术完全。
推荐阅读
- pip - word2vec 安装失败的轮子无法为使用 PEP 517 且无法直接安装的 word2vec 构建轮子
- angular - 错误:遇到未定义的提供者!通常这意味着你有一个循环依赖(可能是由使用'桶' index.ts 文件引起的
- android - 如何实现网络通话,当用户在另一个通话中并且他的通话不受干扰
- java - 在序列化中追加
- tesseract - 在 Pytesseract 中保留字间距
- progressive-web-apps - pwa 提示同一域的多个路径
- javascript - 有没有办法将小写字符与 discord.js 中的大写字符一样处理?
- left-join - AWS AppSync Graphql 等效于左连接查询
- flutter - 有没有办法免费翻译我的颤振应用程序?
- json - 使用 JSON 值查询 SQL 数据库