Why Can't I Access A Protected Member From A Derived Class, Part Three

Why Can't I Access A Protected Member From A Derived Class, Part Three

Rate This
  • Comments 32

Holy goodness, I've been busy. The MVP Summit was fabulous for us; thanks to all who attended and gave us great feedback on our ideas for evolving the languages and tools. And I've been doing some longer-lead thinking and working on language futures, which I will blog about at a later date.

Last time I promised another oddity of "protected" semantics. An issue that I get asked about all the time involves the meaning of this code:

namespace Foo {
  public class Base {
    protected internal void M() { ... }
  }
}

Many people believe that this means " M is accessible to all derived classes that are in this assembly." It does not. It actually means "M is accessible to all derived classes and to all classes in this assembly".  That is, it is the less restrictive combination, not the more restrictive combination.

This is counterintuitive to a lot of people. I have been trying to figure out why, and I think I've got it. I think people conceive of internal, protected and private as restrictions from the "natural" state of public. With that model, protected internal means "apply both the protected restriction and the internal restriction".

That's the wrong way to think about it. Rather, internal, protected and public are weakenings of the "natural" state of private. private is the default in C#; if you want to make something have broader accessibility, you've got to say so. With that model, then it becomes clear that protected internal is a weaker restriction than either alone.

(As an aside: An irksome fact about the design of C# is that we do not consistently default to the more restricted mode. I like that private is the default visibility for most members and "instance", not "virtual" is the default method behaviour. But why aren't classes sealed by default? This would emphasize that participation in an inheritance hierarchy needs to be part of the design of a class.)

We get the feature request fairly frequently to add the "more restricted" form, but the thing is, I'm not sure what use it actually is. If the member is already marked as internal then the only people who can use it are your coworkers. What is the harm in allowing them to party on an internal member from outside the class hierarchy?

Of course, if we did add the feature, the codegen would be trivial; this kind of accessibility is already supported natively in the CLR. The work would come in defining a sensible syntax for it. protected with internal or protected and internal might work. Or, we could define a new keyword having this meaning. proternal, for example. Or intected. (The former sounds very positive; the latter, like bad news from a dentist.)

Long story short, I would not expect this feature any time soon.

Next time, some thoughts on your comments to my last entry.

 

  • Probably because most qualifiers combine as "more restrictive" - When you write "readonly volatile" you expect that attempts to write will fail - not that "Well, yeah, but the volatile rules don't forbid writing, and those are the rules I'm choosing to follow instead of the readonly rules."

    Also because the word "internal" carries the semantics of a restriction - if the keywords were called "derivedvisible" and "filevisible" then maybe it would be seen more as a relaxation. "It's derivedvisible and fileviisible, whichever works for you. It's a floor wax and a dessert topping."

  • Proternal either sounds like the name of a dotcom-era web company or a term for somebody in favour of same-sex adoptions.

    I think you should borrow from the accounting field and go with "dependent".  That's really what this is - a child living in the same household.  No in-laws or great-great grandkids whom you've never met allowed.

  • With all the admiration I have for the design of C#, I think this is one that Anders & Co. got totally wrong.

    First of all, why should I trust my coworkers? As a policy, I don't even trust myself! If I make something as restrictive as possible, then if later I want to open it up, I actually have to think about why I restricted it in the first place. I really don't want to be swapping bodily fluids with every class in the same assembly just because we always compile together.

    Also, if you use protected internal and virtual together, you've effectively made it public, because you can't override without exposing it to every class in the same assembly with the derived class. When I have a virtual member that needs to be accessible within my assembly, I have to make two members, one protected virtual and one internal.

    I find I'm constantly in the situation in which I do not want a member visible outside of my assembly and the only classes inside my assembly that have any business with the member are derived classes. So I'm stuck with either being less restrictive than necessary or, if I can, I make it private and nest all my derived classes.

    It's rare that I need protected or internal. In those rare cases, I would have been happy to create one protected and one internal member, which is a very easy way to create the less restrictive case when the language only supports the more restrictive case. When the language only supports the less restrictive case, it's very hard to create the more restrictive case.

    My suggestion would be to make an attribute that could be put on protected members to make them protected and internal or one that could be put on internal members to make them protected and internal, whichever one you guys think would be more usable. Making it an attribute instead of a language feature would be easier for everybody.

  • I've heard people ask why there isn't such a combination before as well, but I don't get it. I can't really imagine a situation where there's any point in allowing code in other assemblies to derive from a class, but to restrict the API accessible to external code, compared to the one you expose to internal derived classes. You're asking to create two tiers of subclasses - those outside the baseclass's assembly that have a restricted view of their parent, and those inside that can access more of their parent's features. If these members are useful to internal subclasses, why aren't they useful to external ones too? If it's possible to develop fully functional subclasses of your baseclass without accessing your 'proternal' members, then why do your subclasses need them? If it's not possible, then why are you allowing classes outside your assembly to derive from your baseclass at all?

    What people who ask for this restriction probably really want is to restrict deriving from their class to their own assembly, preventing external code from subclassing, which can be done easily enough with an internal constructor. Then any protected members are automatically also restricted to classes in your own assembly too - protected AND internal, just like they asked.

  • There are a number of places in C# where we use an attribute to connote some language feature -- "out" vs "ref", for example, or consuming extension methods.

    That said, first, we are reluctant to add more of the same, and second, this would be a really weird one. The attribute "protected and internal" is a built-in attribute, first class in the metadata. Surfacing that with the syntax of a user-defined attribute would be weird.

  • I heartily agree with MKane. I have learnt from experience that it is good and robust practice to expose as little as possible eve within my assembly. It is nothing to do with trusting coworkers. It is about making code easier to maintain and improve and resistant to bugs. Even my own bugs working elsewhere in the code. One might as well argue that all private methods can be internal as I trust my coworkers. Also, it is even more important to not expose external interface without conscious reason.

    Yet due to C# not exposing this modifier I am forced to use internal -- to prevent exposing arbitrary external interface with all its doc, useability, versioning, compat burden -- when I really just want protected semantics. Now 300 other classes have to see this member.

    The word you use to expose this is a trivial issue. Call it "protectedinternal" (all one word) if you like. Call it or "FamANDAssem" like ILDASM does. Call it zachary.

    Danmose

  • Perhaps a reminder to oneself and ones coworkers that would be sufficient would be an attribute which stopped to stop intellisense showing it outside the assembly?

    This would likely have the desired affect...

  • An attribute to stop intellisense from showing it already exists: System.ComponentModel.EditorBrowsableAttribute(EditorBrowsableState.Never)

  • It's counter-intuitive because modifiers on members are more restrictive. The most obvious example is:

    private readonly int x;

    It's not private OR readonly. It's private AND readonly.

  • In MSIL, we have internal or protected and internal and protected:

    Private, FamANDAssem, Assembly (internal), Family (protected), FamORAssem (protected internal) or Public

  • "But why aren't classes sealed by default?"

    Would you need to think before hand if someone in future will need to derive from it or not?

  • Tanveer: You *should* think before hand whether or not someone will need to derive from it. If they will, you need to think about what implementation decisions to document. For instance, every time your implementation calls a virtual method, that needs to be documented - otherwise an override of the virtual method might decide to call the original method, leading to a stack overflow.

    Inheritance is very useful, but only where designed. In order to use it robustly, you're pretty much forced to leak implementation details, which also limits future refactoring.

    To cut a long story short, I'm very much with Eric on this one.

    (I'm on the fence as to whether it's appropriate to have defaults for various things at all. What if there were no default access modifiers - if you *had* to declare the access for every member and type?)

  • @Eric, re:"What is the harm in allowing them to party on an internal member from outside the class hierarchy?"

    Are you serious? Have you not been getting enough sleep lately? msbuild is spot on: if that were a valid argument, why even have private members, why not make everything internal? "I really don't want to be swapping bodily fluids with every class in the same assembly just because we always compile together." I can't imagine a better way to describe it.

    I think your analysis as to why the meaning of protected internal is counterintuitive to most people is unsound. Think of it this way. Private may be the default state for all members, but accessibility modifiers came into being specifically to enable the object-oriented concept of data hiding, or restricting access to data, as opposed to old C and other functional code where any non-local (i.e. global) variable was fair game to anyone who wanted to use it. It is therefore not surprising that most people tend to think of accessibility modifiers as restrictive rather than relaxing.

    Add to that the fact that people generally think of keywords as adding restrictions from the natural state, because in C# they generally do. abstract must be overridden, sealed can't be inherited, const must be initialized inline, readonly can't be written outside the constructor, fixed can't be compacted, out parameters must be initialized prior to returning from the method, volatile can't be optimized; virtual is pretty much the only keyword other than the accessibility modifiers which follows the relaxing rather than the restricting pattern.

    Regardless, its hard to argue that "protected or internal" is more useful than "protected and internal". I cannot remember a single instance in implement an object hierarchy when I have used protected or internal; but I can remember numerous instances when I could have used protected and internal.

    Why do you not expect this feature any time soon? Just because it will be hard to come up with a keyword?

  • > Have you not been getting enough sleep lately?

    Yes, I have. Have you? or do you always respond to trivial differences of opinion about technical matters with sarcastic, cutting and insulting remarks?

    > if that were a valid argument, why even have private members, why not make everything internal?

    And now you're making a straw man attack by taking my remark out of context.

    Let me be more clear, since apparently I didn't get my point across. If the member is intended to be private, of course it should be private. If it is intended to be accessible to internal subclasses, then what is the harm of it being accessible to internal non-subclasses?  You get NO additional security by going to "protected and internal" because your coworker can simply write a subclass to party on the member. The only benefit you get is the enforcement of your desire to have this particular internal member restricted to use from a subclass -- it becomes a form of documentation.  I am in favour of self-documenting code, but in this case the benefit seems very small.

    None of that implies that we ought to throw "private" to the winds. And I have a hard time imaginging that the number of scenarios in which someone is designing a class that can be inherited one way by external code and another way by internal code is anything but a small number.

    > I think your analysis as to why the meaning of protected internal is counterintuitive to most people is unsound.

    I find it odd that you say that my argument is unsound, and then go on to restate my argument. My argument is that people find this counterintuitive because their incorrect intuition is that these keywords are restrictions, not relaxations. You say that's unsound, and then give a whole lot of perfectly good supporting analysis in favour of my argument.

    Any time someone tells me I'm wrong and then lists of a whole bunch of points supporting my position, I get very confused. Are you sure you think my argument is unsound? Perhaps I simply did not communicate it clearly enough.

    > Why do you not expect this feature any time soon? Just because it will be hard to come up with a keyword?

    No, because we have many, many, many other far higher priorities and limited staff and time in which to deliver on those priorities. Time spent doing trivial features that benefit a tiny number of scenarios is time taken away from more important features that deliver higher value to more customers.

  • > What if there were no default access modifiers

    I started designing such a language once, just for fun. Didn't get very far. I was going to call it "Verbose". You'd end up with declarations like:

    field foo: public instance nonvolatile readable writable System.Int32;

Page 1 of 3 (32 items) 123