阻止受保护成员通过基类/同级类访问的真正原因是什么? [英] What's the real reason for preventing protected member access through a base/sibling class?

查看:65
本文介绍了阻止受保护成员通过基类/同级类访问的真正原因是什么?的处理方法,对大家解决问题具有一定的参考价值,需要的朋友们下面随着小编来一起学习吧!

问题描述

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

  class基础
{
受保护的虚拟void Member(){}
}

class MyDerived:基础
{
//错误CS1540
void Test(Base b){b.Member(); }
//错误CS1540
void Test(YourDerived yd){yd.Member(); }

// OK
void Test(MyDerived md){md.Member(); }
//确定
void Test(MySuperDerived msd){msd.Member(); }
}

class MySuperDerived:MyDerived {}

class YourDerived:Base {}

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

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



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



确定,但是为什么MyDerived可以调用msd.Member()? MySuperDerived可以覆盖Member(),并且该覆盖仅应由MySuperDerived及其子类访问,对吧?



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



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



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



我的印象是,这种编译时限制是为解决实际上只能在运行时解决的问题而进行的错误尝试。但是也许我想念一些东西。是否有人通过MyDerived通过类型为YourDerived或Base的变量访问Base的受保护成员而引起的问题的示例,但是通过类型为MyDerived或MyDerived的变量访问它们时已经存在MySuperDerived?



-



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

解决方案

更新:这个问题是我2010年1月博客的主题。感谢您提出的伟大问题!请参阅:



https://blogs.msdn.microsoft.com/ericlippert/2010/01/14/why-cant-我从派生类的第六部分访问受保护的成员/






< blockquote>

有没有人举过一个
问题的例子,该问题是由
导致MyDerived通过类型为YourDerived的变量
访问Base的
受保护成员的或基本,但是通过类型MyDerived
或MySuperDerived访问变量
时,
是否不存在?


我对您的问题感到困惑,但我愿意试一试。



如果我正确理解,您的问题分为两部分。首先,什么样的缓解攻击措施可以证明通过较少派生的类型调用受保护方法的限制是合理的?其次,为什么相同的理由不能激发阻止对同等派生或更多派生类型的受保护方法的调用?



第一部分很简单:

  // Good.dll:

公共抽象类BankAccount
{
抽象protected void DoTransfer (BankAccount destinationAccount,用户授权用户,十进制数);
}

公共抽象类SecureBankAccount:BankAccount
{
保护的只读int accountNumber;
public SecureBankAccount(int accountNumber)
{
this.accountNumber = accountNumber;
}
公共无效转账(BankAccount destinationAccount,用户用户,十进制金额)
{
if(!Authorized(user,accountNumber))扔东西;
this.DoTransfer(destinationAccount,用户,金额);
}
}

公开密封类SwissBankAccount:SecureBankAccount
{
public SwissBankAccount(int accountNumber):base(accountNumber){}
覆盖受保护的void DoTransfer(BankAccount destinationAccount,用户authorizedUser,十进制金额)
{
//在此处从瑞士银行帐户转帐的代码。
//此代码可以假定authorizedUser已被授权。

//我们得到保证,因为SwissBankAccount是密封的,并且
//所有呼叫者都必须通过基本版本
// SecureBankAccount的公用版本的Transfer。
}
}

// Evil.exe:

class HostileBankAccount:BankAccount
{
覆盖protected void Transfer( BankAccount destinationAccount,用户authorizedUser,十进制数){}

public static void Main()
{
User drEvil = new User( Dr。Evil);
您的BankAccount =新的SwissBankAccount(1234567);
BankAccount矿=新的SwissBankAccount(66666666);
yours.DoTransfer(mine,drEvil,1000000.00m); //编译错误
//您无权仅由于您处于从BankAccount派生的
类型而访问
// SwissBankAccount的受保护成员。
}
}

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



很显然,这是一个愚蠢的示例,显然,完全信任的代码可以对您的类型做任何想做的事-完全信任的代码可以启动调试器并进行更改该代码作为其运行。完全信任表示完全信任。实际上,不要以这种方式设计真正的安全系统!



但是我的观点很简单,就是挫败了这里的攻击是有人试图绕过SecureBankAccount设置的不变式进行最终运行,以访问代码



我希望这回答了您的第一个问题。



您的第二个问题是:为什么SecureBankAccount也没有此限制?在我的示例中,SecureBankAccount表示:

  this.DoTransfer(destinationAccount,用户,金额); 

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



是的,绝对可以!因此,需要 SwissBankAccount的作者了解其基类所做的一切!您不能只是从一些意志薄弱的班级衍生而来,并希望获得最好的!基类的实现允许调用基类公开的受保护方法集。如果要从中派生,则需要阅读该类的文档或代码,并了解在什么情况下将调用受保护的方法,并相应地编写代码。 派生是共享实施细节的一种方式;如果您不了解所衍生事物的实现细节,则不要衍生自它。



此外,基类是总是写在派生类的之前。基础类还没有建立起来,并在不断变化,您大概可以相信该类的作者不要试图在以后的版本中偷偷摸摸地打扰您。 (当然,对基类的更改总是会引起问题;这是脆性基类问题的另一个版本。)



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



此外,我们具有功能,使您可以在派生可能更多的类的接收者上调用受保护的方法。假设我们不允许这样做,并且得出一些荒谬的结论。如果我们不允许在可能派生更多的类的接收者上调用受保护的方法,在什么情况下可以调用受保护的方法 ever ?在那个世界上,唯一一次可以调用受保护方法的情况是,如果要从密封类中调用自己的受保护方法!实际上,几乎不可能永远调用受保护的方法,被调用的实现将始终是派生最多的实现。在这种情况下,受保护有什么意义?您的受保护与私有,并且只能从密封类中调用含义相同。



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



可以吗回答您的问题?


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:

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?

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

Dr. Evil's attempt to steal ONE... MILLION... DOLLARS... from your swiss bank account has been foiled by the C# compiler.

Obviously this is a silly example, and obviously, fully-trusted code could do anything it wants to your types -- fully-trusted code can start up a debugger and change the code as its running. Full trust means full trust. Don't actually design a real security system this way!

But my point is simply that the "attack" that is foiled here is someone attempting to do an end-run around the invariants set up by SecureBankAccount, to access the code in SwissBankAccount directly.

That answers your first question, I hope. If that's not clear, let me know.

Your second question is "Why doesn't SecureBankAccount also have this restriction?" In my example, SecureBankAccount says:

    this.DoTransfer(destinationAccount, user, amount);

Clearly "this" is of type SecureBankAccount or something more derived. It could be any value of a more derived type, including a new SwissBankAccount. Couldn't SecureBankAccount be doing an end-run around SwissBankAccount's invariants?

Yes, absolutely! And because of that, the authors of SwissBankAccount are required to understand everything that their base class does! You can't just go deriving from some class willy-nilly and hope for the best! The implementation of your base class is allowed to call the set of protected methods exposed by the base class. If you want to derive from it then you are required to read the documentation for that class, or the code, and understand under what circumstances your protected methods will be called, and write your code accordingly. Derivation is a way of sharing implementation details; if you don't understand the implementation details of the thing you are deriving from then don't derive from it.

And besides, the base class is always written before the derived class. The base class isn't up and changing on you, and presumably you trust the author of the class to not attempt to break you sneakily with a future version. (Of course, a change to a base class can always cause problems; this is yet another version of the brittle base class problem.)

The difference between the two cases is that when you derive from a base class, you have the behaviour of one class of your choice to understand and trust. That is a tractable amount of work. The authors of SwissBankAccount are required to precisely understand what SecureBankAccount guarantees to be invariant before the protected method is called. But they should not have to understand and trust every possible behaviour of every possible cousin class that just happens to be derived from the same base class. Those guys could be implemented by anyone and do anything. You would have no ability whatsoever to understand any of their pre-call invariants, and therefore you would have no ability to successfully write a working protected method. Therefore, we save you that bother and disallow that scenario.

And besides, we have to allow you to call protected methods on receievers of potentially more-derived classes. Suppose we didn't allow that and deduce something absurd. Under what circumstances could a protected method ever be called, if we disallowed calling protected methods on receivers of potentially-more-derived classes? The only time you could ever call a protected method in that world is if you were calling your own protected method from a sealed class! Effectively, protected methods could almost never be called, and the implementation that was called would always be the most derived one. What's the point of "protected" in that case? Your "protected" means the same thing as "private, and can only be called from a sealed class". That would make them rather less useful.

So, the short answer to both your questions is "because if we didn't do that, it would be impossible to use protected methods at all." We restrict calls through less-derivedtypes because if we don't, it's impossible to safely implement any protected method that depends on an invariant. We allow calls through potential subtypes because if we do not allow this, then we don't allow hardly any calls at all.

Does that answer your questions?

这篇关于阻止受保护成员通过基类/同级类访问的真正原因是什么?的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持IT屋!

查看全文
登录 关闭
扫码关注1秒登录
发送“验证码”获取 | 15天全站免登陆