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.

 

  • kevinowen : I think you missed the part where I said "outside the assembly" adding another named attribute to it that details this exceptional visibility is just fine e.g.

    UnlessWithin.Assembly | UnlessWithin.SubClasses

    It's rather harder to implement the sub classes one since working out whether you are writing code within that context is surprisingly hard (inner classes for example) though removing from the list of possibilities suggested when intellisense sees override is pretty easy.

    The assembly one is much easier since the context of which file -> which assembly is sufficiently well defined and 1-1 that dealing with it shouldn't be too hard.

    I'm with Eric though, much as the occasional mental disconnect on it annoys me it is so not the end of the world compared to plenty of other new languages features like dynamic blocks.

  • Eric> The verbose language...

    It depends on what you understand by "access modifiers" - I was only including the private/public/etc. Apologies if readonly, sealed, virtual etc count in terms of access - I didn't check my use of vocabulary :(

    However, I'd also like to include sealed/virtual for type declarations as well. ("Virtual" doesn't actually feel right - unsealed would perhaps be better.)

    In other words, for people who usually seal classes anyway, and explicitly declare private members and internal types, there'd be little change.

  • No, Jon, you are right -- the others are not access modifiers. My point was that I was going to make a language where you had to specify EVERYTHING, not just access. It would not have been a useful language, but it would have been unambiguous. :-)

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

    There's nothing to be gained from sealed by default, since you can't break someone else's code by deriving from his class. This is quite different from the case for non-virtual by default.

  • Re: classes not sealed by default. There was a video a few years ago with some senior person at MS, it might have been Don Box but I don't remember for sure. He addressed this very issue. He also thought that ideally, classes should be sealed by default. But doing so would have created a big stumbling block fpr developers coming from C++, Java, etc. Remember, .Net was new at the time and MS wanted to make it as easy as possible for devs to start using C#.

  • > There's nothing to be gained from sealed by default, since you can't break someone else's code by deriving from his class.

    You most certainly can! Extending a security-sensitive class to make a hostile subtype and then tricking some consumer into thinking that your object may be used safely as an instance of the benign base type is a common technique for subverting a system with poor controls on inheritance.

  • > Have you not been getting enough sleep lately?

    >> Yes, I have. Have you?

    Not by a long shot :)

    >> or do you always respond to trivial differences of opinion about technical matters with sarcastic, cutting and insulting remarks?

    No, not generally. But there is something about your blog that brings the sarcasm out in me. I honestly didn't mean to insult anyone though.

    >> You get NO additional security by going to "protected and internal" because your coworker can simply write a subclass to party on the member.

    If you are referring to code access security in partially trusted environments, then you are correct, there is no benefit. However, that is not really the point; obviously we are not talking about code access security within an assembly. And you are assuming that arbitrary subclasses can be injected into the object graph at any point. In most well-designed class hierarchies this is not the case.

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

    Essentially, you are correct. We do not have the luxury of perfect project documentation or perfect cross-team communication (oh how I wish...). The best way we have of communicating the intent of our code is the structure of the code itself. A well-designed class hierarchy screams out exactly how it should be used. I believe Krzysztof has talked about this many times. Restricting members to their minimum required accessibility and no more is the most direct way possible of indicating the desired usage of a member. I don't want to restrict a member to "protected and internal" to STOP my coworker from using it; I want to do it to indicate to him that he shouldn't, even when I'm not around to tell him so explicitly.

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

    Good, I totally agree that this is a minor issue which should take a backseat to the many more important features and bug fixes which the community has been requesting. I just wasn't clear whether you were saying that you thought it *shouldn't* be done, or that it *wouldn't* be done.

  • James: The main problem with using a protected method with an internal constructor is that sometimes you want to use internal types as parameters to your protected method. If your class is public, that is not a possibility. So far, the best approach I've found is to make the class internal and expose a public interface.

  • I was one of the peolpe who had always thought that protected internal would work to make things more restrictive. I never sat back and thought about how private is default so it would as you would say loosen restrictions.

    I disagree with your thought that there is no need for a "Proternal" since your trying to protect your own co-workers. One case where this comes into play is when a project enters maintneance mode and the original developers are no longer there. Something marked as "Proternal" helps to clearify the intent that only derived classes within this assembly should be using this method.

    On the same token I'd rather see other cooler language enhancements then this feature which would only be applicable a small percentage of time.

    I agree on sealing but I like that classes are not sealed by default. If developers had to consiously unseal thigns, then I am going to guess that a lot of the controls which I like to derive would be sealed and I'd end up having to use some sort of delegation.

    Nice Post;-)

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

    Ahem.

    double d = 5.0;

    int i = d;

    Why isn't the above allowed? You can simply write

    int i = (int)d;

    The whole point is to force you to cast it, so that you have to think if that's what you want. Same thing with writing a subclass to allow access to a "protected AND internal" member - it means that you thought about it and decided that yes, you really really want access to that member.

    "Not a high enough priority" is a good argument. "Not useful" is a lousy one.

  • Rather than place the links to the most recent C# team content directly in Community Convergence , I

  • Customer feedback is right on this one. 'protected internal' should be FamilyAndAssembly.

    The main paractical effect of these attributes is to give compiler errors when someone does something wrong. So do I want to give a compiler error to my coworker who is not derived from my class? Yes.

    You can argue that it also affects CAS and verification, and that is a very powerful argument. But by the measure of security, the C# team got the decision wrong. Because nearly everyone adding 'protected internal' is expecting it to be restrictive, and is surprised to learn of the contrary *if* they check their assumptions. So C# has undermined (not helped) CAS by making it more likely for developers to introduce vulnerabilities in their code.

    I would cut some slack based on design goals though. Because C# was clearly built to work well (better!) for developers with Java and C++ backgrounds. In Java is there a "package protected" (I honestly have no clue)? In C++ there aren't really sassemblies as such, but can 'friend' access 'portected' members (since they can access privates, I assume so).

    To put it a different way, who does the current 'protected internal' help? It only helps non-subclassed code in the assembly have access to protected members. This is trivially possible (and arguably better modeled) by adding a regular internal method anyway. Maybe Java "protected package" did soemthing like this and it was a competitive response? I really don't understand how this made the bar to get into the language spec as is.

    Honestly, from reading your article it sounds like there was some champion who really believed the 'access modifiers are a loosening of the private constraint' and no one else cared enough to argue back strongly.  

  • I don't entirely agree with your analysis of people's perception. I don't think people conceive of "internal", "protected" and "private" as restrictions because they think of "public" as some sort of "natural" state. I think they conceive of them as restrictions because the words themselves emphasise that they are a restriction; especially "protected" and "internal". They both sound much more like "stop someone from doing something" than "allow someone to do something".

    I have no idea what they could have been called instead, but this explains why the "wrong" intuition is so common.

  • It's a common belief that you cannot make some members both protected AND internal.

    And its true that you cannot do so in a single line, as many, including myself, would wish, but with some cleverness it is 100% do-able. I'll Let my (hopefully) self-documenting code speak for itself.

    [START C# CODE]

    //Code below is 100% tested

    /* FROM ProtectedAndInternal.dll */

    namespace ProtectedAndInternal

    {

       public class MyServiceImplementationBase

       {

           protected static class RelevantStrings

           {

               internal static string AppName = "Kickin' Code";

               internal static string AppAuthor = "Scott Youngblut";

           }

       }

       public class MyServiceImplementation : MyServiceImplementationBase

       {

           public void PrintProperties()

           {

               // WORKS PERFECTLY BECAUSE SAME ASSEMBLY!

               Console.WriteLine(RelevantStrings.AppAuthor);

           }

       }

       public class NotMyServiceImplementation

       {

           public void PrintProperties()

           {

               // FAILS - NOT THE CORRECT INHERITANCE CHAIN

               // Error CS0122: 'ProtectedAndInternal.MyServiceImplementationBase.Relevant' is inaccessible due to its protection level

               // Console.WriteLine(MyServiceImplementationBase.RelevantStrings.AppAuthor);

           }

       }

    }

    /* From AlternateAssemblyService.dll which references ProtectedAndInternal.dll */

    namespace AlternateAssemblyService

    {

       public class MyServiceImplementation : MyServiceImplementationBase

       {

           public void PrintProperties()

           {

               // FAILS - NOT THE CORRECT ASSEMBLY

               // Error CS0117: 'ProtectedAndInternal.MyServiceImplementationBase.RelevantStrings' does not contain a definition for 'AppAuthor'

               // Console.WriteLine(RelevantStrings.AppAuthor);

           }

       }

    }

    [END C# CODE]

    [CopyRight - Scott Youngblut (MCPD)]

  • Forgot to mention that there is a way to make members virtual, I have never had a need to do it, but it does work using delegates, Function<T>, Action<T>, and poor-man's dependency injection (www.lostechies.com/.../how-not-to-do-dependency-injection-in-nerddinner.aspx)

    If you NEED the sample email me, its substantially longer than this snippet.

    var email = string.Concat( @"leat" + "hakkor", "@", "hot", "mail", @".", "com" );

    ^^ Take that spammers

    [Scott Youngblut (MCPD)]

Page 2 of 3 (32 items) 123