13

这是关于 C# 语言的问题,或者至少是关于如何在 Visual Studio 中实现该语言的问题。

假设一个类 Foo 定义了一个隐式运算符到 System.DateTime

public static implicit operator DateTime(Foo item)

考虑以下代码:

Foo foo = SomeMethodWhichCanReturnNull();    
DateTime?  dtFoo = foo;

的期望:编译失败,抱怨没有从Footo转换DateTime?

我发现:编译器实际上调用了定义的隐式运算符 from FootoDateTime并在传递 null 时崩溃(这是转换器可以响应 null 的唯一方法)。

当然,解决方法是定义

public static implicit operator DateTime?(Foo item)

但为什么我必须这样做?是不是DateTimeDateTime?两种不同的类型?

4

5 回答 5

13

首先,C# 语言规范说可以在用户定义的隐式转换的任一侧插入内置的隐式转换。因此,如果您有一个用户定义的隐式转换 from ShapetoGiraffe那么您将被自动允许转换 from Squareto Mammal,因为它是Square --> Shape --> Giraffe --> Mammal. 在您的情况下,额外的转换仅插入一侧,假设操作数是 type Foo。从任何类型到其对应的可空类型都有隐式转换。永远不会插入第二个用户定义的转换;只能在任一侧插入内置转换。

(我注意到用户定义的显式转换也是如此;显式转换可以插入到任一侧。)

其次,您违反了规范,该规范强烈建议用户定义的隐式转换永远不应引发异常。如果您不能保证操作会成功,则更改类型或使其成为显式转换。

第三,您可能想知道如果用户定义的隐式转换中的两种类型都是不可为空的值类型,C# 编译器将自动定义“解除”转换。如果您有一个用户定义的从结构类型 S 到结构类型 T 的隐式转换,那么您会从 S 获得“提升”转换?到 T? 免费,具有s.HasValue ? new T?((T)s.Value) : new T?().

本主题是 C# 规范中较为复杂的领域之一,因此如果您想了解确切的详细信息,我建议您仔细阅读。

于 2013-05-31T17:29:50.963 回答
7

DateTime?实际上是一个 C# 语法糖来表示Nullable<DateTime>. 当您编写类似DateTime? dtFoo = foo编译器的内容时​​,实际上会生成如下代码:

Nullable<DateTime> dtFoo = new Nullable<DateTime>(foo);

从编译器的角度来看,这非常好,因为可为空的构造函数将 aDateTime作为参数并foo对其进行类型转换。

于 2013-05-31T17:16:25.820 回答
2

当你有这个:

Foo foo = SomeMethodWhichCanReturnNull();    
DateTime? dtFoo = foo;

编译器看到存在从 to 的隐式转换和从toFooDateTime隐式转换。它决定它因此可以隐式转换 from to并且非常愉快地编译。DateTimeDateTime?FooDateTime?

implicit操作员不应抛出异常。您违反这一点的事实就是您看到令人困惑的行为的原因。您可能应该将其更改为运算符explicit,并可以选择在.implicitDateTime?nullnull Foo

于 2013-05-31T17:16:11.893 回答
0

这与您可以执行以下操作的原因相同:

DateTime time1 = new DateTime();
DateTime? time2 = time1;

法线DateTime可以转换为nullable DateTime. 这意味着你的Foo类,就像 a 一样DateTime,可以遵循路径Foo -> DateTime -> DateTime?

我猜DateTime?(或任何其他 null 类)对其普通类型有一个隐式运算符。

于 2013-05-31T17:15:36.513 回答
0

首先,请记住T?(when T'sa struct) 实际上意味着Nullable<T>.

现在,让我们编写一个简短的测试程序来复制它:

using System;
class Program
{
    public static void Main(string[] args)
    {
        DateTime? x = new Program();
        Console.ReadKey(true);
    }
    public static implicit operator DateTime(Program x)
    {
        return new DateTime();
    }
}

现在看看它编译成什么(使用伟大的 dotPeek 反编译):

using System;
internal class Program
{
  public Program()
  {
    base..ctor();
  }

  public static implicit operator DateTime(Program x)
  {
    return new DateTime();
  }

  public static void Main(string[] args)
  {
    DateTime? nullable = new DateTime?((DateTime) new Program()); 
    Console.ReadKey(true);
  }
}

事实上,dotPeek 在这里对我很好:它应该是一样的,但DateTime? nullable = new DateTime?((DateTime) new Program());改为Nullable<DateTime> nullable = new Nullable<DateTime>((DateTime) new Program());

如您所见,编译器implicit只是将转换转换为转换。explicit

我猜发生的是编译器首先认为[1]你不能制作Fooa DateTime?,因为 a)它不是引用类型,并且 b)它不是 a DateTime。那么它可能会通过它的一些隐式运算符[2],并找到一个匹配DateTime的。然后它意识到[1]你可以将 a 转换DateTime为 a Nullable<DateTime>,所以它会这样做。

:)

[1] : 不是很想,但不要迂腐。
[2] : Eric Lippert 对此有评论,解释了这一点。

于 2013-05-31T17:23:16.937 回答