There was some discussion at work about the line of code:
IEnumerable<object> xx = new List<string>();
This line of code didn’t work in the initial versions of C#, even those with generics, because of the need for variance in the IEnumerable interface. [Variance allows the compiler to relate the instantiations of IEnumerable<A> and IEnumerable<B> when the types A and B have a subtype relationship]
Of course, when you’re discussing this kind of thing, it’s important that you can talk about the parts of the C# language specification that justify the steps the compiler is going to be taking. I believed that the conversion was because of an implicit reference conversion in the specification [6.1.6 Implicit Reference Conversion], but, of course, it’s really hard to be certain that this is the rule the compiler is going to use and that there isn’t some other rule which is actually being applied.
So what do we do?
I remembered reading how the Roslyn compiler had been written with an aim of keeping it very close to the C# specification, so that it was easier to verify that the implementation was correct. The Roslyn source is available here on GitHub and it’s fairly easy to build the compiler if you have Visual Studio 2015 Update 3.
You can then write a simple source file containing the above code, and set the csc project as the startup project with the debug command line set to point to this file. The various conversions that annotate the semantic tree are listed in a ConversionKind enumeration and it is fairly easy to find uses of the ImplicitReference enumeration member to see where the annotation is added to the tree. This gave me a way to breakpoint and then look at the call stack to determine where I should set a breakpoint and start stepping. [This isn’t always trivial because the call stack doesn’t really tell you how you got to a certain point, but rather tells you where you are going to go when certain method calls finish. These concepts are sometimes different (for example in the case of tail calls)]
For our example code, the key point is that we find the implicit reference conversion used in the ConversionsBase.cs file where we see a call to the method HasAnyBaseInterfaceConversion with derivedType List<string> and baseType IEnumerable<object>. When we walk across the interfaces of the derivedType argument by calling the method d.AllInterfacesWithDefinitionUseSiteDiagnostics, we’ll enumerate across the type IEnumerable<string> and the compiler will check that it is variance converable to IEnumerable<object> in the call to HasInterfaceVarianceConversion .
At this point the call stack looks like this:
What did I learn from this exercise?
There is now a C# implementation of the specification, so it is actually possible to check that you understand the parts of the specification that make some code valid. No longer do we guess what a C++ implementation of the compiler is doing, but we can animate the specification by stepping through the C# code. From the parts of the code that I have read, I’m not sure that I’d completely agree that the code follows the specification (making it easy to map from one to the other), but having an open source implementation does mean you can search for terms that you see in the specification to help you narrow down the search.
There are loads of other parts of the specification that I want to understand in more detail, so this is definitely an exercise that I am going to repeat in the future.