简体   繁体   中英

Why does C# implicitly convert to nullable DateTime if there is no implicit operator for nullable DateTime?

This is a question about the C# language or at least how that language is implemented in Visual Studio.

Assume one has a class Foo which defines an implicit operator to System.DateTime

public static implicit operator DateTime(Foo item)

Consider the following code:

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

What I would expect: A failure-to-compile complaining that there is no conversion from Foo to DateTime? .

What I find: The compiler actually calls the defined implicit operator from Foo to DateTime and crashes when it is passed a null (which is the only way the converter can respond to a null).

Of course, work-around is to define

public static implicit operator DateTime?(Foo item)

but why do I have to do this? Are not DateTime and DateTime? two different types?

First off, the C# language specification says that a built-in implicit conversion can be inserted on either side of a user-defined implicit conversion. So if you had a user-defined implicit conversion from Shape to Giraffe then you are automatically allowed to convert from Square to Mammal , because it goes Square --> Shape --> Giraffe --> Mammal . In your case the extra conversion is only inserted on one side, presuming that the operand is of type Foo . There is an implicit conversion from any type to its corresponding nullable type. A second user defined conversion is never inserted; only built in conversions can be inserted on either side.

(I note that this is true of user-defined explicit conversions as well; explicit conversions may be inserted on either side.)

Second, you are in violation of the specification, which strongly suggests that a user-defined implicit conversion should never throw an exception. If you cannot guarantee that the operation will succeed then either change the types or make it an explicit conversion.

Third, you might be interested to know that the C# compiler will automatically define a "lifted" conversion if both types in a user-defined implicit conversion are non-nullable value types. If you have a user-defined implicit conversion from struct type S to struct type T then you get a "lifted" conversion from S? to T? for free, with the semantics of s.HasValue ? new T?((T)s.Value) : new T?() s.HasValue ? new T?((T)s.Value) : new T?() .

This subject is one of the more complex areas in the C# specification so I recommend that you read it carefully if you wish to know the exact details.

DateTime? is actually a C# syntactic sugar to represent Nullable<DateTime> . When you write something like DateTime? dtFoo = foo DateTime? dtFoo = foo the compiler actually generate code like:

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

Which is perfectly fine from compiler point of view as the nullable constructor take a DateTime as param and foo has type conversion for it.

When you have this:

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

The compiler sees that there is an implicit conversion from Foo to DateTime , and an implicit conversion from DateTime to DateTime? . It decides it can thus implicitly convert from Foo to DateTime? and quite happily compiles.

An implicit operator should not throw an exception. The fact that you are violating this is why you are seeing confusing behavior. You should probably change that to an explicit operator, and optionally add an implicit conversion to DateTime? that returns null in the case of a null Foo .

This is valid for the same reason you can do the following:

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

A normal DateTime can be transformed into a nullable DateTime . This means that your Foo class, which acts like a DateTime , can follow the path Foo -> DateTime -> DateTime?

I guess DateTime? (or any other null-ed class) has an implicit operator to its normal type.

First of all, remember that T? (when T 'sa struct) actually means Nullable<T> .

Now, let's make a short test program that replicates this:

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

And now see what it compiles to (decompiled using the great 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);
  }
}

In fact, dotPeek is being nice to me here: it should be the same, but with DateTime? nullable = new DateTime?((DateTime) new Program()); DateTime? nullable = new DateTime?((DateTime) new Program()); changed into Nullable<DateTime> nullable = new Nullable<DateTime>((DateTime) new Program());

As you can see, implicit conversions are just turned into explicit ones by the compiler.

What I'm guessing happens is that the compiler first thinks [1] that you can't make Foo a DateTime? , as a) it's not a reference type, and b) it's not a DateTime . So then it might go through some of its implicit operators [2] , and finds one that matches DateTime . And then it realizes [1] that you can convert a DateTime to a Nullable<DateTime> , so it then does it.

:)

[1] : Not really thinks, but don't be pedantic.
[2] : Eric Lippert has a comment about this, that explains it.

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM