“内部”の実用的な使用法C#のキーワード
-
03-07-2019 - |
質問
C#の internal
キーワードの実際の使用方法を説明してください。
internal
修飾子が現在のアセンブリへのアクセスを制限していることは知っていますが、いつどのような状況でそれを使用すべきですか?
解決
同じアセンブリ内の他の多くのクラスからアクセスしたいが、他のアセンブリ内のコードがアクセスできないようにしたいユーティリティまたはヘルパークラス/メソッド。
MSDN (archive.org経由):
内部アクセスの一般的な使用法は、コンポーネントのグループが他のアプリケーションコードにさらされることなくプライベートな方法で連携できるため、コンポーネントベースの開発です。たとえば、グラフィカルユーザーインターフェイスを構築するためのフレームワークは、内部アクセスを持つメンバーを使用して連携するControlクラスとFormクラスを提供できます。これらのメンバーは内部にあるため、フレームワークを使用しているコードには公開されません。
と共にinternal修飾子を使用することもできます。 InternalsVisibleTo
アセンブリレベルの属性で「友人」を作成します;ターゲットアセンブリ内部クラスへの特別なアクセスが許可されているアセンブリ。
これは、テストするアセンブリの内部メンバーを呼び出すことができる単体テストアセンブリの作成に役立ちます。もちろん、他のアセンブリにはこのレベルのアクセスが許可されていないため、システムを解放してもカプセル化は維持されます。
他のヒント
BobがBigImportantClassを必要とする場合、Bobは、プロジェクトAを所有する人々にサインアップして、BigImportantClassが彼のニーズを満たすように書かれ、それが彼のニーズを満たすことを確認するためにテストされ、彼のニーズを満たすと文書化されることを保証する必要があり、彼のニーズを満たさないようにプロセスが変更されないことを保証するプロセスが導入されること。
クラスが内部クラスの場合、そのプロセスを実行する必要はありません。これにより、プロジェクトAの予算を節約し、他のことに費やすことができます。
内部のポイントは、それがボブの生活を難しくするということではありません。それは、プロジェクトAが機能、寿命、互換性などに関してどのような高価な約束をするかを制御できるということです。
内部を使用するもう1つの理由は、バイナリを難読化する場合です。難読化ツールは、内部クラスのクラス名をスクランブルしても安全であることを知っていますが、パブリッククラスの名前はスクランブルできません。既存の参照が壊れる可能性があるためです。
多数の複雑な機能を単純なパブリックAPIにカプセル化するDLLを作成している場合、“内部”パブリックに公開されないクラスメンバーで使用されます。
複雑さを隠すこと(別名カプセル化)は、品質の高いソフトウェアエンジニアリングの主要な概念です。
非マネージコードのラッパーを構築する場合、internalキーワードが頻繁に使用されます。
DllImportするC / C ++ベースのライブラリがある場合、これらの関数をクラスの静的関数としてインポートし、内部にすることができるため、ユーザーは元のAPIではなくラッパーにのみアクセスできます。何も台無しにできません。静的な関数は、必要な複数のラッパークラスに対して、アセンブリ内のあらゆる場所で使用できます。
Mono.Cairoをご覧ください。このアプローチを使用するcairoライブラリのラッパーです。
「できる限り厳密な修飾子として使用する」ことによって駆動される別のアセンブリから明示的にアクセスする必要があるまで、たとえば別のクラスのメソッドにアクセスする必要があるすべての場所で内部を使用するルール。
通常、アセンブリインターフェイスはクラスインターフェイスの合計よりも狭いため、使用する場所は非常に多くあります。
内部は使いすぎだと思います。他のコンシューマには公開しない特定のクラスにのみ特定の機能を公開するべきではありません。
これは私の意見では、インターフェースを破壊し、抽象化を破壊します。これは決して使用すべきではないというわけではありませんが、より良い解決策は、別のクラスにリファクタリングするか、可能であれば別の方法で使用することです。ただし、これは常に可能とは限りません。
それが問題を引き起こす可能性のある理由は、別の開発者があなたと同じアセンブリ内に別のクラスを構築する責任を負う可能性があるためです。内部構造があると、抽象化の明瞭さが低下し、誤用されると問題が発生する可能性があります。それを公開したのと同じ問題になります。他の開発者によって構築されている他のクラスは、外部クラスと同じように、まだ消費者です。クラスの抽象化とカプセル化は、外部クラスの保護だけでなく、すべてのクラスの保護を目的としています。
別の問題は、多くの開発者が、アセンブリの別の場所で使用する必要があり、その時点で内部にマークする必要があると考えていることです 。その後、別の開発者がそのことを考えるかもしれません。通常、最終的なニーズがあるまで、プライベートとしてマークします。
しかし、これのいくつかは主観的である可能性があり、決して使用すべきではないと言っているわけではありません。必要なときに使用してください。
私が覚えていないブログで、先週、おそらく週に興味深いものを見ました。基本的に私はこれを信用できませんが、いくつかの有用なアプリケーションがあるかもしれないと思いました。
抽象クラスを別のアセンブリに見せたいが、誰かがそれを継承できるようにしたくないとしましょう。 Sealedは、理由により抽象であるため機能しません。そのアセンブリの他のクラスは、それを継承します。他のアセンブリのどこかにParentクラスを宣言する必要があるため、プライベートは機能しません。
namespace Base.Assembly { public abstract class Parent { internal abstract void SomeMethod(); } //This works just fine since it's in the same assembly. public class ChildWithin : Parent { internal override void SomeMethod() { } } } namespace Another.Assembly { //Kaboom, because you can't override an internal method public class ChildOutside : Parent { } public class Test { //Just fine private Parent _parent; public Test() { //Still fine _parent = new ChildWithin(); } } }
ご覧のように、継承することなく誰かがParentクラスを効果的に使用できます。
この例には、Assembly1.csとAssembly2.csの2つのファイルが含まれています。最初のファイルには、内部基本クラスBaseClassが含まれています。 2番目のファイルでは、BaseClassをインスタンス化しようとするとエラーが発生します。
// Assembly1.cs
// compile with: /target:library
internal class BaseClass
{
public static int intM = 0;
}
// Assembly1_a.cs
// compile with: /reference:Assembly1.dll
class TestAccess
{
static void Main()
{
BaseClass myBase = new BaseClass(); // CS0122
}
}
この例では、例1で使用したのと同じファイルを使用し、BaseClassのアクセシビリティレベルを public に変更します。また、メンバーIntMのアクセシビリティレベルを internal に変更します。この場合、クラスをインスタンス化できますが、内部メンバーにアクセスできません。
// Assembly2.cs
// compile with: /target:library
public class BaseClass
{
internal static int intM = 0;
}
// Assembly2_a.cs
// compile with: /reference:Assembly1.dll
public class TestAccess
{
static void Main()
{
BaseClass myBase = new BaseClass(); // Ok.
BaseClass.intM = 444; // CS0117
}
}
ソース: http: //msdn.microsoft.com/en-us/library/7c5ka91b(VS.80).aspx
内部の非常に興味深い使用-内部メンバーはもちろん、それが宣言されているアセンブリに限定されている-は、「友人」を取得しています。それからある程度の機能。フレンドメンバとは、宣言されたアセンブリ以外の特定のアセンブリにのみ表示されるものです。 C#にはフレンドのサポートが組み込まれていませんが、CLRには組み込まれています。
InternalsVisibleToAttribute を使用して、フレンドアセンブリを宣言すると、フレンドアセンブリ内からのすべての参照は、宣言しているアセンブリの内部メンバーをフレンドアセンブリのスコープ内でパブリックとして扱います。これに関する問題は、すべての内部メンバーが表示されることです。選択して選択することはできません。
InternalsVisibleToの適切な使用法は、さまざまな内部メンバーを単体テストアセンブリに公開し、それらのメンバーをテストするための複雑なリフレクションの回避策を不要にすることです。すべての内部メンバーが表示されることはそれほど問題ではありませんが、このアプローチをとると、クラスインターフェイスがかなり混乱し、宣言アセンブリ内のカプセル化が台無しになる可能性があります。
現在のアセンブリのスコープ内でアクセスできるようにする必要があるメソッドやクラスなどがある場合。
たとえば、DALにはORMがありますが、オブジェクトをビジネスレイヤーに公開しないでください。すべての対話は、静的メソッドを介して実行し、必要なパラメーターを渡す必要があります。
経験則として、2種類のメンバーがあります:
- パブリックサーフェス:外部アセンブリから表示(パブリック、保護、および内部保護): 呼び出し元は信頼されないため、パラメーターの検証、メソッドのドキュメントなどが必要です。
- プライベートサーフェス:外部アセンブリ(プライベートクラス、内部クラス、または内部クラス)からは見えません: 呼び出し元は一般に信頼されているため、パラメーターの検証、メソッドのドキュメントなどは省略できます。
ノイズの低減、公開する型が少ないほど、ライブラリはよりシンプルになります。 改ざん防止/セキュリティは別の方法です(ただし、Reflectionが勝つことができます)。
内部クラスを使用すると、アセンブリのAPIを制限できます。これには、APIを理解しやすくするなどの利点があります。
また、アセンブリにバグが存在する場合、修正により重大な変更が導入される可能性は低くなります。内部クラスがなければ、クラスのパブリックメンバーを変更すると重大な変更になると想定する必要があります。内部クラスを使用すると、パブリックメンバーを変更しても、アセンブリ(およびInternalsVisibleTo属性で参照されるアセンブリ)の内部APIのみが破損すると想定できます。
クラスレベルおよびアセンブリレベルでのカプセル化が好きです。これに反対する人もいますが、機能が利用可能であることを知ってうれしいです。
データバックエンドにLINQ-to-SQLを使用するプロジェクトがあります。私には2つの主要な名前空間があります。BizとDataです。 LINQデータモデルはDataに存在し、「内部」とマークされています。 Biz名前空間には、LINQデータクラスをラップするパブリッククラスがあります。
つまり、 Data.Client
と Biz.Client
があります。後者は、データオブジェクトのすべての関連プロパティを公開します。例:
private Data.Client _client;
public int Id { get { return _client.Id; } set { _client.Id = value; } }
Bizオブジェクトには、プライベートコンストラクター(ファクトリメソッドの使用を強制する)と、次のような内部コンストラクターがあります。
internal Client(Data.Client client) {
this._client = client;
}
これはライブラリ内のどのビジネスクラスでも使用できますが、フロントエンド(UI)にはデータモデルに直接アクセスする方法がないため、ビジネスレイヤーは常に仲介者として機能します。
internal
を実際に使用したのはこれが初めてであり、非常に有用であることが証明されています。
クラスのメンバーを internal
にすることが理にかなっている場合があります。 1つの例は、クラスのインスタンス化方法を制御する場合です。クラスのインスタンスを作成するためのある種のファクトリを提供するとしましょう。コンストラクターを internal
にすると、ファクトリー(同じアセンブリーにある)はクラスのインスタンスを作成できますが、そのアセンブリーの外部のコードは作成できません。
ただし、特定の理由がない限り、クラスまたはメンバーを internal
にすることには意味がありません。それらを public
にするか、または< code> private 特定の理由なし。
内部キーワードを使用したことがあるのは、製品のライセンス確認コードだけです;-)
内部キーワードの使用法の1つは、アセンブリのユーザーからの具体的な実装へのアクセスを制限することです。
オブジェクトを構築するためのファクトリまたはその他の中心的な場所がある場合、アセンブリのユーザーはパブリックインターフェイスまたは抽象基本クラスを処理するだけで済みます。
また、内部コンストラクタを使用すると、パブリッククラスがインスタンス化される場所とタイミングを制御できます。
これについては、通常、Listオブジェクトをアセンブリの外部ユーザーに公開せず、IEnumerableを公開することをお勧めします。ただし、配列構文とその他のすべてのListメソッドを取得できるため、アセンブリ内でListオブジェクトを使用する方がはるかに簡単です。そのため、通常、アセンブリ内で使用されるListを公開する内部プロパティがあります。
このアプローチについてのコメントを歓迎します。
誰かがあなたのプロジェクトの名前空間を見ると、 public
として定義されたクラスが自動的にインテリセンスに表示されることに注意してください。 APIの観点からは、プロジェクトのユーザーに使用できるクラスのみを表示することが重要です。 internal
キーワードを使用して、見たくないものを隠します。
プロジェクトAの Big_Important_Class
をプロジェクト外で使用する場合、 internal
とマークしないでください。
ただし、多くのプロジェクトでは、多くの場合、実際にはプロジェクト内での使用のみを目的としたクラスがあります。たとえば、パラメータ化されたスレッド呼び出しへの引数を保持するクラスがある場合があります。これらの場合、意図しないAPIの変更から身を守る以外の理由がない限り、それらを internal
としてマークする必要があります。
アイデアは、ライブラリを設計するときは、外部から(ライブラリのクライアントが)使用することを目的としたクラスのみを公開することです。このようにして、
- 将来のリリースで変更される可能性があります(公開されている場合は、クライアントコードが破損する可能性があります)
- クライアントには役に立たず、混乱を招く可能性があります
- 安全ではありません(そのため、不適切な使用はライブラリをひどく破損させる可能性があります)
etc。
社内のソリューションを開発する場合、内部要素を使用することはそれほど重要ではありません。通常、クライアントは常にあなたと連絡を取り、コードにアクセスするからです。ただし、ライブラリ開発者にとっては非常に重要です。
オブジェクト指向のパラダイムにきれいに適合しないクラスまたはメソッドがあり、それらが危険なことを行い、制御下にある他のクラスおよびメソッドから呼び出す必要があり、許可したくない場合他の人が使用します。
public class DangerousClass {
public void SafeMethod() { }
internal void UpdateGlobalStateInSomeBizarreWay() { }
}