I think I understand why this small C# console application will not compile:
using System; namespace ConsoleApp1 { class Program { static void WriteFullName(Type t) { Console.WriteLine(t.FullName); } static void Main(string[] args) { WriteFullName(System.Text.Encoding); } } }
The compiler raises a CS0119 error: 'Encoding' is a type which is not valid in the given context
. I know that I can produce a Type object from its name by using the typeof()
operator:
... static void Main(string[] args) { WriteFullName(typeof(System.Text.Encoding)); } ...
And everything works as expected.
But to me, that use of typeof()
has always seemed somewhat redundant. 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?
Or maybe the compiler is perfectly capable of taking that step, but it has been chosen not to because of the problems that might generate. Would that result in any ambiguity/legibility issues that I cannot think of right now?
An implicit dynamic conversion exists from an expression of type dynamic to any type T .
The typeof is an operator keyword which is used to get a type at the compile-time. Or in other words, this operator is used to get the System. Type object for a type. This operator takes the Type itself as an argument and returns the marked type of the argument.
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 ifC
is aType
or the typeC
. 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() { M(Color.Red); N(Color.ToString()); } }
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.
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With