217

C++ の friend キーワードを使用すると、をそのフレンドとしてclass A指定できます。class Bこれにより、 の/メンバーClass Bにアクセスできます。privateprotectedclass A

これが C# (および VB.NET) から除外された理由について、私は何も読んだことがありません。この以前の StackOverflow の質問に対するほとんどの回答は、それが C++ の有用な部分であり、それを使用する十分な理由があると言っているようです。私の経験では、私は同意しなければなりません。

もう 1 つの質問friendは、C# アプリケーションで似たようなことを行う方法を実際に尋ねているように思えます。答えは通常、ネストされたクラスを中心に展開されますが、friendキーワードを使用するほどエレガントではないようです。

元のDesign Patterns ブックでは、例全体で定期的に使用しています。

要約すると、なぜfriendC#に欠けているのでしょうか?C#でそれをシミュレートする「ベストプラクティス」の方法は何ですか?

(ちなみに、internalキーワードは同じではありません。アセンブリ全体内のすべてのinternalクラスがメンバーにアクセスfriendできるようにする一方で、特定のクラスに他の1 つのクラスだけに完全なアクセス権を与えることを可能にします)

4

22 に答える 22

109

余談ですが。フレンドを使用することは、カプセル化に違反することではなく、逆にそれを強制することです。アクセサー + ミューテーター、演算子のオーバーロード、パブリック継承、ダウンキャストなどと同様に、よく誤用されますが、キーワードに目的がない、または悪い目的があるという意味ではありません。

他のスレッドでKonrad Rudolphメッセージを参照するか、必要に応じて C++ FAQの関連エントリを参照してください。

于 2008-10-15T10:01:45.600 に答える
70

プログラミングに友達を持つことは、多かれ少なかれ「汚い」と見なされ、悪用されやすい. クラス間の関係を壊し、オブジェクト指向言語のいくつかの基本的な属性を弱体化させます。

そうは言っても、これは素晴らしい機能であり、私自身も C++ で何度も使用しています。C#でも使いたいです。しかし、C# の「純粋な」OOness (C++ の疑似 OOness と比較して) のせいだと思います。

重大な注意点: 内部は友人ほど良くありませんが、仕事は完了します。DLL を使用せずにコードをサード パーティの開発者に配布することはめったにないことに注意してください。そのため、あなたとあなたのチームが内部クラスとその使用について知っている限り、問題はありません。

編集友人のキーワードがOOPをどのように弱体化させるかを明確にしましょう。

プライベートおよび保護された変数とメソッドは、おそらく OOP の最も重要な部分の 1 つです。オブジェクトのみが使用できるデータまたはロジックをオブジェクトが保持できるという考えにより、環境とは無関係に機能の実装を記述できます。また、環境は、処理に適していない状態情報を変更することはできません。フレンドを使用すると、2 つのクラスの実装を結合することになります。これは、それらのインターフェイスを結合した場合よりもさらに悪いことです。

于 2008-10-15T03:40:47.333 に答える
51

参考までに、.NET の別の関連するがまったく同じではないのは です[InternalsVisibleTo]。これにより、アセンブリは、型/メンバーへの「内部」アクセスを (効果的に) 持つ別のアセンブリ (単体テスト アセンブリなど) を指定できます。元のアセンブリ。

于 2008-10-15T05:41:25.453 に答える
14

実際、C# は、特別な言葉を使わずに純粋な OOP の方法で同じ動作を取得する可能性を提供します。これはプライベート インターフェイスです。

質問に関しては、友人に相当するC#は何ですか? この記事と重複しているとマークされており、誰も本当に良い実現を提案していません - ここで両方の質問に対する答えを示します。

ここからの主なアイデアは次のとおりです。プライベートインターフェイスとは何ですか?

たとえば、別のクラスのインスタンスを管理し、それらに対していくつかの特別なメソッドを呼び出すことができるクラスが必要です。このメソッドを他のクラスに呼び出す可能性を与えたくありません。これは、c++ の世界で友人の c++ キーワードが行うこととまったく同じです。

実際の良い例は、一部のコントローラーが現在の状態オブジェクトを更新し、必要に応じて別の状態オブジェクトに切り替えるフル ステート マシン パターンであると思います。

あなたは出来る:

  • Update() メソッドを public にする最も簡単で最悪の方法 - なぜそれが悪いのか、誰もが理解してくれることを願っています。
  • 次の方法は、内部としてマークすることです。クラスを別のアセンブリに配置すれば十分ですが、その場合でも、そのアセンブリ内の各クラスは各内部メソッドを呼び出すことができます。
  • プライベート/保護されたインターフェイスを使用します-そして、私はこの方法に従いました。

Controller.cs

public class Controller
{
    private interface IState
    {
        void Update();
    }

    public class StateBase : IState
    {
        void IState.Update() {  }
    }

    public Controller()
    {
        //it's only way call Update is to cast obj to IState
        IState obj = new StateBase();
        obj.Update();
    }
}

Program.cs

class Program
{
    static void Main(string[] args)
    {
        //it's impossible to write Controller.IState p = new Controller.StateBase();
        //Controller.IState is hidden
        var p = new Controller.StateBase();
        //p.Update(); //is not accessible
    }
}

では、継承はどうでしょうか。

明示的なインターフェイス メンバーの実装は仮想として宣言できないため、で説明されている手法を使用する必要があり、IState を保護されているものとしてマークして、コントローラーからも派生できるようにする必要があります。

Controller.cs

public class Controller
{
    protected interface IState
    {
        void Update();
    }

    public class StateBase : IState
    {
        void IState.Update() { OnUpdate(); }
        protected virtual void OnUpdate()
        {
            Console.WriteLine("StateBase.OnUpdate()");
        }
    }

    public Controller()
    {
        IState obj = new PlayerIdleState();
        obj.Update();
    }
}

PlayerIdleState.cs

public class PlayerIdleState: Controller.StateBase
{
    protected override void OnUpdate()
    {
        base.OnUpdate();
        Console.WriteLine("PlayerIdleState.OnUpdate()");
    }
}

最後に、クラス Controller のスロー継承をテストする方法の例: ControllerTest.cs

class ControllerTest: Controller
{
    public ControllerTest()
    {
        IState testObj = new PlayerIdleState();
        testObj.Update();
    }
}

すべてのケースをカバーし、私の答えが役に立ちました。

于 2016-05-15T13:06:17.513 に答える
13

C# のインターフェイスを使用することで、C++ で "friend" が使用されるのと同じ種類のことを達成できるはずです。2 つのクラス間で渡されるメンバーを明示的に定義する必要があります。これは余分な作業ですが、コードを理解しやすくすることもできます。

インターフェイスを使用してシミュレートできない「友達」の合理的な使用例を誰かが持っている場合は、それを共有してください! C++ と C# の違いをよりよく理解したいと思います。

于 2008-10-15T03:40:04.013 に答える
13

C++ デザイナーを使用friendすると、private* メンバーが誰に公開されるかを正確に制御できます。しかし、彼はプライベートメンバーのすべてを公開することを余儀なくされています.

C# を使用internalすると、デザイナーは公開するプライベート メンバーのセットを正確に制御できます。明らかに、彼は 1 つのプライベート メンバーだけを公開できます。ただし、アセンブリ内のすべてのクラスに公開されます。

通常、設計者は、いくつかのプライベート メソッドのみを、選択したいくつかの他のクラスに公開したいと考えています。たとえば、クラス ファクトリ パターンでは、クラス C1 がクラス ファクトリ CF1 によってのみインスタンス化されることが望ましい場合があります。したがって、クラス C1 は、保護されたコンストラクターとフレンド クラス ファクトリ CF1 を持つことができます。

ご覧のとおり、カプセル化を突破できる 2 つの次元があります。 friend一方の次元に沿ってそれを破り、もう一方の次元に沿って破りますinternal。カプセル化の概念に違反しているのはどれですか? 言いにくい。しかし、 と の両方が利用できると便利friendですinternal。さらに、これら 2 つに追加するのに適したのは、3 番目のタイプのキーワードです。これは、メンバーごとに使用され ( のようにinternal)、ターゲット クラスを指定します ( のようにfriend)。

* 簡潔にするために、「プライベートおよび/または保護」の代わりに「プライベート」を使用します。

- ニック

于 2011-01-02T05:36:27.003 に答える
8

友達はユニットテストを書くときに非常に便利です。

これは、クラス宣言をわずかに汚染するという犠牲を伴いますが、クラスの内部状態を実際にどのテストが気にするかをコンパイラーが強制的に思い出させるものでもあります。

私が見つけた非常に便利でクリーンなイディオムは、ファクトリクラスがあり、それらが作成したアイテムの友達になり、コンストラクターが保護されている場合です。より具体的には、これは、レポートライターオブジェクトに一致するレンダリングオブジェクトを作成し、特定の環境にレンダリングすることを担当する単一のファクトリがあったときでした。この場合、レポートライタークラス(画像ブロック、レイアウトバンド、ページヘッダーなど)とそれらに一致するレンダリングオブジェクトとの関係についての単一の知識があります。

于 2009-01-19T16:27:31.887 に答える
8

C# のキーワード"internal"を使用すると、C++ の "friend" に近づくことができます。

于 2008-10-15T03:28:55.690 に答える
8

C# には、決定論的破壊がないのと同じ理由で、「friend」キーワードがありません。慣習を変えると、人々は自分の新しいやり方が他の人の古いやり方よりも優れているかのように、賢く感じます。それはすべて誇りです。

「フレンド クラスが悪い」と言うのは、「goto を使用しないでください」や「Linux は Windows よりも優れている」などの他の不適格なステートメントと同じくらい近視眼的です。

プロキシ クラスと組み合わせた "friend" キーワードは、クラスの特定の部分のみを特定の他のクラスに公開する優れた方法です。プロキシ クラスは、他のすべてのクラスに対する信頼できるバリアとして機能できます。「パブリック」ではそのようなターゲティングは許可されず、概念的な「is a」関係が実際に存在しない場合、「保護」を使用して継承の効果を得るのは厄介です。

于 2010-04-09T05:52:54.590 に答える
7

これは実際には C# の問題ではありません。これは、IL の根本的な制限です。C# は、検証可能であることを求める他の .Net 言語と同様に、これによって制限されます。この制限には、C++/CLI (仕様セクション 20.5 ) で定義されたマネージド クラスも含まれます。

そうは言っても、ネルソンはなぜこれが悪いことなのかについて良い説明をしていると思います.

于 2008-10-15T08:07:42.460 に答える
4

この制限の言い訳をやめてください。友達は悪いが、内部は良い?それらは同じものですが、その友人だけが、誰がアクセスを許可され、誰が許可されないかをより正確に制御できます.

これは、カプセル化パラダイムを強制するためですか? アクセサ メソッドを作成する必要があります。全員 (クラス B のメソッドを除く) がこれらのメソッドを呼び出さないようにするにはどうすればよいでしょうか? 「友達」がいないため、これも制御できないため、できません。

完璧なプログラミング言語はありません。C# は私が見た中で最高の言語の 1 つですが、不足している機能についてばかげた言い訳をしても、だれの助けにもなりません。C++ では、簡単なイベント/デリゲート システム、リフレクション (+ 自動デシリアライゼーション)、および foreach が恋しいですが、C# では、オペレーターのオーバーロード (はい、それは必要ないと言い続けます)、デフォルト パラメーター、const が恋しいです。回避できないこと、多重継承 (はい、あなたはそれを必要とせず、インターフェイスは十分な代替品であると私に言い続けます)、およびメモリからインスタンスを削除することを決定する機能 (いいえ、あなたが特別でない限り、これはひどく悪いことではありません)いじくり屋)

于 2010-11-15T07:41:53.543 に答える
2

.Net 3以降、InternalsVisibleToAttributeがありますが、ユニットテストの台頭後にアセンブリをテストするために追加しただけだと思います。それを使用する他の多くの理由はわかりません。

アセンブリ レベルで機能しますが、内部では機能しない仕事をします。つまり、アセンブリを配布したいが、配布されていない別のアセンブリに特権アクセスを持たせたい場合です。

当然のことながら、誰かが保護されたアセンブリと一緒に偽の友人を作成するのを避けるために、友人アセンブリを強力なキーにする必要があります。

于 2011-07-11T21:25:53.277 に答える
1

以前は友達を定期的に使用していましたが、OOP違反や設計上の欠陥の兆候ではないと思います。最小限のコードで適切な目的を達成するための最も効率的な手段である場所がいくつかあります。

具体的な例の1つは、他のソフトウェアへの通信インターフェイスを提供するインターフェイスアセンブリを作成する場合です。一般に、プロトコルとピアの特性の複雑さを処理し、クライアントアプリとアセンブリ間でメッセージと通知を渡すことを含む比較的単純な接続/読み取り/書き込み/転送/切断モデルを提供するいくつかのヘビーウェイトクラスがあります。これらのメッセージ/通知はクラスでラップする必要があります。属性は、作成者であるため、通常、プロトコルソフトウェアで操作する必要がありますが、多くのものは、外部に対して読み取り専用のままにする必要があります。

プロトコル/「作成者」クラスが作成されたすべてのクラスに密接にアクセスすることはOOPの違反であると宣言するのはまったくばかげています。作成者クラスは、途中でデータのすべてのビットをビットマンジする必要がありました。私が最も重要だと思ったのは、「OOPforOOP'sSake」モデルが通常もたらすすべてのBSの余分なコード行を最小限に抑えることです。余分なスパゲッティはただより多くのバグを作ります。

属性、プロパティ、メソッドのレベルで内部キーワードを適用できることを人々は知っていますか?これはトップレベルのクラス宣言だけではありません(ほとんどの例はそれを示しているようですが)。

friendキーワードを使用するC++クラスがあり、それをC#クラスでエミュレートする場合:1。C#クラスをパブリックとして宣言します2. C ++で保護されているため、友人がアクセスできるすべての属性/プロパティ/メソッドを次のように宣言します。 C#3の内部。すべての内部属性とプロパティへのパブリックアクセス用の読み取り専用プロパティを作成します。

私はそれが友人と100%同じではないことに同意します、そしてユニットテストは友人のようなものの必要性の非常に貴重な例です(プロトコルアナライザーのロギングコードもそうです)。ただし、internalは、公開したいクラスへの公開を提供し、[InternalVisibleTo()]は残りを処理します。これは、単体テスト用に特別に作成されたようです。

友人が「どのクラスにアクセスできるかを明示的に制御できるので、より良くなる」限り、そもそも同じアセンブリで悪の疑いのあるクラスが何をしているのでしょうか。アセンブリを分割します!

于 2011-01-30T09:41:40.240 に答える
1

友達を使うと物事が制御不能になる可能性があると示唆する人もいます。私は同意しますが、それはその有用性を損なうものではありません. すべてのクラス メンバーを公開する以上に、友人が OO パラダイムを必ずしも傷つけるとは確信していません。確かに、この言語ではすべてのメンバーを公開することができますが、そのような設計パターンを避けるのは訓練されたプログラマーです。同様に、訓練されたプログラマーは、それが理にかなっている特定の場合にのみ友人の使用を予約します。場合によっては、内部が露出しすぎているように感じます。クラスまたはメソッドをアセンブリ内のすべてに公開するのはなぜですか?

独自のベース ページを継承する ASP.NET ページがあり、それがさらに System.Web.UI.Page を継承します。このページには、保護されたメソッドでアプリケーションのエンド ユーザー エラー レポートを処理するコードがいくつかあります。

ReportError("Uh Oh!");

これで、ページに含まれるユーザー コントロールができました。ユーザー コントロールが、ページ内のエラー レポート メソッドを呼び出せるようにしたいと考えています。

MyBasePage bp = Page as MyBasePage;
bp.ReportError("Uh Oh");

ReportError メソッドが保護されている場合、それはできません。内部にすることはできますが、アセンブリ内のすべてのコードに公開されます。現在のページ (子コントロールを含む) の一部である UI 要素に公開したいだけです。より具体的には、ベース コントロール クラスでまったく同じエラー レポート メソッドを定義し、ベース ページでメソッドを呼び出すだけにしたいと考えています。

protected void ReportError(string str) {
    MyBasePage bp = Page as MyBasePage;
    bp.ReportError(str);
}

友人のようなものは、おそらく属性のように、言語を「OO」のようにすることなく、言語で有用かつ実装できると信じています。これにより、クラスまたはメソッドを特定のクラスまたはメソッドの友人にすることができ、開発者は非常に特定のアクセス。おそらく...(疑似コード)のようなもの

[Friend(B)]
class A {

    AMethod() { }

    [Friend(C)]
    ACMethod() { }
}

class B {
    BMethod() { A.AMethod() }
}

class C {
    CMethod() { A.ACMethod() }
}

私の前の例の場合、おそらく次のようなものがあります(セマンティクスについて議論することはできますが、私はアイデアを理解しようとしているだけです):

class BasePage {

    [Friend(BaseControl.ReportError(string)]
    protected void ReportError(string str) { }
}

class BaseControl {
    protected void ReportError(string str) {
        MyBasePage bp = Page as MyBasePage;
        bp.ReportError(str);
    }
}

私が見ているように、フレンドの概念には、物事を公開したり、メンバーにアクセスするためのパブリック メソッドやプロパティを作成したりすることほどリスクはありません。友人がデータのアクセシビリティの別のレベルの粒度を許可し、そのアクセシビリティを内部またはパブリックで広げるのではなく狭めることができる場合。

于 2012-07-18T20:09:38.157 に答える
1

それは C# コンパイル モデルと関係があるのではないかと思います。実行時に IL をコンパイルして JIT をビルドします。つまり、C# ジェネリックが C++ ジェネリックと根本的に異なるのと同じ理由です。

于 2008-10-15T03:29:59.087 に答える
1

友情は、インターフェイスと実装を分離することでシミュレートできます。アイデアは次のとおりです。「具体的なインスタンスが必要ですが、そのインスタンスの構築アクセスを制限します」。

例えば

interface IFriend { }

class Friend : IFriend
{
    public static IFriend New() { return new Friend(); }
    private Friend() { }

    private void CallTheBody() 
    {  
        var body = new Body();
        body.ItsMeYourFriend(this);
    }
}

class Body
{ 
    public void ItsMeYourFriend(Friend onlyAccess) { }
}

ItsMeYourFriend()public であるにもかかわらず、Friend他の誰もクラスの具体的なインスタンスを取得できないため、クラスのみがアクセスできますFriendNew()ファクトリメソッドはインターフェイスを返しますが、プライベート コンストラクターがあります。

詳細については、私の記事Friends and internal interface members at no cost withcoding to interfacesを参照してください。

于 2012-06-21T19:47:40.580 に答える
1

プライベートに保ち、リフレクションを使用して関数を呼び出すことができます。プライベート関数をテストするように依頼した場合、テスト フレームワークはこれを行うことができます

于 2010-06-04T13:24:44.413 に答える
0

友情は、「エージェント」 (いくつかの内部クラス) を使用してシミュレートすることもできます。次の例を検討してください。

public class A // Class that contains private members
{
  private class Accessor : B.BAgent // Implement accessor part of agent.
  {
    private A instance; // A instance for access to non-static members.
    static Accessor() 
    { // Init static accessors.
      B.BAgent.ABuilder = Builder;
      B.BAgent.PrivateStaticAccessor = StaticAccessor;
    }
    // Init non-static accessors.
    internal override void PrivateMethodAccessor() { instance.SomePrivateMethod(); }
    // Agent constructor for non-static members.
    internal Accessor(A instance) { this.instance = instance; }
    private static A Builder() { return new A(); }
    private static void StaticAccessor() { A.PrivateStatic(); }
  }
  public A(B friend) { B.Friendship(new A.Accessor(this)); }
  private A() { } // Private constructor that should be accessed only from B.
  private void SomePrivateMethod() { } // Private method that should be accessible from B.
  private static void PrivateStatic() { } // ... and static private method.
}
public class B
{
  // Agent for accessing A.
  internal abstract class BAgent
  {
    internal static Func<A> ABuilder; // Static members should be accessed only by delegates.
    internal static Action PrivateStaticAccessor;
    internal abstract void PrivateMethodAccessor(); // Non-static members may be accessed by delegates or by overrideable members.
  }
  internal static void Friendship(BAgent agent)
  {
    var a = BAgent.ABuilder(); // Access private constructor.
    BAgent.PrivateStaticAccessor(); // Access private static method.
    agent.PrivateMethodAccessor(); // Access private non-static member.
  }
}

静的メンバーへのアクセスのみに使用すると、はるかに簡単になります。このような実装の利点は、すべての型がフレンドシップ クラスの内部スコープで宣言され、インターフェイスとは異なり、静的メンバーにアクセスできることです。

于 2014-01-02T15:59:17.230 に答える
0

C++ を使用していて、friend キーワードを使用していることがわかった場合は、設計上の問題があることを非常に強く示しています。なぜなら、クラスが他のクラスのプライベート メンバーにアクセスする必要があるからです。

于 2008-10-15T05:28:24.933 に答える
0

BSD

友達は純粋なOOnessを傷つけると述べられていました. 私は同意します。

また、友達がカプセル化を助けるとも言われましたが、私も同意します。

オブジェクト指向の方法論に友情を追加する必要があると思いますが、C++ ほどではありません。フレンド クラスがアクセスできるいくつかのフィールド/メソッドが必要ですが、すべてのフィールド/メソッドにアクセスできるようにしたくはありません。実生活と同じように、友人には個人の冷蔵庫へのアクセスを許可しますが、銀行口座へのアクセスは許可しません。

次のように実装できます

    class C1
    {
        private void MyMethod(double x, int i)
        {
            // some code
        }
        // the friend class would be able to call myMethod
        public void MyMethod(FriendClass F, double x, int i)
        {
            this.MyMethod(x, i);
        }
        //my friend class wouldn't have access to this method 
        private void MyVeryPrivateMethod(string s)
        {
            // some code
        }
    }
    class FriendClass
    {
        public void SomeMethod()
        {
            C1 c = new C1();
            c.MyMethod(this, 5.5, 3);
        }
    }

もちろん、コンパイラの警告が生成され、インテリセンスが損なわれます。しかし、それは仕事をします。

余談ですが、自信のあるプログラマーは、プライベート メンバーにアクセスせずにテスト ユニットを実行する必要があると思います。これは範囲外ですが、TDD について読んでみてください。ただし、それでもやりたい場合 (c++ のような友達がいる場合) のようなものを試してください

#if UNIT_TESTING
        public
#else
        private
#endif
            double x;

したがって、UNIT_TESTING を定義せずにすべてのコードを記述し、ユニット テストを実行する場合は、ファイルの最初の行に #define UNIT_TESTING を追加します (ユニット テストを実行するすべてのコードを #if UNIT_TESTING の下に記述します)。それは慎重に扱われるべきです。

単体テストは友達の使い方の悪い例だと思うので、友達が良いと思う理由を例に挙げます。破壊システム (クラス) があるとします。使用に伴い、遮断システムは摩耗し、改修が必要になります。ここで、認可されたメカニックのみが修理できるようにしたいと考えています。例を簡単にするために、メカニックは自分の個人的な(プライベート)ドライバーを使用して修正すると言います。そのため、メカニック クラスは、breakingSystem クラスのフレンドである必要があります。

于 2012-07-05T08:06:55.663 に答える