C# で同等の Friend クラス

Abdul Mateen 2023年10月12日
  1. C++ のフレンド クラス
  2. Friend クラスに相当する C# を使用する
C# で同等の Friend クラス

このチュートリアルでは、C# で同等のフレンド クラスについて説明します。 最初に、C++ の例を使用してプログラミングでのフレンド クラスとその使用法について説明します。

次に、C# に相当するフレンド クラスがない場合、代替手段を見ていきます。

C++ のフレンド クラス

主にオブジェクト指向プログラミングに適用される重要なプログラミング機能は、データ隠蔽です。 データ メンバーをクラスの外部から非表示にすることは、データ メンバーをクラス内でのみ更新できることを意味します。

したがって、データ メンバーの値を変更できるのは 1つのクラス (データ メンバーを保持する) だけであると言えます。

データを非表示にすることで、変更するクラスをテストする必要があるため、テストの範囲を縮小します。 他のクラスは、あるクラスのプライベート データ メンバーにアクセスできません。 したがって、コードが変更されていない場合はテストする必要はありません。

長くて複雑なコードでは、データの隠蔽が少し損なわれることがあります。 クラスは、いくつかの関数またはクラスをフレンドとして宣言できます。

それに応じて、friend 関数と friend クラスはプライベート データ メンバーにアクセスできます。

友情の構文は次のとおりです。

class ABC {
  ... friend class XYZ;
  friend int change_data_member(int, char);
  ...

クラス ABC は、3 行目でクラス XYZ をフレンドとして宣言します。 4 行目は、friend メソッドを宣言するための構文を示しています。

ここで、クラス ABCchange_data_member メソッドを friend 関数として宣言しています。

クラス ABC からフレンドシップ フラグを受け取った後、クラス XYZ のすべての関数は、クラス ABC のプライベート データ メンバーにアクセスできます。

ただし、関数にフレンドシップを付与する場合、フレンドシップの範囲は制限されます。 friend 関数 (つまり、friend として宣言されている) だけが、友情を与えるクラスのメンバーのプライベート データにアクセスできます。

C++ での友情の概念の実装を見てみましょう。

#include <iostream>
using namespace std;
class ABC {
  int privateMember;

 public:
  ABC(int privateMember) { this->privateMember = privateMember; }
  friend int getPrivateMember(ABC &);
  friend void setPrivateMember(ABC &, int);
};
int getPrivateMember(ABC &obj) { return obj.privateMember; }
void setPrivateMember(ABC &obj, int privateMember) {
  obj.privateMember = privateMember;
}
int main() {
  ABC abc(5);
  cout << "Private Member: " << getPrivateMember(abc) << '\n';
  setPrivateMember(abc, 7);
  cout << "Private Member: " << getPrivateMember(abc) << '\n';
  return 0;
}

このコードでは、クラス ABC にプライベート データ メンバー privateMember があります。 クラス本体の最後の 2 行では、2つの非クラス関数 getPrivateMembersetPrivateMember にフレンドシップが付与されています。

このコードの出力は以下のとおりです。

Private Member: 5
Private Member: 7

プライベート データ メンバーは、入力と出力の両方でアクセスされます。 以下は friend クラスの例です:

#include <iostream>
using namespace std;
class ABC {
  int privateMember;
  friend class DEF;
};
class DEF {
  ABC abc;

 public:
  int getPrivateMember() { return abc.privateMember; }
  void setPrivateMember(int privateMember) {
    abc.privateMember = privateMember;
  }
};
int main() {
  DEF def;
  def.setPrivateMember(12);
  cout << "Private Member: " << def.getPrivateMember() << '\n';
  return 0;
}

クラス ABC の 2 行目では、クラス DEF に友情が付与されます。 フレンドシップ ステータスを持つことで、クラス DEFgetPrivateMember および setPrivateMember メソッドは、クラス ABC のプライベート メンバーにアクセスできます。

このコードの出力は以下のとおりです。

Private Member: 12

この friend 関数または friend クラスの概念は、C# ではなく C++ で利用できます。 ただし、非メンバー データ関数にアクセスできる方法がいくつかあります。

Friend クラスに相当する C# を使用する

C# の一部の非メンバー関数から任意のクラスのデータ メンバーにアクセスするには、さまざまな方法があります。 これについて、1つずつ説明しましょう。

ネストされたクラスの使用

C++ の friend クラスに最も近い C# の同等/代替手段は、ネストされた/内部クラスを作成することです。 ネストされたクラスは、外部クラスのプライベート データ メンバーにアクセスできます。

次の例では、Outer_class へのネストされたクラスとして Inner_class クラスを作成します。 包含クラスのスコープ内にある場合、Inner_classOuter_class のプライベート メンバーにアクセスでき、それによって friend クラスを模倣します。

using System;
public class Outer_class {
  int privateMember;
  Outer_class(int privateMember) {
    this.privateMember = privateMember;
  }
  public class Inner_class {
    public void innerClassFunction() {
      Outer_class obj = new Outer_class(5);
      Console.WriteLine("Private Member: " + obj.privateMember);
    }
  }
}
public class Driver {
  static public void Main() {
    Outer_class.Inner_class obj = new Outer_class.Inner_class();
    obj.innerClassFunction();
  }
}

上記のコードには、1つの外部クラスと 1つの内部クラスがあります。 変数 privateMember は外部クラスのプライベート メンバーです。

Inner_class 内では、外部クラスの privateMember であるプライベート データ メンバーにアクセスしています。

次に、Inner_class のオブジェクトを作成した Driver クラスがあります。 Inner_class オブジェクトを使用して、Inner_class 内で innerClassFunction を呼び出します。

出力:

Private Member: 5

上記のコードは問題なくビルドおよび実行されますが、以下の例に示すように、部分クラスの概念を使用して、Inner_classOuter_class から分離することをお勧めします。

using System;
public partial class Outer_class {
  int privateMember;
  Outer_class(int privateMember) {
    this.privateMember = privateMember;
  }
}
public partial class Outer_class {
  public class Inner_class {
    public void innerClassFunction() {
      Outer_class obj = new Outer_class(5);
      Console.WriteLine("Private Member: " + obj.privateMember);
    }
  }
}
public class Driver {
  static public void Main() {
    Outer_class.Inner_class obj = new Outer_class.Inner_class();
    obj.innerClassFunction();
  }
}

Outer_class の 2 番目の部分クラス定義には、関数 innerClassFunction があります。 この innerClassFunction メソッドは、外部クラスのオブジェクトを作成します。

Outer_class オブジェクトを作成する際に、コンストラクター パラメーターの引数として 5 を渡します。 このコンストラクター パラメーターは、次の出力に示すように、外部クラスの最初の部分定義のデータ メンバーの値になります。

Private Member: 5

部分クラスは、複数の人がクラスの異なる部分を作成する大規模なプロジェクトで重要です。

ここでは、この部分クラス オプションを利用して、外部クラスの別の部分部分として Inner_class を内部に記述しています。 この部分スキームは、部分単位を分離したままにしておくことができますが、それらを単一の単位として使用できます。

このスキームの主な利点は、実際のクラス定義が Inner_class 定義を書く人に公開されないことです。

反射オブジェクト

Reflection のオブジェクトを使用して、実行時の型情報を取得できます。

Reflection オブジェクトを使用して、プログラムの実行中に Type 情報を取得できます。 System.Reflection 名前空間を使用して、実行中のプログラムのメタデータにアクセスできます。

System.Reflection は、実行時に役立つ情報を提供し、さまざまな型、値、およびクラス オブジェクトをプログラムに追加するのに役立ちます。

さらに、System.Reflection 名前空間を使用してメソッドのハンドルを取得し、クラスのプライベート メソッドをテストできます。

このようにして、クラスのプライベート メソッドを呼び出すことができます。 したがって、friend クラスを複製できます。

たとえば、プライベート メソッド callable_fun() を持つ次のクラスを想定します。

public class Class1 {
  private char callable_fun() {
    return 'C';
  }
}

プライベート関数 callable_fun をテストするには、次のコードを記述する必要があります。

using System.Reflection;
public class Driver {
  static public void Main() {
    Class1 c = new Class1();
    Type class1Type = c.GetType();
    MethodInfo callMeMethod =
        class1Type.GetMethod("callable_fun", BindingFlags.Instance | BindingFlags.NonPublic);
    char result = (char)callMeMethod.Invoke(c, null);
    Console.WriteLine("Output:" + result);
  }
}

このコードの出力は以下のとおりです。

Output:C

Visual Studio Team System を使用している場合は、メソッドを右クリックして [Create Unit Tests…] を選択することで、プライベート アクセサーを含むプロキシ クラスを VS に自動的に生成させることができます。

InternalsVisibleToAttribute クラスの使用

このクラスは、通常は現在のアセンブリ内でのみ表示される型について、指定されたアセンブリへの可視性を高めるのに役立ちます。

// Syntax
[System.AttributeUsage(System.AttributeTargets.Assembly, AllowMultiple = true, Inherited = false)]
public sealed class InternalsVisibleToAttribute : Attribute

2つの署名されていないアセンブリがある場合、InternalsVisibleToAttribute クラスは、別の署名されていないアセンブリが 1つの署名されていないアセンブリのプライベート メンバーにアクセスするのに役立ちます。 1つのアセンブリの名前 (friend クラス/アセンブリの名前を持つ) が引数として渡されます。

次に、InternalsVisibleToAttribute を使用して別の署名されていないアセンブリのプライベート メンバーにアクセスする例を見てみましょう。 ここで、SecondClassFirstClass のプライベート メンバーにアクセスします。

以下のコードを参照してください。

using System;
using System.Runtime.CompilerServices;
using Utilities.Example;

[assembly:InternalsVisibleToAttribute("FirstClass")]

namespace Utilities.Example {
  public class FirstClass {
    internal static int InnerMethod(int x) {
      return x * x;
    }
  }
}

次の例は、SecondClass アセンブリのソース コードを示しています。

public class SecondClass {
  public static void Main() {
    Console.WriteLine(FirstClass.InnerMethod(10));
  }
}

このコードの出力は 100 です。

アセンブリに署名した場合、ディレクトリ パスとファイル名拡張子は必要ありません。

次の例では、InternalsVisibleToAttribute を使用して、別の署名済みアセンブリから見える署名済みアセンブリ内の innerMethod という名前のプライベート メソッドを呼び出します。 内部 innerMethod メソッドを含む FirstClass クラスを定義します。

using System;
using System.IO;
using System.Runtime.CompilerServices;

[assembly:InternalsVisibleTo(
    "Friend_Assembly, PublicKey=002400000480000094" + "0000000602000000240000525341310004000" +
    "001000100bf8c25fcd44838d87e245ab35bf7" + "3ba2615707feea295709559b3de903fb95a93" +
    "3d2729967c3184a97d7b84c7547cd87e435b5" + "6bdf8621bcb62b59c00c88bd83aa62c4fcdd4" +
    "712da72eec2533dc00f8529c3a0bbb4103282" + "f0d894d5f34e9f0103c473dce9f4b457a5dee" +
    "fd8f920d8681ed6dfcb0a81e96bd9b176525a" + "26e0b3")]

public class FirstClass {
  internal static int innerMethod(int x) {
    return x * x;
  }
}

次の例が、Friend_Assembly という名前の厳密な名前のアセンブリにコンパイルされているとします。 FirstClass の内部メソッドは innerMethod を正常に呼び出しますが、メソッドは FirstClass の内部にあります。

public class SecondClass {
  public static void Main() {
    int result = FirstClass.innerMethod(10);
    Console.WriteLine(result);
  }
}

このコードの出力は、やはり 100 です。

C# の C++ friend クラスに相当するさまざまなクラスを紹介しました。 読者は、要件に応じてどれでも使用できます。

関連記事 - Csharp Class