web-dev-qa-db-ja.com

これ以外の方法でC#で保護されたメンバーにアクセスできないのはなぜですか?

このコード:

abstract class C
{
    protected abstract void F(D d);
}

class D : C
{
    protected override void F(D d) { }

    void G(C c)
    {
        c.F(this);
    }
}

このエラーを生成します:

タイプ 'C'の修飾子を介して保護メンバー 'C.F(D)'にアクセスできません。修飾子はタイプ 'D'である(またはそれから派生する)必要があります

彼らは世界で何を考えていましたか? (そのルールを変更することは何かを壊すでしょうか?)そして、Fを公開すること以外にその回避策はありますか?


編集:これが(Thanks Greg )である理由がわかりますが、合理性についてはまだ少し困惑しています。与えられた:

class E : C
{
    protected override void F(D d) { }
}  

なぜDがE.Fを呼び出せないべきではないのですか?


エラーメッセージは編集されているので、そこにタイプミスがあったかもしれません。

39
BCS

「保護された」キーワードは、そのタイプから派生したタイプとタイプのみがメンバーにアクセスできることを意味します。 DはCとは関係がないため、メンバーにアクセスできません。

そのメンバーにアクセスできるようにしたい場合は、いくつかのオプションがあります

  • 公開する
  • 内部にする。これにより、すべてのタイプが同じアセンブリ内のメンバーにアクセスできるようになります(または、友人を追加する場合は他のアセンブリ)
  • CからDを派生

[〜#〜]編集[〜#〜]

このシナリオは、C#仕様のセクション3.5.3で説明されています。

これが許可されない理由は、階層間の呼び出しが可能になるためです。 Dに加えて、Eと呼ばれるCの別の基本クラスがあったと想像してください。コードをコンパイルできれば、DがメンバーEFにアクセスできるようになります。このタイプのシナリオは、C#(およびI信じるCLRですが、100%わかりません)。

EDIT2なぜこれが悪いのか

警告、これは私の意見です

これが現在許可されている理由は、クラスの動作について推論することが非常に困難になるためです。アクセス修飾子の目的は、特定のメソッドにアクセスできるユーザーを正確に制御できるようにすることです。次のクラスを想像してください

sealed class MyClass : C {
  override F(D d) { ... } 
}

Fがややタイムクリティカルな関数である場合にどうなるかを考えます。現在の振る舞いで、クラスの正しさを推論できます。結局のところ、MyClass.Fが呼び出されるケースは2つだけです。

  1. Cで呼び出される場所
  2. MyClassで明示的に呼び出す場所

これらの呼び出しを調べて、MyClassがどのように機能するかについて合理的な結論を出すことができます。

今、C#が階層間の保護されたアクセスを許可する場合、そのような保証はできません。完全に異なるアセンブリの誰もがCから来て派生することができます。その後、自由にMyClass.Fを呼び出すことができます。これは私のクラスの正しさについて推論することを完全に不可能にします。

18
JaredPar

これが機能しない理由は、C#では保護されたメソッドの階層間の呼び出しが許可されていないためです。 Eから派生したクラスCがあったとしましょう:

  C
 / \
D   E

次に、メソッドを呼び出そうとしている参照は、実際にはE型のインスタンスである可能性があるため、メソッドは実行時にE.Fに解決されます。 Dは階層の別のブランチにあるため、EEの保護されたメソッドを呼び出すことができないため、これはC#では許可されていません。

var d = new D();
var e = new E();
d.G(e); // oops, now this will call E.F which isn't allowed from D

キーワードprotectedはメンバー " そのクラス内および派生クラスインスタンスによってアクセス可能 "を意味し、E.FはDのメンバーではないため、これは理にかなっています。

41
Greg Beech

DはCから継承されますが、DはCの保護されたメンバーにアクセスできません。 DはDの保護された(そしてプライベート!)メンバーにアクセスできるので、Cの代わりにDの別のインスタンスを配置すると、すべてが機能します。しかし、Gregが述べたように、Cは実際にはまったく異なるものである可能性があり、コンパイラーはCが何であるかを知らないため、Dが実際にアクセスできない可能性があるものにアクセスできないようにする必要があります。

これをC#コンパイラの観点から説明する一連の投稿:

11
MSN

この制限は、静的に保護されたメソッドを使用することで回避できます。

abstract class C
{
    protected abstract void F (D d);

    // Allows calling F cross-hierarchy for any class derived from C
    protected static void F (C c, D d)
    {
        c.F(d);
    }
}

class D : C
{
    protected override void F (D d) { }

    void G (C c)
    {
        // c.F(this);
        F(c, this);
    }
}

これはセキュリティの観点からは完璧ではありません(誰でもCから派生できます)が、クラスFのパブリックインターフェイスからメソッドCを非表示にするだけの場合は、このトリックが役立つことがあります。

3
Athari

簡単に言うと、派生クラス内からアクセスしようとした場合でも、インスタンスの保護メンバーへのアクセスはパブリックアクセスと見なされます。したがって、それは拒否されました。


あちこちにたくさんの答えがありますが、「なぜ子供から親クラスの保護されたメンバーにアクセスできないのか」を明確にしたものはありませんでした。上記は、これらの紛らわしい答えを読んだ後、コードをもう一度見た後に私が理解したことです。

例:

class Parent
{
    protected int foo = 0;
}

// Child extends from Parent
class Child : Parent
{
    public void SomeThing(Parent p)
    {
        // Here we're trying to access an instance's protected member.
        // So doing this...
        var foo = p.foo;
    }
}

// (this class has nothing to do with the previous ones)
class SomeoneElse
{
    public void SomeThing(Parent p)
    {
        // ...is the same as doing this (i.e. public access).
        var foo = p.foo++;
    }
}

p.fooにはアクセスできると思いますが、これは子クラス内にいるため、インスタンスからアクセスしているため、パブリックアクセスのように拒否されます。

インスタンスからではなく、クラス内からprotectedメンバーへのアクセスが許可されています(そうです、これはわかっています)。

class Child : Parent
{
    public void SomeThing()
    {
        // I'm allowed to modify parent's protected foo because I'm
        // doing so from within the class.
        foo++;
    }
}

最後に、完全を期すために、実際には、同じクラス内でアクセスしている場合にのみ、インスタンスのprotectedおよびprivateメンバーにさえアクセスできます。

class Parent
{
    protected int foo = 0;

    private int bar = 0;

    public void SomeThing(Parent p)
    {
        // I'm allowed to access an instance's protected and private
        // members because I'm within Parent accessing a Parent instance
        var foo = p.foo;
        p.bar = 3;
    }
}
1
Parziphal

この種の振る舞いが理にかなっている理由を理解するために、オブジェクト指向プログラミング言語でアクセス修飾子がまったく必要な理由を考えてみましょう。特定のクラスメンバーを使用できるスコープを制限するためにが必要です。これにより、使用状況の検索が簡単になります。

要約する:

  • public メンバーのすべての使用法を見つけるには、プロジェクト全体を検索する必要があります(これは、独立した開発者)
  • protected メンバーのすべての使用法を見つけるには、コンテナクラスとそのすべてのサブクラスを検索する必要があります
  • private メンバーのすべての使用法を見つけるには、 container class を検索する必要があります。

そのため、コンパイラがスーパークラスから保護されたメソッドを上記の方法で呼び出すことを許可した場合、 this answer で説明されているように、保護されたメソッドの階層間呼び出しが発生する可能性があります。そのような状況では、メンバーを定義する最も親のクラスのすべての子を検索する必要がありました。そして、それは範囲を拡大します。

PS。同じ動作がJavaに実装されています。

1
wheleph

はい、可能です。このような例はまもなくできるでしょう。

そのためには、次のことを行う必要があります。

  1. デフォルトフォーム(EditAppointmentDialog)を継承し、カスタマイズを行います(そのためにwinformsデザイナーを使用することもできます)。

パブリック部分クラスCustomAppointmentEditDialog:EditAppointmentDialog {private RadComboBox cmbShowTimeAs = null;

    public CustomAppointmentEditDialog() 
    { 
        InitializeComponent(); 

        this.cmbShowTimeAs = this.Controls["cmbShowTimeAs"] as RadComboBox; 
    } 

    private void chkConfirmed_ToggleStateChanged(object sender, StateChangedEventArgs args) 
    { 
        this.cmbShowTimeAs.SelectedValue = (args.ToggleState == ToggleState.On) ? 
            (int)AppointmentStatus.Busy : (int)AppointmentStatus.Tentative; 
    } 
} 

上記のコードで、追加のチェックボックスを追加し、予定のステータス(時間を表示)がオフの場合は仮に、オンの場合はビジーに設定しました。コンボボックスにアクセスする奇妙な方法は、コンボボックスが現在プライベートであるためです。これは、2009年の第1四半期リリースで変更されます。

  1. RadSchedulerのAppointmentEditDialogShowingイベントをサブスクライブし、デフォルトのフォームをカスタマイズしたフォームに置き換えます。

プライベートIEditAppointmentDialog appointmentEditDialog = null;

    protected override void OnLoad(EventArgs e) 
    { 
        base.OnLoad(e); 

        this.radScheduler1.AppointmentEditDialogShowing += new EventHandler<AppointmentEditDialogShowingEventArgs>(radScheduler1_AppointmentEditDialogShowing); 
    } 

    void radScheduler1_AppointmentEditDialogShowing(object sender, Telerik.WinControls.UI.AppointmentEditDialogShowingEventArgs e) 
    { 
        if (this.appointmentEditDialog == null) 
        { 
            this.appointmentEditDialog = new CustomAppointmentEditDialog(); 
        } 
        e.AppointmentEditDialog = this.appointmentEditDialog; 
    } 

これがお役に立てば幸いです。他にご不明な点がございましたら、お気軽にご返信ください。

0
bensuwait