What's the real reason for preventing protected member access through a base/sibling class?
问题
I recently discovered that a method in a derived class can only access the base class's protected instance members through an instance of the derived class (or one of its subclasses):
class Base
{
protected virtual void Member() { }
}
class MyDerived : Base
{
// error CS1540
void Test(Base b) { b.Member(); }
// error CS1540
void Test(YourDerived yd) { yd.Member(); }
// OK
void Test(MyDerived md) { md.Member(); }
// OK
void Test(MySuperDerived msd) { msd.Member(); }
}
class MySuperDerived : MyDerived { }
class YourDerived : Base { }
I managed to work around this restriction by adding a static method to the base class, since Base's methods are allowed to access Base.Member, and MyDerived can call that static method.
I still don't understand the reason for this limitation, though. I've seen a couple different explanations, but they fail to explain why MyDerived.Test() is still allowed to access MySuperDerived.Member.
The Principled Explanation: 'Protected' means it's only accessible to that class and its subclasses. YourDerived could override Member(), creating a new method that should only be accessible to YourDerived and its subclasses. MyDerived can't call the overridden yd.Member() because it's not a subclass of YourDerived, and it can't call b.Member() because b might actually be an instance of YourDerived.
OK, but then why can MyDerived call msd.Member()? MySuperDerived could override Member(), and that override should only be accessible to MySuperDerived and its subclasses, right?
You don't really know until runtime whether you're calling an overridden member or not. And when the member is a field, it can't be overridden anyway, but access is still forbidden.
The Pragmatic Explanation: Other classes might add invariants that your class doesn't know about, and you must use their public interface so they can maintain those invariants. If MyDerived could directly access protected members of YourDerived, it could break those invariants.
My same objection applies here. MyDerived doesn't know what invariants MySuperDerived might add, either -- it might be defined in a different assembly by a different author -- so why can MyDerived access its protected members directly?
I get the impression that this compile-time limitation exists as a misguided attempt to solve a problem that can really only be solved at runtime. But maybe I'm missing something. Does anyone have an example of a problem that would be caused by letting MyDerived access Base's protected members through a variable of type YourDerived or Base, but does not exist already when accessing them through a variable of type MyDerived or MySuperDerived?
--
UPDATE: I know the compiler is just following the language specification; what I want to know is the purpose of that part of the spec. An ideal answer would be like, "If MyDerived could call YourDerived.Member(), $NIGHTMARE would happen, but that can't happen when calling MySuperDerived.Member() because $ITSALLGOOD."
回答
UPDATE: This question was the subject of my blog in January 2010. Thanks for the great question! See:
Does anyone have an example of a problem that would be caused by letting MyDerived access Base's protected members through a variable of type YourDerived or Base, but does not exist already when accessing them through a variable of type MyDerived or MySuperDerived?
I am rather confused by your question but I am willing to give it a shot.
If I understand it correctly, your question is in two parts. First, what attack mitigation justifies the restriction on calling protected methods through a less-derived type? Second, why does the same justification not motivate preventing calls to protected methods on equally-derived or more-derived types?
The first part is straightforward:
// Good.dll:
public abstract class BankAccount
{
abstract protected void DoTransfer(BankAccount destinationAccount, User authorizedUser, decimal amount);
}
public abstract class SecureBankAccount : BankAccount
{
protected readonly int accountNumber;
public SecureBankAccount(int accountNumber)
{
this.accountNumber = accountNumber;
}
public void Transfer(BankAccount destinationAccount, User user, decimal amount)
{
if (!Authorized(user, accountNumber)) throw something;
this.DoTransfer(destinationAccount, user, amount);
}
}
public sealed class SwissBankAccount : SecureBankAccount
{
public SwissBankAccount(int accountNumber) : base(accountNumber) {}
override protected void DoTransfer(BankAccount destinationAccount, User authorizedUser, decimal amount)
{
// Code to transfer money from a Swiss bank account here.
// This code can assume that authorizedUser is authorized.
// We are guaranteed this because SwissBankAccount is sealed, and
// all callers must go through public version of Transfer from base
// class SecureBankAccount.
}
}
// Evil.exe:
class HostileBankAccount : BankAccount
{
override protected void Transfer(BankAccount destinationAccount, User authorizedUser, decimal amount) { }
public static void Main()
{
User drEvil = new