gpt4 book ai didi

c# - 阻止 protected 成员通过基类/同级类访问的真正原因是什么?

转载 作者:可可西里 更新时间:2023-11-01 07:53:39 25 4
gpt4 key购买 nike

我最近发现,派生类中的方法只能通过派生类(或其子类之一)的实例访问基类的 protected 实例成员:

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 { }

我设法通过在基类中添加静态方法来解决此限制,因为允许Base的方法访问Base.Member,而MyDerived可以调用该静态方法。

不过,我仍然不明白此限制的原因。我已经看到了几种不同的解释,但是它们无法解释为什么仍然允许MyDerived.Test()访问MySuperDerived.Member。

原则上的解释:“ protected ”意味着该类及其子类只能访问它。 YourDerived 可以覆盖Member(),从而创建一个只能由YourDerived及其子类访问的新方法。 MyDerived无法调用重写的yd.Member(),因为它不是YourDerived的子类,并且它不能调用b.Member(),因为b可能实际上是YourDerived的实例。

可以,但是为什么MyDerived可以调用msd.Member()? MySuperDerived可以重写Member(),并且该重写只能由MySuperDerived及其子类访问,对吗?

直到运行时,您才真正知道是否要调用重写的成员。当成员是字段时,无论如何都不能覆盖它,但是仍然禁止访问。

务实的解释:其他类可能会添加您的类不知道的不变量,因此您必须使用它们的公共(public)接口(interface),以便它们可以维护这些不变量。如果MyDerived可以直接访问YourDerived的 protected 成员,则可能破坏这些不变式。

我的反对意见也适用于此。 MyDerived也不知道MySuperDerived可能添加哪些不变式-它可能是由不同的作者在不同的程序集中定义的-那么为什么MyDerived可以直接访问其 protected 成员?

我得到的印象是,这种编译时限制是为解决实际上只能在运行时解决的问题而进行的错误尝试。但是也许我想念一些东西。有没有人举过一个问题示例,该问题可能是由于MyDerived通过类型为YourDerived或Base的变量访问Base的 protected 成员而导致的,但通过类型MyDerived或MySuperDerived的变量访问它们时还不存在?

-

更新:我知道编译器只是在遵循语言规范。我想知道的是规范那部分的目的。一个理想的答案将是:“如果MyDerived可以调用YourDerived.Member(),则将发生$ NIGHTMARE,但在调用MySuperDerived.Member()时则不会发生,因为$ ITSALLGOOD。”

最佳答案

更新:这个问题是我2010年1月博客的主题。非常感谢您提出这个问题!看到:

https://blogs.msdn.microsoft.com/ericlippert/2010/01/14/why-cant-i-access-a-protected-member-from-a-derived-class-part-six/

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?



您的问题让我很困惑,但我愿意试一试。

如果我正确理解,您的问题分为两部分。首先,哪种攻击缓解措施可证明通过较少派生的类型调用 protected 方法的限制是合理的?其次,为什么相同的理由没有动机阻止在同等或更多衍生的类型上调用 protected 方法?

第一部分很简单:
// 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.
}
}

Evil博士试图从您的瑞士银行帐户中窃取一...百万...美元...已被C#编译器挫败。

显然,这是一个愚蠢的示例,显然,完全信任的代码可以对您的类型进行任何操作-完全信任的代码可以启动调试器并在运行时更改代码。完全信任意味着完全信任。实际上,不要以这种方式设计真正的安全系统!

但是我的观点很简单,就是在这里阻止的“攻击”是有人试图绕过SecureBankAccount设置的不变量进行最终运行,以直接访问SwissBankAccount中的代码。

我希望这回答了您的第一个问题。如果不清楚,请告诉我。

您的第二个问题是“为什么SecureBankAccount也没有此限制?”在我的示例中,SecureBankAccount说:
    this.DoTransfer(destinationAccount, user, amount);

显然,“this”是SecureBankAccount类型或其他派生类型。它可以是更多派生类型的任何值,包括新的SwissBankAccount。 SecureBankAccount是否可以围绕SwissBankAccount的不变量进行最终运行?

是的,一点没错!因此,SwissBankAccount的作者必须了解其基类所做的一切!你不能只是从一些意志薄弱的类(Class)中衍生而来,并希望获得最好的!基类的实现允许调用基类公开的一组 protected 方法。如果要从中派生,则需要阅读该类的文档或代码,并了解在什么情况下将调用 protected 方法,并相应地编写代码。派生是共享实现细节的一种方式。如果您不了解所衍生事物的实现细节,则不要从中衍生出来。

此外,基类始终在派生类之前编写。基类并没有改变,您可能会相信,您相信该类的作者不会尝试用以后的版本来偷偷摸摸地打扰您。 (当然,对基类的更改总是会引起问题;这是脆弱的基类问题的又一个版本。)

两种情况之间的区别在于,当您从基类派生时,您可以选择一种类别的行为来理解和信任。那是一项艰巨的工作。 SwissBankAccount的作者必须在调用 protected 方法之前准确了解SecureBankAccount保证不变的内容。但是他们不必理解和信任恰好是从同一基类派生的每个表亲类的每个可能行为。那些人可以被任何人实现,无所不能。您将完全无法理解它们的任何预调用不变式,因此,您将无法成功编写有效的 protected 方法。因此,我们为您节省了麻烦并禁止了这种情况。

此外,我们还必须允许您在派生可能更多的类的接收者上调用 protected 方法。假设我们不允许这样做,并且得出一些荒谬的结论。如果我们不允许在可能派生更多的类的接收者上调用 protected 方法,那么在什么情况下可以调用 protected 方法?在那个世界上,唯一一次可以调用 protected 方法的情况是,如果您正在从密封类中调用自己的 protected 方法!实际上,几乎永远不会调用 protected 方法,被调用的实现将始终是派生最多的实现。在这种情况下,“ protected ”有什么意义?您的“ protected ”与“私有(private),和只能从密封类中调用”是相同的意思。那将使它们变得不太有用。

因此,这两个问题的简短答案是“因为如果我们不这样做,根本就不可能使用 protected 方法。”我们通过较少派生的类型来限制调用,因为如果不这样做,就不可能安全地实现任何依赖于不变式的 protected 方法。我们允许通过潜在的子类型进行调用,因为如果我们不允许这样做,那么根本就不允许任何调用。

这能回答您的问题吗?

关于c# - 阻止 protected 成员通过基类/同级类访问的真正原因是什么?,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/1904782/

25 4 0
Copyright 2021 - 2024 cfsdn All Rights Reserved 蜀ICP备2022000587号
广告合作:1813099741@qq.com 6ren.com