Hide and seek

Hide and seek

Rate This
  • Comments 8

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.)

 

 

 

  • 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

  • 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

  • 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?

  • 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?

  • You cant really hide a method, if someone really wants to call the "hidden" method they will always be able to.

    Anyhow, IMHO if some class in you're inheritance chain needs to hide public inherited members then probably something is wrong with your class hierarchy to begin with. You should think more in a "has a" relationship than a "is a" type relationship in most of those cases.

  • 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?

  • 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

  • "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.

Page 1 of 1 (8 items)