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.