18

最近、派生クラスのメソッドは、派生クラス (またはそのサブクラスの 1 つ) のインスタンスを介してのみ、基本クラスの保護されたインスタンス メンバーにアクセスできることを発見しました。

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 にアクセスできる理由を説明できていません。

原則的な説明: 「保護」とは、そのクラスとそのサブクラスのみがアクセスできることを意味します。YourDerivedMember() をオーバーライドして、YourDerived とそのサブクラスのみがアクセスできる新しいメソッドを作成できます。MyDerived は、オーバーライドされた yd.Member() を呼び出すことができません。これは、YourDerived のサブクラスではないためです。また、b が実際には YourDerived のインスタンスである可能性があるため、b.Member() を呼び出すこともできません。

では、なぜ MyDerived は msd.Member() を呼び出せるのでしょうか? MySuperDerived は Member() をオーバーライドできますが、そのオーバーライドは MySuperDerived とそのサブクラスのみがアクセスできるはずですよね?

オーバーライドされたメンバーを呼び出しているかどうかは、実行時までわかりません。また、メンバーがフィールドの場合、オーバーライドすることはできませんが、アクセスは引き続き禁止されています。

実用的な説明:他のクラスは、クラスが知らない不変条件を追加する可能性があり、それらの不変条件を維持できるように、公開インターフェイスを使用する必要があります。MyDerived が YourDerived の保護されたメンバーに直接アクセスできる場合、それらの不変条件が壊れる可能性があります。

私の同じ反論がここに当てはまります。MyDerived は、MySuperDerived が追加する可能性のある不変条件も知りません。別の作成者によって別のアセンブリで定義される可能性があります。では、なぜ MyDerived はその保護されたメンバーに直接アクセスできるのでしょうか?

このコンパイル時の制限は、実際には実行時にしか解決できない問題を解決しようとする誤った試みとして存在するという印象を受けます。しかし、多分私は何かを見逃しています。MyDerived がタイプ YourDerived または Base の変数を介して Base の保護されたメンバーにアクセスできるようにすることによって引き起こされる問題の例はありますが、タイプ MyDerived または MySuperDerived の変数を介してそれらにアクセスする場合にはまだ存在しませんか?

--

更新: コンパイラが言語仕様に従っていることはわかっています。私が知りたいのは、仕様のその部分の目的です。理想的な答えは、「MyDerived が YourDerived.Member() を呼び出すことができれば、$NIGHTMARE が発生しますが、MySuperDerived.Member() を呼び出すと、$ITSALLGOOD のため発生しません。」

4

6 に答える 6

21

更新: この質問は、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 の変数を介してそれらにアクセスする場合にはまだ存在しませんか?

私はあなたの質問にかなり混乱していますが、喜んで試してみます。

私の理解が正しければ、あなたの質問は 2 つの部分に分かれています。まず、派生型の少ない型を介して保護されたメソッドを呼び出すことの制限を正当化する攻撃の軽減策は何ですか? 第二に、なぜ同じ正当化が、同等に派生した、またはより派生した型での保護されたメソッドの呼び出しを防止する動機にならないのでしょうか?

最初の部分は簡単です:

// 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 があなたのスイスの銀行口座から 1... 100 万... ドル... を盗もうとする試みは、C# コンパイラによって阻止されました。

明らかにこれはばかげた例であり、明らかに、完全に信頼されたコードは、型に対して必要なことを何でも実行できます。完全に信頼されたコードは、デバッガーを起動し、実行中にコードを変更できます。完全な信頼とは、完全な信頼を意味します。この方法で実際のセキュリティ システムを実際に設計しないでください。

しかし、私が言いたいのは、ここで失敗した「攻撃」は、誰かが SecureBankAccount によって設定された不変条件を迂回して、SwissBankAccount のコードに直接アクセスしようとしているということです。

これで最初の質問に答えられると思います。不明な場合はお知らせください。

2 番目の質問は、「SecureBankAccount にもこの制限がないのはなぜですか?」です。私の例では、SecureBankAccount は次のように述べています。

    this.DoTransfer(destinationAccount, user, amount);

明らかに「これ」は、SecureBankAccount 型またはそれ以上の派生型です。これは、新しい SwissBankAccount を含む、より派生した型の任意の値である可能性があります。SecureBankAccount は、SwissBankAccount の不変条件をめぐってエンドランを行っているのではないでしょうか?

そのとおり!そのため、SwissBankAccount の作成者は、基本クラスが行うすべてのことを理解する必要があります。あるクラスから意のままに派生して、最高のものを期待することはできません! 基本クラスの実装は、基本クラスによって公開された保護されたメソッドのセットを呼び出すことができます。それから派生させたい場合は、そのクラスのドキュメントまたはコードを読み、保護されたメソッドが呼び出される状況を理解し、それに応じてコードを記述する必要があります。派生は、実装の詳細を共有する方法です。派生元の実装の詳細を理解していない場合は、派生しないでください。

さらに、基本クラスは常に派生クラスの前に記述されます。基本クラスは稼働しておらず、変更も加えられていません。おそらく、クラスの作成者が、将来のバージョンでこっそり壊れようとしないことを信頼していると思われます。(もちろん、基底クラスへの変更は常に問題を引き起こす可能性があります。これは脆弱な基底クラスの問題の別のバージョンです。)

2 つのケースの違いは、基本クラスから派生する場合、選択した1 つのクラスの動作を理解して信頼できることです。それは扱いやすい作業量です。SwissBankAccount の作成者は、保護されたメソッドが呼び出される前に、SecureBankAccount が不変であることを保証するものを正確に理解する必要があります。しかし、考えられるすべてのいとこクラスのすべての可能な動作を理解し、信頼する必要はありません。それはたまたま同じ基本クラスから派生したものです。それらの人は誰でも実装でき、何でもできます。呼び出し前の不変条件を理解する能力はまったくないため、機能する保護されたメソッドを正常に作成することはできません。したがって、私たちはあなたを悩ませず、そのシナリオを許可しません.

さらに、潜在的により派生したクラスのレシーバーで保護されたメソッドを呼び出せるようにする必要があります私たちがそれを許しておらず、ばかげたことを推測したとしましょう。より派生する可能性のあるクラスのレシーバーで保護されたメソッドを呼び出すことを禁止した場合、どのような状況で保護されたメソッドが呼び出される可能性がありますか? その世界で保護されたメソッドを呼び出すことができるのは、シールされたクラスから独自の保護されたメソッドを呼び出す場合だけです! 事実上、保護されたメソッドはほとんど呼び出され、呼び出された実装は常に最も派生したものになります。その場合の「保護」のポイントは何ですか?あなたの「保護された」は、「プライベートであり、封印されたクラスからのみ呼び出すことができる」と同じことを意味します。それはそれらをかなり役に立たなくするでしょう。

したがって、両方の質問に対する簡単な答えは、「そうしなければ、保護されたメソッドを使用することはまったく不可能になるからです」です。低派生型による呼び出しを制限します。そうしないと、不変条件に依存する保護されたメソッドを安全に実装することが不可能になるからです。潜在的なサブタイプを介した呼び出しを許可します。これを許可しない場合、呼び出しはほとんど許可されないためです。

それはあなたの質問に答えていますか?

于 2009-12-15T04:19:14.920 に答える
6

Eric Lippert は、彼のブログ投稿の 1 つでそれをよく説明しています。

于 2009-12-15T02:29:56.030 に答える
1

設計の純度に関する漠然とした概念のために、この動作が明示的に許可されていないという前提で操作しています。私はマイクロソフトで働いていませんが、真実ははるかに単純だと思います。それは禁止されておらず、サポートされていないだけです。比較的影響を少なくするために実装するには時間がかかるからです。

あまり使用されていない場合は、それだけではうまくいかないprotected internal場合の大部分をカバーするでしょう。protected

于 2009-12-15T03:56:54.110 に答える
1

「保護されている」とは、定義しているクラスとすべてのサブクラスのみがメンバーにアクセスできることを意味します。

MySuperDerivedのサブクラスであるMyDerivedため、からMemberアクセスできますMyDerived。このように考えてください:MySuperDerivedは でMyDerivedあり、そのプライベート メンバーと保護されたメンバー ( から継承MyDerived) は からアクセスできますMyDerived

ただし、は でYourDerivedはないMyDerivedため、その非公開および保護されたメンバーには からアクセスできませんMyDerived

また、 のインスタンスでもサブクラスでもない である可能性があるためMember、 のインスタンスにアクセスすることはできません。BaseBaseYourDerivedMyDerivedMyDerived

また、静的メソッドを使用してアクセスを許可しないでください。それはカプセル化の目的を無効にしており、物事を適切に設計していないという大きな臭いです.

于 2009-12-15T02:12:22.660 に答える
1

あなたはこれについて完全に間違った方法で考えているようです。

「誰が何を呼び出せるか」ではなく、どこで何を代用できるかということです。

MyDerived のサブクラスは、MyDerived (オーバーライドされた保護されたメソッドを含む) を常に代用できる必要があります。Base の他のサブクラスにはそのような制約がないため、MyDerived の代わりにそれらを置き換えることはできません。

于 2009-12-15T02:13:41.240 に答える
1

http://msdn.microsoft.com/en-us/library/bcd5672a.aspx

基本クラスの保護されたメンバーは、派生クラスの型を介してアクセスが発生した場合にのみ、派生クラスでアクセスできます。

「何?」のドキュメントがあります。質問。今、「なぜ?」がわかればいいのにと思います。:)

明らかvirtualに、このアクセス制限とは何の関係もありません。

うーん、私はあなたが兄弟のもので何かに取り組んでいると思います... MyDerived は YourDerived.Member を呼び出すことができないはずです

MyDerived が Base.Member を呼び出すことができる場合、実際には YourDerived のインスタンスで動作している可能性があり、実際に YourDerived.Member を呼び出している可能性があります。

ああ、ここに同じ質問があります:基本クラス変数を介してアクセスされる C# 保護メンバー

于 2009-12-15T02:23:03.503 に答える