Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Why can't subclasses create new objects with a base class protected constructor?

I'm porting some Java code to C#, and I ran into this idiom used to copy objects:

class Base
{
    int x;
    public Base(int x) { this.x = x; }
    protected Base(Base other) { x = other.x; }
}

class Derived : Base
{
    Base foo;
    public Derived(Derived other)
        : base(other)
    {
        foo = new Base(other.foo); // Error CS1540
    }
}

Error CS1540 being:

Cannot access protected member 'Base.Base(Base)' via a qualifier of type 'Base'; the qualifier must be of type 'Derived' (or derived from it)

I understand the purpose of this error: it prevents accessing protected members of sibling types. But Base.Base(Base) is obviously not going to be invoked on a sibling type! Is this simply not included in the spec, or am I missing some reason why this would not be safe?

EDIT: Gah, the idiom was new Base(other.foo) not new Base(other)

like image 917
Simon Buchan Avatar asked Feb 02 '23 17:02

Simon Buchan


2 Answers

Sections 3.5.2 and 3.5.3 of the language specification spell this out, I'll post 3.5.2 for convenience (it's shorter!) and let you find 3.5.3 on your own.

In intuitive terms, when a type or member M is accessed, the following steps are evaluated to ensure that the access is permitted:

  • First, if M is declared within a type (as opposed to a compilation unit or a namespace), a compile-time error occurs if that type is not accessible.
  • Then, if M is public, the access is permitted.
  • Otherwise, if M is protected internal, the access is permitted if it occurs within the program in which M is declared, or if it occurs within a class derived from the class in which M is declared and takes place through the derived class type (§3.5.3).
  • Otherwise, if M is protected, the access is permitted if it occurs within the class in which M is declared, or if it occurs within a class derived from the class in which M is declared and takes place through the derived class type (§3.5.3).
  • Otherwise, if M is internal, the access is permitted if it occurs within the program in which M is declared.
  • Otherwise, if M is private, the access is permitted if it occurs within the type in which M is declared.
  • Otherwise, the type or member is inaccessible, and a compile-time error occurs.

Basically, access to protected members of the base have to be through instances of the derived. As you said, siblings cannot access each others' protected members, but the language specification also prohibits children from accessing protected members of the base unless the reference is through the child.

like image 164
Anthony Pegram Avatar answered Feb 06 '23 15:02

Anthony Pegram


I you could do this, then you could always trivially invoke protected members of any class that allowed you to derive from it, even though the derived class is not used. This totally subverts the security of the protected mechanism.

For example, let's say we derive from Base with Derived as above. If the rules weren't as they are, you could add a method like this to Derived:

public static void CallProtectedMethod(Base baseInstance)
{
    baseInstance.ProtectedMethod();
}

and now anyone can invoke it like this:

Derived.CallProtectedMethod(baseInstance);

while the direct approach fails:

baseInstance.ProtectedMethod();

In this case baseInstance might really be of type Base and have nothing to do with Derived. To prevent this and ensure that protected methods stay protected unless the instance really is of the Derived type, calling these methods through a different instance is illegal.

Likewise for protected constructors:

public static Base CreateProtectedBase()
{
    return new Base();
}

and now anyone can invoke it like this:

var baseInstance = Derived.CreateProtectedBase();

while the direct approach fails:

var baseInstance = new Base();
like image 26
Rick Sladkey Avatar answered Feb 06 '23 16:02

Rick Sladkey