Udostępnij za pośrednictwem


Hide and seek

Another interesting question from StackOverflow. That thing is a gold mine for blog topics. Consider the following:

class B
{
public int X() { return 123; }
}
class D : B
{
new protected int X() { return 456; }
}
class E : D
{
public int Y() { return X(); } // returns 456
}
class P
{
public static void Main()
{
D d = new D();
Console.WriteLine(d.X());
  }
}

There are two possible behaviours here. We could resolve X to be B.X and compile successfully, or resolve X to be D.X and give a "you can't access a protected method of D from inside class Program" error.

[UPDATE: I've clarified this portion of the text to address questions from the comments. Thanks for the good questions.]

We do the former.To compute the set of possible resolutions of name lookup,  the spec says"the set consists of all accessible members named N in T, including inherited members" but D.X is not accessible from outside of D; it's protected. So D.X is not in the accessible set.

The spec then says "members that are hidden by other members are removed from the set". Is B.X hidden by anything? It certainly appears to be hidden by D.X. Well, let's check. The spec says "A declaration of a new member hides an inherited member only within the scope of the new member. " The declaration of D.X is only hiding B.X within its scope: the body of D and the bodies of declarations of types derived from D. Since P is neither of those, D.X is not hiding B.X there, so B.X is visible, so that's the one we choose.

Inside E, D.X is accessible and hides B.X, so D.X is in the set and B.X is not.

What's the justification for this choice? Doesn't this conflict with our rule that methods in more derived classes are better than methods in base classes?

No, it doesn't. Remember, the rule about prefering derived to base methods is to mitigate the brittle base class problem. So is this rule! Consider this brittle base class scenario:

FooCorp makes Foo.DLL:

public class Foo
{
public object Blah() { ... }
}

BarCorp makes Bar.DLL:

public class Bar : Foo
{
  // stuff not having to do with Blah
}

 ABCCorp makes ABC.EXE:

 public class ABC
{
static void Main()
{
Console.WriteLine((new Bar()).Blah());
}
}

Now BarCorp says "You know, in our internal code we can guarantee that Blah only ever returns string thanks to our knowledge of our derived implementation. Let's take advantage of that fact in our internal code."

public class Bar : Foo
{
internal new string Blah()
{
object r = base.Blah();
Debug.Assert(r is string);
return (string)r;
  }
}

ABCCorp picks up a new version of Bar.DLL which has a bunch of bug fixes that are blocking them. Should their build break because they have a call to Blah, an internal method on Bar? Of course not. That would be terrible. This change is a private implementation detail that should be invisible outside of Bar.DLL. The fact that hiding methods are ignored outside of their scopes means that they can be safely used for internal implementation details without breaking downstream users.

 

(Eric is in Oslo at NDC; this posting was prerecorded.)

Comments

  • Anonymous
    June 14, 2010
    The specification says "A declaration of a new member hides an inherited member only within the scope of the new member.", but says nothing about the lower accessibility. if we replace protected to public  class D : B { new public int X() }   D.X won't hide  B.X in the main method? Excellent question. The way I originally wrote this article implied what you stated, which of course is incorrect. I've clarified the text. The relevant portion of the spec is in the member lookup section. - Eric

  • Anonymous
    June 14, 2010
    Yes it does. The new member is public so any call to it is within it's scope. You are confusing scope with accessibility domain. The scope of an entity is the region of program text in which it is legal to refer to the entity by an unqualified name; a public field of a class is in scope only inside the class. Anywhere else you have to qualify its name to access it. The accessibility domain of a public member of a public class is everywhere; the accessibility domain is the region of program text where it is legal to refer to the entity by qualified or unqualified name. The problem was that I was not calling out the specific part of the spec that dealt with the relationship between member lookup and hidden members. I've clarified the text.  - Eric

  • Anonymous
    June 15, 2010
    Good to learn something new. I didn't realize freeborn's question had a catch to it, as D.X obviously does hide B.X in that case. I get lost with some of these terms in english (not my native language). Do you have any blog where you explain what we are exactly referring to when we talk about scope, domains, declaration spaces, etc?

  • Anonymous
    June 15, 2010
    So what is the recommended way for a subclass to hide a function from the base class? A public implementation that throws an exception when called?

  • Anonymous
    June 15, 2010
    The comment has been removed

  • Anonymous
    June 15, 2010
    If hiding only happens within the scope of a member (which Main is outside of), then if D.X were public, why does B.X appear to be hidden in Main? Is it only because of the betterness algorithm?

  • Anonymous
    June 16, 2010
    oReally here is something about scope, declaration space and lifetime.: blogs.msdn.com/.../what-s-the-difference-part-two-scope-vs-declaration-space-vs-lifetime.aspx

  • Anonymous
    June 18, 2010
    "So what is the recommended way for a subclass to hide a function from the base class? A public implementation that throws an exception when called?" I have no idea if this is a recommended way (I doubt it is) but here goes. Use "new" to hide the method. In you new method throw the error. Then apply the EditorBrowsableAttribute on the method to hide it from intellisense. If it is not in intellisense then it does not exist right =). You do not get a compile time error but this may get you 90% of the way there.