Eric Lippert just blogged on this very topic.
The basic gist of it is to ensure that a class can "trust" the caller of a protected method. Classes that share a common base class--even if that common base defines the protected method--are essentially strangers in this regard.
Eric's example is based on the idea of a bank application. Rather than recreating his example, I'll just regurgitate it here:
// 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 authorizedUser,
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 User("Dr. Evil");
BankAccount yours = new SwissBankAccount(1234567);
BankAccount mine = new SwissBankAccount(66666666);
yours.DoTransfer(mine, drEvil, 1000000.00m); // compilation error
// You don't have the right to access the protected member of
// SwissBankAccount just because you are in a
// type derived from BankAccount.
}
}
While what you present seems like a no-brainer, if it were allowed to happen then the sort of shenanigans you see here would be possible. Right now you know that a protected method call either comes from your type (which you have control over) or from a class that you directly inherit from (which you know at the time that you compile). If it were opened to anyone that inherited from the declaring type, then you would never have the surety of knowing the types that can call your protected method.
While you're initializing your BaseClass
variable to an instance of your own class, the compiler only sees that the variable is of type BaseClass
, putting you outside of the circle of trust. The compiler doesn't analyze all of the assignment calls (or potential assignment calls) to determine if it's "safe".