This project is read-only.

Lowest common supertype in conditional expression

Topics: C# Language Design
Oct 7, 2014 at 2:04 PM
In a conditional expression b ? x : y, the last two operands determine the type of the whole expression. Essentially the procedure consists in taking the type of x and checking whether y is implicitly convertible to it, or viceversa, otherwise a compile-time error occurs.

This implies that the following expression does not compile:
b ? new ArgumentException() : new NullReferenceException()
even if it could (should) reasonably compile, with type SystemException, which is the lowest (most specific) common supertype of the type of the last two operands.
The only way to have such expression compile is casting any of the two expressions to the desired supertype.

A lowest common supertype (LCS) always exists (at least object) and using the LCS to determine the type of a conditional expression is indeed a generalization of the current specification.

This tecnique can be usefully applied to any kind of type, including nullable types, interfaces and dynamic types:
b ? 42 : null // int?
b ? "hello" : 3.14 // "intersection type" IConvertible and IComparable
b ? dynamicObject : otherObject // dynamic
Oct 7, 2014 at 2:29 PM
Yep, I also find this annoying occasionally.
Oct 7, 2014 at 4:36 PM
Given
interface Intf1a { void foo(); };
interface Intf1b { void moo(); };
interface Intf2a: Intf1a, Intf1b {};
interface Intf2b: Intf1a, Intf1b {};
Intf2a i2a;
Intf2b i2b;
What should be the type of boolExp ? i2a : i2b? How should the compiler evaluate (boolExp ? i2a : i2b).foo() or (boolExp ? i2a : i2b).moo()? As it happens, Java is able to handle such situations by defining the result as type Intf2a & Intf2b, but the .NET type system has type to which every implementation of i2a and i2b may be cast, and which supports both Intf1a.foo() and Intf1b.moo(). Allowing the compiler to infer whether it should use Intf1a or Intf1b based upon how the result of the expression is used would be a nice approach, but it would be contrary to the "traditional" inside-out type evaluation semantics which have been widely used since ancient times; I'm not sure I would see a basis for using outside-in type evaluation here given that C# doesn't do so in many other cases where it would be more helpful (e.g. d1 = f1+f2+f3; or l1=i1*i2;).

I think it would be great if .NET included generic structural composite interface types, such that any type which implemented or derived from T and U would also be deemed to implement GenericStructuralCompositeType<T,U>, which would be deemed to implement or derive from from T and U, and if type GenericCompositeIntersectionType<V,W> were deemed to implement all possible GenericStructuralCompositeType<T,U> which could be satisfied by both V and W. In that case, the compiler would be able to infer a least common type for the two arguments to the conditional operator, and have it behave properly as an intersection type. Unfortunately, unless or until such a feature is added to the Runtime, there's no way a compiler could use it.

PS--While it might be possible to have a compiler permit the indicated style of operation in cases where no ambiguity exists, it such usage became popular, then modifying a class to implement another interface would often be a breaking change.