5

(The "user-defined" in the title refers to the fact that addition and subtraction of TimeSpan and DateTime are not a part of the C# standard. They are defined in the BCL.)

Playing around with lifted operators on nullable TimeSpan and DateTime values, I wrote the following code. Note that the framework offers different operations on TimeSpan and DateTime.

There's one symmetrical (and commutative) addition where you take in two TimeSpan and return the sum TimeSpan. The "inverse" of this addition is the subtraction of two TimeSpan yielding a TimeSpan.

Then there's another kind of addition, asymmetrical, where you take one DateTime (left operand) and one TimeSpan (right operand) to produce a DateTime. Because of the asymmetry of this operation, it has two "kinds" of inverses: One where you subtract two DateTime from each other to get the TimeSpan difference, and one where you have one DateTime and subtract from it one TimeSpan to produce a result DateTime.

static void Main()
{
  DateTime? n_dt = new DateTime(2012, 12, 25);
  TimeSpan? n_ts = TimeSpan.FromDays(62.0);

  var a = n_dt + n_ts;   // OK
  var b = n_ts + n_ts;   // OK

  var c = null + n_dt;   // OK, string concatenation! Type of expression is String
  var d = null + n_ts;   // OK, compiler prefers TS+TS, not DT+TS
  var e = n_dt + null;   // OK, DT+TS
  var f = n_ts + null;   // OK, TS+TS
  var g = null + null;   // error, type of expression is undetermined

  var h = n_dt - n_dt;   // OK
  var i = n_dt - n_ts;   // OK
  var j = n_ts - n_ts;   // OK

  var k = null - n_dt;   // OK, DT-DT
  var l = null - n_ts;   // compiler prefers TS-TS, not DT-TS
  var m = n_dt - null;   // error, compiler won't choose between DT-DT amd DT-TS, type of expression is undetermined
  var n = n_ts - null;   // OK, TS-TS
  var o = null - null;   // OK, integer subtraction! Type of expression is Nullable<Int32>

  // illegal:
//var p = n_dt + n_dt;
//var q = n_ts + n_dt;
//var r = n_ts - n_dt;
}

Some questions arise naturally.

It's a bit strange that o is allowed and gives an int? (why not a long? by the way?) while g is disallowed. Is this in the spec? Also, it's a little strange that the "impossible" c is resolved by string concatenation. Apparently the compiler decides that the null in c is a (string)null. Adding an expression of explicit type object to a DateTime, on the other hand, will not compile.

But my main question is: Why can the compiler choose an overload for d and l, but with m it complains about ambiguity?

Jeppe Stig Nielsen
  • 60,409
  • 11
  • 110
  • 181
  • 3
    By-far the weirdest thing here is var o = null - null; – Dave Bish Oct 25 '12 at 16:24
  • `c` uses string concatenation because language operators are preferred over user defined operators, and that expression matches the string concatenation operator of `operator +(string, object)`. That of course raises the question of `d` though, as by that logic that should also resolve to string concatenation. – Servy Oct 25 '12 at 16:25
  • @DaveBish Agreed. I'd assume it would be ambigous between int/long – Servy Oct 25 '12 at 16:26
  • 1
    @DaveBish Ah, found the answer. Section 7.3.6 of the C# specs; it determines how, when given two numeric types, it determines which operator to use. `int` has the highest precedence on the list. – Servy Oct 25 '12 at 16:34
  • @Servy - The inference to int? still feels pretty weird!! – Dave Bish Oct 25 '12 at 16:37
  • @Servy and I suppose that `null + null` is ambiguous between `(int?)null + (int?)null` and `(string)null + (object)null`, while no such ambiguity exists for the binary `-` operator. – phoog Oct 25 '12 at 16:39
  • @phoog That's exactly right, and since the string operator isn't numeric it isn't subject to 7.3.6 for resolution. – Servy Oct 25 '12 at 16:41

1 Answers1

0

The reason seems to be that with m, the two possible operations are defined inside the same type, namely System.DateTime. There's no way to choose between them.

On the other hand, with d and l, one operation is defined in System.TimeSpan, and the other one is defined in System.DateTime. But in the lines of d and l, we see TimeSpan, but there's no mention of any DateTime type what so ever in the assignments of d and l. It seems like the compiler then only searches through operators defined in the type System.TimeSpan, and forgets to search through user-defined operators defined in all other types (which would by the way be very many types to search). In that way, during the resolution of d and l, the compiler never discovers the operators defined inside the DateTime type.

Jeppe Stig Nielsen
  • 60,409
  • 11
  • 110
  • 181
  • 1
    See section 7.3.4 of the C# spec, as it appears to be relevant and to confirm this answer. – Servy Oct 25 '12 at 16:32