Why isn’t there an implicit typeof?

If the compiler knows that some token is a reference to a given type (as error CS0119 suggests) and it knows that the destination of some assignment (be it a function parameter, a variable or whatever) expects a reference to a given type, why can’t the compiler take it as an implicit typeof() call?

First off, your proposal is that the compiler reason both “inside to outside” and “outside to inside” at the same time. That is, in order to make your proposed feature work the compiler must both deduce that the expression System.Text.Encoding refers to a type and that the context — a call to WriteFullName — requires a type. How do we know that the context requires a type? The resolution of WriteFullName requires overload resolution because there could be a hundred of them, and maybe only one of them takes a Type as an argument in that position.

So now we must design overload resolution to recognize this specific case. Overload resolution is hard enough already. Now consider the implications on type inference as well.

C# is designed so that in the vast majority of cases you do not need to do bidirectional inference because bidirectional inference is expensive and difficult. The place where we do use bidirectional inference is lambdas, and it took me the better part of a year to implement and test it. Getting context-sensitive inference on lambdas was a key feature that was necessary to make LINQ work and so it was worth the extremely high burden of getting bidirectional inference right.

Moreover: why is Type special? It’s perfectly legal to say object x = typeof(T); so shouldn’t object x = int; be legal in your proposal? Suppose a type C has a user-defined implicit conversion from Type to C; shouldn’t C c = string; be legal?

But let’s leave that aside for a moment and consider the other merits of your proposal. For example, what do you propose to do about this?

class C {
  public static string FullName = "Hello";
Type c = C;
Console.WriteLine(c.FullName); // "C"
Console.WriteLine(C.FullName); // "Hello"

Does it not strike you as bizarre that c == C but c.FullName != C.FullName ? A basic principle of programming language design is that you can stuff an expression into a variable and the value of the variable behaves like the expression, but that is not at all true here.

Your proposal is basically that every expression that refers to a type has a different behaviour depending on whether it is used or assigned, and that is super confusing.

Now, you might say, well, let’s make a special syntax to disambiguate situations where the type is used from situations where the type is mentioned, and there is such a syntax. It is typeof(T)! If we want to treat T.FullName as T being Type we say typeof(T).FullName and if we want to treat T as being a qualifier in a lookup we say T.FullName, and now we have cleanly disambiguated these cases without having to do any bidirectional inference.

Basically, the fundamental problem is that types are not first class in C#. There are things you can do with types that you can only do at compile time. There’s no:

Type t = b ? C : D;
List<t> l = new List<t>();

where l is either List<C> or List<D> depending on the value of b. Since types are very special expressions, and specifically are expressions that have no value at runtime they need to have some special syntax that calls out when they are being used as a value.

Finally, there is also an argument to be made about likely correctness. If a developer writes Foo(Bar.Blah) and Bar.Blah is a type, odds are pretty good they’ve made a mistake and thought that Bar.Blah was an expression that resolves to a value. Odds are not good that they intended to pass a Type to Foo.

Follow up question:

why is it possible with method groups when passed to a delegate argument? Is it because usage and mentioning of a method are easier to distinguish?

Method groups do not have members; you never say:

class C { public void M() {} }
var x = C.M.Whatever;

because C.M doesn’t have any members at all. So that problem disappears. We never say “well, C.M is convertible to Action and Action has a method Invoke so let’s allow C.M.Invoke(). That just doesn’t happen. Again, method groups are not first class values. Only after they are converted to delegates do they become first class values.

Basically, method groups are treated as expressions that have a value but no type, and then the convertibility rules determine what method groups are convertible to what delegate types.

Now, if you were going to make the argument that a method group ought to be convertible implicitly to MethodInfo and used in any context where a MethodInfo was expected, then we’d have to consider the merits of that. There has been a proposal for decades to make an infoof operator (pronounced “in-foof” of course!) that would return a MethodInfo when given a method group and a PropertyInfo when given a property and so on, and that proposal has always failed as too much design work for too little benefit. nameof was the cheap-to-implement version that got done.

A question you did not ask but which seems germane:

You said that C.FullName could be ambiguous because it would be unclear if C is a Type or the type C. Are there other similar ambiguities in C#?

Yes! Consider:

enum Color { Red }
class C {
  public Color Color { get; private set; }
  public void M(Color c) { }
  public void N(String s) { }
  public void O() {

In this scenario, cleverly called the “Color Color Problem”, the C# compiler manages to figure out that Color in the call to M means the type, and that in the call to N, it means this.Color. Do a search in the specification on “Color Color” and you’ll find the rule, or see blog post Color Color.

Leave a Comment