28

I just read this interesting article by Eric Lippert, Top 10 Worst C# Features. Near the end he states:

The rules for resolving names after the aforementioned colon are not well founded; you can end up in situations where you need to know what the base class is in order to determine what the base class is.

By colon he is referring to the inheritance operator (e.g. Dog : Animal).

What situation is Eric referring to? Can anyone provide a code sample?

ErikE
  • 48,881
  • 23
  • 151
  • 196
Ian Newson
  • 7,679
  • 2
  • 47
  • 80
  • 10
    Why not ask this in the comments for the article. You'd have a better chance of Eric seeing it and really only he can answer what he meant. There's also his [blog entry](http://ericlippert.com/2015/08/18/bottom-ten-list/) that links to that. – juharr Aug 19 '15 at 14:20
  • I guess this is an error, he might mean : you need to know what the base class is in order to determine what the **derived** class is. – Menelaos Vergis Aug 19 '15 at 14:20
  • 4
    @MenelaosVergis: I'm pretty sure it isn't an error. – SLaks Aug 19 '15 at 14:21
  • 1
    @MenelaosVergis: possibly, but i don't understand it either. If it wasn't a typo it is a contradiction. You can't (or it's pointless to) determine what you already know. – Tim Schmelter Aug 19 '15 at 14:21
  • 1
    Yes, it's contradiction, that's what it means that the algorithm isn't well-founded. – Jörg W Mittag Aug 19 '15 at 14:38
  • 1
    @juharr You're right, I just figured he might notice it here too, but I thought others might be able to answer also. I've added a comment to his blog pointing to this question. – Ian Newson Aug 19 '15 at 15:08
  • 1
    I'm voting to close this question as off-topic because this question cannot be unlikely to be answered accurately by anyone other than the original author of the article in question. – James Webster Aug 19 '15 at 15:23
  • 2
    @JamesWebster: That isn't really true; he's just asking how the base class can be ambiguous. – SLaks Aug 19 '15 at 16:40
  • @SLaks I stand by my vote, but that's exactly why votes are in place. If nobody else agrees with me, nothing will happen – James Webster Aug 19 '15 at 16:42
  • 1
    @JamesWebster: See actual answer. – SLaks Aug 19 '15 at 16:50

2 Answers2

24

This can happen in convoluted scenarios with generics, inheritance, and nested classes:

class Base<T> {
    public class Inner {}
}

class Derived : Base<Derived.Inner2> {
    public class Inner2 : Inner {}
}

Result

  • To determine Derived's base class, we need to bind Derived.Inner2.
  • To bind Derived.Inner2, we need to resolve the Inner symbol.
  • The Inner symbol is inherited from its containing scope's base class, so we need to determine Derived's base class again.
SLaks
  • 868,454
  • 176
  • 1,908
  • 1,964
  • 1
    Great answer. Lot of fun playing around with this code and `ToString`. – Ian Newson Aug 19 '15 at 21:24
  • @IanNewson: Thanks! TryRoslyn (see my link) is actually a more convenient way to see what's going on. – SLaks Aug 19 '15 at 21:25
  • Oh and this isn't directed at this answer's author in particular, but I don't see why Eric would consider this an issue? It's a distant edge case, and C# handles it OK. – Ian Newson Aug 19 '15 at 21:25
  • @IanNewson: Eric Lippert came from the compiler team. No matter how much of an edge case it is, the compiler still has to handle it. (and you can just imagine how annoying this would be to implement this) – SLaks Aug 19 '15 at 21:51
  • See http://source.roslyn.io/#Microsoft.CodeAnalysis.CSharp/Symbols/Source/SourceNamedTypeSymbol_Bases.cs for the Roslyn's implementation of this mess. – SLaks Aug 19 '15 at 21:55
  • 7
    @IanNewson: I spent many, many days finding a dozen different cases like this one and in some of them C# did *not* handle it OK, because the error messages were wrong or inconsistent, the wrong type was chosen, and so on. The problem is that the language specification itself is unclear; Mads and I tried to clarify it once but ended up adding breaking changes. We decided to not spend too much additional time on it because, as you correctly note, the scenarios which expose the not-well-founded-ness of the spec are very unrealistic. – Eric Lippert Aug 20 '15 at 12:30
  • @EricLippert: What were the ones that broke? – SLaks Aug 20 '15 at 12:41
  • 2
    @SLaks: I've been trying to find my notes on that. When (if!) I do, I'll write a blog post about them. I do recall that the broken cases typically involved situations where there were types nested three deep with generic constructions in the base classes, and the cycle detector would incorrectly detect that a type was being circularly defined. – Eric Lippert Aug 20 '15 at 13:06
18

SLaks gives a good answer; see my comments for some additional notes.

As I said in the comments I am looking for my old notes on this subject and if I find them, I'll write a blog. Here's a fun additional example. This program is legal. Are the meanings of N in the class declaration and the field declaration the same or different? If they are the same, what is a fully-qualified type expression for them? If they are different, why does the specification require that they be different?

public class N {}
public class B<T> 
{
    public class N {}
}

public class D : B<N> // base class
{
  N n;  // field
}

This illustrates the fundamental problem: name lookup requires that the base class is known, but the base class is looked up by name.

Now think about how interfaces work in the mix. Suppose class D also implements an interface IN, similarly nested in B and available globally. Does the interface lookup resolve to the base class or the global namespace? These are the sorts of questions that you have to resolve when you're writing a compiler.

Eric Lippert
  • 647,829
  • 179
  • 1,238
  • 2,067
  • Assuming a global namspace `Global`, `B` is fully qualified as `B`, and `N n;` refers to the nested class so is fully qualified as `B.N n;`. If you wanted the `N` in `class D : B` to be the nested `N`, you'd have to supply a type parameter, e.g. `class D : B.N>`. Of course `sometype` can be `B`, or even `B.N`, e.g. `public class D : B.N>>.N>>.N>.N>>.N>` and so on. – Galax Nov 25 '15 at 18:07
  • I copied and pasted to take that idea a lot further, and at around 700 nested levels of generics (which I would hope are more than enough for anybody...) I finally got the error `The User Diagnostic Analyzer 'Microsoft.CodeAnalysis.CSharp.Diagnostics.SimplifyTypeNames.CSharpSimplifyTypeNamesDiagnosticAnalyzer' threw an exception of type 'System.InsufficientExecutionStackException' with message 'Insufficient stack to continue executing the program safely. This can happen from having too many functions on the call stack or function on the stack using too much stack space.` – Galax Nov 25 '15 at 18:09
  • 1
    @Galax: Indeed, there used to be a tester on the C# compiler team who delighted in finding situations like that which would blow up the analyzer. In some cases we replaced the recursive algorithms with iterative ones, and in some we just let it blow up, assuming that such programs are unrealistic. – Eric Lippert Nov 25 '15 at 18:11
  • I was impressed that it let me go that far, some of the tooltips took a few seconds to generate but took up 1/4 of my monitor. I can remember crashing the Visual C++ IDE about 10 years ago doing much less offensive things with a couple of levels deep of nested templates. – Galax Nov 25 '15 at 18:20