更新:这个问题是我 2010 年 1 月博客的主题。感谢您提出的好问题!看:
https://blogs.msdn.microsoft.com/ericlippert/2010/01/14/why-cant-i-access-a-protected-member-from-a-derived-class-part-six/
有没有人举例说明让 MyDerived 通过 YourDerived 或 Base 类型的变量访问 Base 的受保护成员而导致的问题,但在通过 MyDerived 或 MySuperDerived 类型的变量访问它们时不存在?
我对你的问题感到很困惑,但我愿意试一试。
如果我理解正确,您的问题分为两部分。首先,什么样的攻击缓解可以证明通过较少派生类型调用受保护方法的限制是合理的?其次,为什么同样的理由不能阻止对同样派生或更多派生类型的受保护方法的调用?
第一部分很简单:
// 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 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.
}
}
邪恶博士试图从您的瑞士银行账户中窃取一……百万……美元……已被 C# 编译器阻止。
显然这是一个愚蠢的例子,而且很明显,完全受信任的代码可以对您的类型做任何事情——完全受信任的代码可以启动调试器并在代码运行时更改代码。完全信任意味着完全信任。不要以这种方式设计真正的安全系统!
但我的观点很简单,这里被挫败的“攻击”是有人试图围绕 SecureBankAccount 设置的不变量进行最终运行,以直接访问 SwissBankAccount 中的代码。
我希望这回答了你的第一个问题。如果不清楚,请告诉我。
您的第二个问题是“为什么 SecureBankAccount 也没有这个限制?” 在我的示例中,SecureBankAccount 说:
this.DoTransfer(destinationAccount, user, amount);
显然,“this”属于 SecureBankAccount 类型或更多派生的类型。它可以是更多派生类型的任何值,包括新的 SwissBankAccount。SecureBankAccount 不能围绕 SwissBankAccount 的不变量进行最终运行吗?
是的,一点没错!正因为如此,SwissBankAccount 的作者必须了解他们的基类所做的一切!你不能随便从某个班级派生并希望最好!允许基类的实现调用基类公开的一组受保护方法。如果您想从中派生,则需要阅读该类的文档或代码,并了解在什么情况下将调用受保护的方法,并相应地编写代码。推导是一种共享实现细节的方式;如果您不了解您所派生的事物的实现细节,请不要从中派生。
此外,基类总是写在派生类之前。基类并没有在你身上发生变化,大概你相信类的作者不会试图用未来的版本偷偷地破坏你。(当然,对基类的更改总是会导致问题;这是脆弱基类问题的另一个版本。)
这两种情况的区别在于,当您从基类派生时,您可以理解和信任您选择的一个类的行为。这是一个易于处理的工作量。SwissBankAccount 的作者需要在调用受保护的方法之前准确理解 SecureBankAccount 保证不变的内容。但他们不应该理解和信任每一个可能的表亲阶级的每一个可能的行为这恰好是从同一个基类派生的。这些家伙可以由任何人实施并做任何事情。您将无法理解它们的任何调用前不变量,因此您将无法成功编写工作受保护的方法。因此,我们为您省去了麻烦并禁止这种情况。
此外,我们必须允许您在可能更多派生类的接收者上调用受保护的方法。假设我们不允许这样做并推断出一些荒谬的东西。如果我们不允许在可能派生更多的类的接收者上调用受保护的方法,在什么情况下可以调用受保护的方法?在那个世界中,您唯一可以调用受保护方法的情况是,如果您从密封类中调用自己的受保护方法!实际上,受保护的方法几乎永远不会被调用,并且被调用的实现总是最派生的。在那种情况下,“受保护”有什么意义?您的“受保护”与“私有,只能从密封类中调用”的含义相同。那将使它们变得不那么有用。
因此,对您的两个问题的简短回答是“因为如果我们不这样做,就根本不可能使用受保护的方法。” 我们通过较少派生类型限制调用,因为如果我们不这样做,就不可能安全地实现任何依赖于不变量的受保护方法。我们允许通过潜在子类型进行调用,因为如果我们不允许这样做,那么我们根本不允许任何调用。
这能回答你的问题吗?