c# - 为什么从 <T> 到 <U> 的隐式转换运算符接受 <T?>?

标签 c# generics nullable implicit-conversion value-type

这是一种我无法理解的奇怪行为。在我的示例中,我有一个类 Sample<T>和来自 T 的隐式转换运算符至 Sample<T> .

private class Sample<T>
{
   public readonly T Value;

   public Sample(T value)
   {
      Value = value;
   }

   public static implicit operator Sample<T>(T value) => new Sample<T>(value);
}

T 使用可空值类型时会出现问题例如 int? .

{
   int? a = 3;
   Sample<int> sampleA = a;
}

这里是关键部分:
在我看来这不应该编译因为 Sample<int>定义从 int 的转换至 Sample<int>但不是来自 int?Sample<int> . 但它编译并运行成功!(我的意思是调用转换运算符并将 3 分配给 readonly 字段。)

而且情况变得更糟。这里没有调用转换运算符并且 sampleB将设置为 null :

{
   int? b = null;
   Sample<int> sampleB = b;
}

一个很好的答案可能会分为两部分:

  1. 为什么第一个片段中的代码可以编译?
  2. 在这种情况下我可以阻止代码编译吗?

最佳答案

你可以看看编译器是如何降低这段代码的:

int? a = 3;
Sample<int> sampleA = a;

进入this :

int? nullable = 3;
int? nullable2 = nullable;
Sample<int> sample = nullable2.HasValue ? ((Sample<int>)nullable2.GetValueOrDefault()) : null;

因为 Sample<int>是一个类,它的实例可以被分配一个空值,并且使用这样一个隐式运算符,也可以分配一个可空对象的基础类型。所以这样的赋值是有效的:

int? a = 3;
int? b = null;
Sample<int> sampleA = a; 
Sample<int> sampleB = b;

如果Sample<int>将是 struct ,那当然会出错。

编辑: 那么为什么这可能呢?我在规范中找不到它,因为它是故意违反规范的,只是为了向后兼容而保留的。您可以在 code 中阅读相关信息:

DELIBERATE SPEC VIOLATION:
The native compiler allows for a "lifted" conversion even when the return type of the conversion not a non-nullable value type. For example, if we have a conversion from struct S to string, then a "lifted" conversion from S? to string is considered by the native compiler to exist, with the semantics of "s.HasValue ? (string)s.Value : (string)null". The Roslyn compiler perpetuates this error for the sake of backwards compatibility.

这个“错误”就是这样implemented在罗斯林:

Otherwise, if the return type of the conversion is a nullable value type, reference type or pointer type P, then we lower this as:

temp = operand
temp.HasValue ? op_Whatever(temp.GetValueOrDefault()) : default(P)

所以根据spec对于给定的用户定义转换运算符 T -> U存在一个提升运算符 T? -> U?其中 TU是不可为空的值类型。然而,这种逻辑也适用于 U 的转换运算符。由于上述原因是引用类型。

第 2 部分 如何防止代码在这种情况下编译?好吧,有办法。您可以专门为可空类型定义一个额外的隐式运算符,并使用属性 Obsolete 对其进行修饰。 .这将需要类型参数 T限于 struct :

public class Sample<T> where T : struct
{
    ...

    [Obsolete("Some error message", error: true)]
    public static implicit operator Sample<T>(T? value) => throw new NotImplementedException();
}

此运算符将被选为可空类型的第一个转换运算符,因为它更具体。

如果您不能做出这样的限制,您必须为每个值类型单独定义每个运算符(如果您真的确定您可以利用反射并使用模板生成代码):

[Obsolete("Some error message", error: true)]
public static implicit operator Sample<T>(int? value) => throw new NotImplementedException();

如果在代码中的任何地方引用都会出错:

Error CS0619 'Sample.implicit operator Sample(int?)' is obsolete: 'Some error message'

关于c# - 为什么从 <T> 到 <U> 的隐式转换运算符接受 <T?>?,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/50388010/

相关文章:

c# - Calendar.GetDayOfWeek() 可以返回 (DayOfWeek)7 吗?

c# - 使用 xsd :id in C# web service

c# - WPF中使用样式的透明按钮背景

将普通旧 CLR 对象序列化为 JSON 的 C# 代码

c# - 使用 LINQ 使搜索方法通用

C#10 可为空模式 : how to tell the compiler I set the non-nullable property in the constructor indirectly?

c# - 在通用提供程序中如何查找和编辑 T 的属性? (C#)

java - 编译器认为 Comparable 类型不是 Comparable

c# - 如何将字符串转换为运行时确定的可空类型?

c# - 模棱两可的调用匹配困惑