実際のサムラむのカプセル化、たたはCの内郚キヌワヌドに関連付けられたニュアンス

プロロヌグ内郚は新しいパブリックです


私たちは皆、すべおが正しく行われるプロゞェクトを倢芋おいたした。 ずおも自然に思えたす。 良いコヌドを曞くこずの可胜性に぀いお知るずすぐに、同じコヌドに぀いお簡単に読んだり修正したりできる䌝説が聞こえるずすぐに、それですぐに点灯したす。


画像

このようなプロゞェクトは私の人生で起こりたした。 もう䞀぀。 そしお、私は任意の監督の䞋でそれを行いたす。 したがっお、私がしたかっただけでなく、すべおを正しくしなければなりたせんでした。 「正しい」こずの1぀は、「カプセル化を尊重し、最倧限に近いこずです。垞に開く時間があるため、閉じるには遅すぎたす」。 したがっお、できる限り、パブリックではなくクラスに内郚アクセス修食子を䜿甚し始めたした。 そしおもちろん、あなたが新しい蚀語機胜を積極的に䜿甚し始めるず、いく぀かのニュアンスが生じたす。 それらに぀いお順番に、私は䌝えたいです。


攻撃的な基本ヘルプ

思い出させおラベルを付けるだけです。


  • アセンブリは、.NETでの展開の最小単䜍であり、基本的なコンパむル単䜍の1぀です。 珟状では、.dllたたは.exeです。 圌らは、モゞュヌルず呌ばれるいく぀かのファむルに分割できるず蚀っおいたす。
  • public-アクセス修食子。これは、マヌクされたすべおのナヌザヌがアクセスできるこずを意味したす。
  • internal-アクセス修食子。぀たり、アセンブリ内でのみ䜿甚可胜ずマヌクされたす。
  • protected-マヌクされおいるクラスの盞続人のみがアクセスできるようにマヌクされおいるこずを瀺すアクセス修食子。
  • private-配眮されおいるクラスでのみ䜿甚可胜ずマヌクされおいるこずを瀺すアクセス修食子。 そしお誰もいない。


単䜓テストずフレンドリヌビルド


C ++では、フレンドリヌクラスのような奇劙な機胜がありたした。 クラスをフレンドずしお割り圓おるず、クラス間のカプセル化の境界が消去されたした。 これは、C ++で最も奇劙な機胜ではないず思われたす。 おそらく、最も奇劙なトップ10も含たれおいたせん。 しかし、いく぀かのクラスを緊密にリンクしお足元を撃぀こずは、どういうわけか簡単すぎお、この機胜に適したケヌスを芋぀けるこずは非垞に困難です。


.NETには、フレンドリヌなアセンブリがあり、䞀皮の再考があるこずを知るのは、さらに驚くべきこずでした。 ぀たり、あるアセンブリに、別のアセンブリの内郚ロックの埌ろに隠れおいるものを衚瀺させるこずができたす。 これを知ったずき、私はいくらか驚きたした。 さお、どのように、なぜですか ポむントは䜕ですか 分離に関係する2぀のアセンブリを誰がしっかりずバむンドしたすか 䞍可解な状況で公開された堎合、この蚘事では考慮したせん。


そしお、同じプロゞェクトで、私は本物のsaの道の分岐の1぀であるナニットテストを孊び始めたした。 たた、颚氎では単䜓テストを別のアセンブリで行う必芁がありたす。 同じ颚氎でアセンブリ内に隠すこずができるすべおのものに぀いおは、アセンブリ内に隠す必芁がありたす。 私は非垞に䞍快な遞択に盎面したした。 テストは暪に䞊んで、有甚なコヌドず䞀緒にクラむアントに送られるか、すべおがキヌワヌドpublicでカバヌされたす。これは、パンが湿っおいる時間です。


そしお、ここで、私の蚘憶のどこかから、友奜的な集䌚に぀いお䜕かが埗られたした。 アセンブリ "YourAssemblyName"がある堎合、次のように蚘述できるこずがわかりたした。


[assembly: InternalsVisibleTo("YourAssemblyName.Tests")] 

たた、アセンブリ「YourAssemblyName.Tests」では、「YourAssemblyName」の内郚キヌワヌドでマヌクされたものが衚瀺されたす。 この行は、AssemblyInfo.csで入力できたす。AssemblyInfo.csは、VSがそのような属性を保存するために特別に䜜成したす。


虐埅を基本的なヘルプに戻す
.NETでは、抜象、パブリック、内郚、静的などの既に組み蟌たれおいる属性やキヌワヌドに加えお、独自の属性を䜜成できたす。 そしお、フィヌルド、プロパティ、クラス、メ゜ッド、むベント、アセンブリ党䜓など、必芁なものにそれらを掛けたす。 Cでは、これを行うには、単玔に属性名を角かっこで囲んで蚘述したす。 「アセンブリはここから始たる」ずいうコヌドのどこにも盎接瀺されおいないため、䟋倖はアセンブリ自䜓です。 そこで、属性名の前にアセンブリを远加したす。

したがっお、オオカミはうんざりしたたたで、矊は安党であり、可胜なものはすべおアセンブリ内にただ隠れおいたす。ナニットテストは期埅どおり別のアセンブリに䜏んでおり、私がかろうじお思い出した機胜にはそれを䜿甚する理由がありたす。 おそらく唯䞀の既存の理由。


私は1぀の重芁な点をほずんど忘れおいたした。 属性アクションInternalsVisibleToは䞀方向です。


保護されおいる<内郚


状況AずBはパむプの䞊に座っおいたした。


 using System; namespace Pipe { public class A { public String SomeProperty { get; protected set; } } internal class B { //ERROR!!! The accessibility modifier of the 'B.OtherProperty.set' accessor must be more //restrictive than the property or indexer 'B.OtherProperty' internal String OtherProperty { get; protected set; } } } 

Aはアセンブリの倖郚で䜿甚されないため、コヌドレビュヌプロセスで砎壊されたしたが、䜕らかの理由でパブリックアクセス修食子を持぀こずができるため、Bはコンパむル゚ラヌを匕き起こし、最初の数分間で混乱に陥るこずがありたした。


基本的に、゚ラヌメッセヌゞは論理的です。 プロパティアクセサヌは、プロパティ自䜓を公開するこずはできたせん。 コンパむラがこれにヘッダヌを提䟛するず、誰でも理解しお反応したす。


 internal String OtherProperty { get; public set; } 

しかし、この行に察する䞻匵はすぐに脳を砎壊したす


 internal String OtherProperty { get; protected set; } 

この行に぀いお䞍満はないこずに泚意しおください。


 internal String OtherProperty { get; private set; } 

よく考えなければ、次の階局が頭の䞭に構築されたす。


 public > internal > protected > private 

そしお、この階局はうたく機胜しおいるようです。 䞀箇所を陀きたす。 where internal> protected。 コンパむラヌの䞻匵の本質を理解するために、内郚および保護によっお課せられる制限を思い出したしょう。 内郚-アセンブリ内郚のみ。 保護-盞続人のみ。 盞続人に泚意しおください。 クラスBがパブリックずしおマヌクされおいる堎合、別のアセンブリでその子孫を定矩できたす。 そしお、蚭定されたアクセサは、プロパティ党䜓に存圚しない堎所に実際にアクセスしたす。 Cコンパむラは劄想的であるため、そのような可胜性さえ蚱すこずはできたせん。


圌に感謝したすが、継承者にアクセサぞのアクセスを蚱可する必芁がありたす。 そしお、特にそのような堎合のために、保護された内郚アクセス修食子がありたす。


このヘルプはそれほど䞍快ではありたせん
  • protected internal-マヌクされたものがアセンブリ内で、 たたはマヌクされたものが存圚するクラスの盞続人が利甚できるこずを瀺すアクセス修食子。


したがっお、コンパむラにこのプロパティの䜿甚を蚱可し、盞続人に蚭定するには、これを行う必芁がありたす。


 using System; namespace Pipe { internal class B { protected internal String OtherProperty { get; protected set; } } } 

そしお、アクセス修食子の正しい階局は次のようになりたす。


 public > protected internal > internal/protected > private 

むンタヌフェヌス


したがっお、状況A、I、Bはパむプの䞊に座っおいたした。


 namespace Pipe { internal interface I { void SomeMethod(); } internal class A : I { internal void SomeMethod() { //'A' does not implement interface member 'I.SomeMethod()'. //'A.SomeMethod()' cannot implement an interface member because it is not public. } } internal class B : I { internal void SomeMethod() { //'B' does not implement interface member 'I.SomeMethod()'. //'B.SomeMethod()' cannot implement an interface member because it is not public. } } } 

私たちは正確に座っお、アセンブリの倖偎に干枉したせんでした。 しかし、それらはコンパむラによっお拒吊されたした。 ここで、クレヌムの本質ぱラヌメッセヌゞから明らかです。 むンタヌフェむスの実装は開いおいる必芁がありたす。 むンタヌフェむス自䜓が閉じおいおも。 むンタヌフェヌスの実装ぞのアクセスをその可甚性に結び付けるこずは論理的ですが、そうではないものはそうではありたせん。 むンタヌフェむスの実装はパブリックでなければなりたせん。


そしお、2぀の方法がありたす。 最初歯のきしみず歯ぎしりを通しお、むンタヌフェむスの実装にパブリックアクセス修食子を掛けたす。 2番目むンタヌフェむスの明瀺的な実装。 次のようになりたす。


 namespace Pipe { internal interface I { void SomeMethod(); } internal class A : I { public void SomeMethod() { } } internal class B : I { void I.SomeMethod() { } } } 

2番目の堎合、アクセス修食子はありたせん。 この堎合、メ゜ッドの実装は誰に利甚可胜ですか 誰も蚀わないようにしたしょう。 䟋で衚瀺する方が簡単です


 B b = new B(); //'B' does not contain a definition for 'SomeMethod' and no accessible extension method //'SomeMethod' accepting a first argument of type 'B' could be found //(are you missing a using directive or an assembly reference?) b.SomeMethod(); //OK (b as I).SomeMethod(); 

むンタヌフェむスIの明瀺的な実装ずは、倉数をタむプIに明瀺的にキャストするたで、このむンタヌフェむスを実装するメ゜ッドがないこずを意味したす。 b as I.SomeMethodを曞くたびにオヌバヌロヌドになる可胜性がありたす。 LikeIb.SomeMethod。 そしお、私はこれを回避する2぀の方法を芋぀けたした。 私は自分のこずを考え、正盎にグヌグルでグヌグルで怜玢したした。


最初の方法は工堎です


  internal class Factory { internal I Create() { return new B(); } } 

さお、たたはこのニュアンスを隠すこずができる他のパタヌン。


方法2-拡匵メ゜ッド


  internal static class IExtensions { internal static void SomeMethod(this I i) { i.SomeMethod(); } } 

驚くべきこずに、それは動䜜したす。 次の行ぱラヌのスロヌを停止したす。


 B b = new B(); b.SomeMethod(); 

IntelliSenseがVisual Studioで、むンタヌフェむスを明瀺的に実装するメ゜ッドではなく、拡匵メ゜ッドに䌝えるように、呌び出しが行われたす。 そしお誰も圌らに立ち向かうこずを犁じたせん。 たた、むンタヌフェヌス拡匵メ゜ッドは、そのすべおの実装で呌び出すこずができたす。


ただし、1぀泚意点がありたす。 クラス自䜓の内郚では、thisキヌワヌドを介しおこのメ​​゜ッドにアクセスする必芁がありたす。そうしないず、コンパむラは拡匵メ゜ッドを参照する必芁があるこずを理解できたせん。


  internal class B : I { internal void OtherMethod() { //Error!!! SomeMethod(); //OK this.SomeMethod(); } void I.SomeMethod() { } } 

そのため、そうすべきではない堎所にパブリックたたはパブリックがありたすが、そこには害がなく、各内郚むンタヌフェむスに少し䜙分なコヌドが含たれおいるようです。 あなたの奜みに合わせお小さな悪を遞択しおください。


リフレクション


リフレクションを䜿甚しおコンストラクタヌを芋぀けようずしたずきに、これは痛い思いをしたした。もちろん、内郚クラスで内郚ずしおマヌクされおいたした。 そしお、リフレクションは公開されないものを䜕も提䟛しないこずが刀明したした。 そしお、これは原則ずしお論理的です。


たず、熟考したす。賢い人が賢い本に曞いたこずを正しく芚えおいれば、それはアセンブリメタデヌタで情報を芋぀けるこずです。 理論的には、これはあたりにも倚くを䞎えるべきではありたせん少なくずもそうは思いたした。 第二に、リフレクションの䞻な甚途は、プログラムを拡匵可胜にするこずです。 あなたは郚倖者にある皮のむンタヌフェヌスを提䟛したすおそらくむンタヌフェヌスの圢でさえ、fiy-ha。 そしお、圌らはそれを実装し、プラグむン、MOD、拡匵機胜を倖出先でロヌドされたアセンブリの圢で提䟛し、そこからリフレクションがそれらを取埗したす。 そしお、それ自䜓で、APIは公開されたす。 ぀たり、反射を通しお内郚を芋るこずは、実甚的な芳点からは技術的にも無意味でもありたせん。


曎新する ここのコメントでは、明瀺的に芁求した堎合、䞀般的にすべおを反映するこずができたす。 瀟内でも、プラむベヌトでも。 䜕らかのコヌド分析ツヌルを䜜成しおいない堎合は、そうしないようにしおください。 以䞋のテキストは、未解決のメンバヌタむプを探しおいる堎合に関連しおいたす。 そしお䞀般的に、コメントを枡さないでください、倚くの興味深いこずがありたす。


これはリフレクションで終了するこずもできたしたが、A、I、Bがパむプに座っおいた前の䟋に戻りたしょう。


 namespace Pipe { internal interface I { void SomeMethod(); } internal static class IExtensions { internal static void SomeMethod(this I i) { i.SomeMethod(); } } internal class A : I { public void SomeMethod() { } internal void OtherMethod() { } } internal class B : I { internal void OtherMethod() { } void I.SomeMethod() { } } } 

クラスAの䜜成者は、内郚クラスのメ゜ッドがパブリックずしおマヌクされおいれば、コンパむラヌが苊痛を感じないようにし、コヌドをわざわざする必芁がない堎合、䜕も悪いこずはないず刀断したした。 むンタヌフェむスは内郚ずしおマヌクされ、それを実装するクラスは内郚ずしおマヌクされ、倖郚からは、パブリックずしおマヌクされたメ゜ッドに到達する方法がないようです。


そしおドアが開き、反射が静かに朜入したす。


 using Pipe; using System; using System.Reflection; namespace EncapsulationTest { public class Program { public static void Main(string[] args) { FindThroughReflection(typeof(I), "SomeMethod"); FindThroughReflection(typeof(IExtensions), "SomeMethod"); FindThroughReflection(typeof(A), "SomeMethod"); FindThroughReflection(typeof(A), "OtherMethod"); FindThroughReflection(typeof(B), "SomeMethod"); FindThroughReflection(typeof(B), "OtherMethod"); Console.ReadLine(); } private static void FindThroughReflection(Type type, String methodName) { MethodInfo methodInfo = type.GetMethod(methodName); if (methodInfo != null) Console.WriteLine($"In type {type.Name} we found {methodInfo}"); else Console.WriteLine($"NULL! Can't find method {methodName} in type {type.Name}"); } } } 

必芁に応じお、このコヌドを研究し、スタゞオに送り蟌みたす。 ここでは、リフレクションを䜿甚しお、すべおの皮類のパむプ名前空間Pipeからすべおのメ゜ッドを芋぀けおみたす。 そしお、それは私たちに䞎える結果です


タむプIでは、Void SomeMethodが芋぀かりたした
NULL IExtensions型にメ゜ッドSomeMethodが芋぀かりたせん
タむプAでVoid SomeMethodが芋぀かりたした
NULL タむプAにメ゜ッドOtherMethodが芋぀かりたせん
NULL タむプBにメ゜ッドSomeMethodが芋぀かりたせん
NULL タむプBにメ゜ッドOtherMethodが芋぀かりたせん

MethodInfo型のオブゞェクトを䜿甚するず、芋぀かったメ゜ッドを呌び出すこずができるずすぐに蚀わなければなりたせん。 ぀たり、反射が䜕かを芋぀けた堎合、玔粋に理論的にカプセル化に違反する可胜性がありたす。 そしお、䜕かが芋぀かりたした。 最初に、クラスAからの同じpublic void SomeMethod。それは、他に䜕を蚀うのか、予想されおいたした。 この譲歩はただ結果をもたらすかもしれたせん。 次に、むンタヌフェヌスIのSomeMethodを無効にしたす。これはすでに興味深いものです。 どのようにロックアップしおも、むンタヌフェむスに配眮された抜象メ゜ッドたたはCLRが実際にそこに配眮するものは実際には開いおいたす。 したがっお、別の段萜で結論が䞋されたす。


どのタむプのSystem.Typeタむプを配垃するかを泚意深く芋おください。


しかし、これら2぀の方法にはもう1぀埮劙な違いがありたすので、怜蚎したいず思いたす。 内郚むンタヌフェむスメ゜ッドず内郚クラスのパブリックメ゜ッドは、リフレクションを䜿甚しお芋぀けるこずができたす。 合理的な人ずしお、私は圌らがメタデヌタに該圓するず結論付けたす。 経隓豊富な人ずしお、私はこの結論を怜蚌したす。 ILDasmはこれを支揎したす。


パむプのメタデヌタのりサギの穎を芗いおみおください

アセンブリはリリヌスで組み立おられたした


TypeDef #2 (02000003)
-------------------------------------------------------
TypDefName: Pipe.I (02000003)
Flags : [NotPublic] [AutoLayout] [Interface] [Abstract] [AnsiClass] (000000a0)
Extends : 01000000 [TypeRef]
Method #1 (06000004)
-------------------------------------------------------
MethodName: SomeMethod (06000004)
Flags : [Public] [Virtual] [HideBySig] [NewSlot] [Abstract] (000005c6)
RVA : 0x00000000
ImplFlags : [IL] [Managed] (00000000)
CallCnvntn: [DEFAULT]
hasThis
ReturnType: Void
No arguments.

TypeDef #3 (02000004)
-------------------------------------------------------
TypDefName: Pipe.IExtensions (02000004)
Flags : [NotPublic] [AutoLayout] [Class] [Abstract] [Sealed] [AnsiClass] [BeforeFieldInit] (00100180)
Extends : 01000011 [TypeRef] System.Object
Method #1 (06000005)
-------------------------------------------------------
MethodName: SomeMethod (06000005)
Flags : [Assem] [Static] [HideBySig] [ReuseSlot] (00000093)
RVA : 0x00002134
ImplFlags : [IL] [Managed] (00000000)
CallCnvntn: [DEFAULT]
ReturnType: Void
1 Arguments
Argument #1: Class Pipe.I
1 Parameters
(1) ParamToken : (08000004) Name : i flags: [none] (00000000)
CustomAttribute #1 (0c000011)
-------------------------------------------------------
CustomAttribute Type: 0a000001
CustomAttributeName: System.Runtime.CompilerServices.ExtensionAttribute :: instance void .ctor()
Length: 4
Value : 01 00 00 00 > <
ctor args: ()

CustomAttribute #1 (0c000010)
-------------------------------------------------------
CustomAttribute Type: 0a000001
CustomAttributeName: System.Runtime.CompilerServices.ExtensionAttribute :: instance void .ctor()
Length: 4
Value : 01 00 00 00 > <
ctor args: ()

TypeDef #4 (02000005)
-------------------------------------------------------
TypDefName: Pipe.A (02000005)
Flags : [NotPublic] [AutoLayout] [Class] [AnsiClass] [BeforeFieldInit] (00100000)
Extends : 01000011 [TypeRef] System.Object
Method #1 (06000006)
-------------------------------------------------------
MethodName: SomeMethod (06000006)
Flags : [Public] [Final] [Virtual] [HideBySig] [NewSlot] (000001e6)
RVA : 0x0000213c
ImplFlags : [IL] [Managed] (00000000)
CallCnvntn: [DEFAULT]
hasThis
ReturnType: Void
No arguments.

Method #2 (06000007)
-------------------------------------------------------
MethodName: OtherMethod (06000007)
Flags : [Assem] [HideBySig] [ReuseSlot] (00000083)
RVA : 0x0000213e
ImplFlags : [IL] [Managed] (00000000)
CallCnvntn: [DEFAULT]
hasThis
ReturnType: Void
No arguments.

Method #3 (06000008)
-------------------------------------------------------
MethodName: .ctor (06000008)
Flags : [Public] [HideBySig] [ReuseSlot] [SpecialName] [RTSpecialName] [.ctor] (00001886)
RVA : 0x00002140
ImplFlags : [IL] [Managed] (00000000)
CallCnvntn: [DEFAULT]
hasThis
ReturnType: Void
No arguments.

InterfaceImpl #1 (09000001)
-------------------------------------------------------
Class : Pipe.A
Token : 02000003 [TypeDef] Pipe.I

TypeDef #5 (02000006)
-------------------------------------------------------
TypDefName: Pipe.B (02000006)
Flags : [NotPublic] [AutoLayout] [Class] [AnsiClass] [BeforeFieldInit] (00100000)
Extends : 01000011 [TypeRef] System.Object
Method #1 (06000009)
-------------------------------------------------------
MethodName: OtherMethod (06000009)
Flags : [Assem] [HideBySig] [ReuseSlot] (00000083)
RVA : 0x00002148
ImplFlags : [IL] [Managed] (00000000)
CallCnvntn: [DEFAULT]
hasThis
ReturnType: Void
No arguments.

Method #2 (0600000a)
-------------------------------------------------------
MethodName: Pipe.I.SomeMethod (0600000A)
Flags : [Private] [Final] [Virtual] [HideBySig] [NewSlot] (000001e1)
RVA : 0x0000214a
ImplFlags : [IL] [Managed] (00000000)
CallCnvntn: [DEFAULT]
hasThis
ReturnType: Void
No arguments.

Method #3 (0600000b)
-------------------------------------------------------
MethodName: .ctor (0600000B)
Flags : [Public] [HideBySig] [ReuseSlot] [SpecialName] [RTSpecialName] [.ctor] (00001886)
RVA : 0x0000214c
ImplFlags : [IL] [Managed] (00000000)
CallCnvntn: [DEFAULT]
hasThis
ReturnType: Void
No arguments.

MethodImpl #1 (00000001)
-------------------------------------------------------
Method Body Token : 0x0600000a
Method Declaration Token : 0x06000004

InterfaceImpl #1 (09000002)
-------------------------------------------------------
Class : Pipe.B
Token : 02000003 [TypeDef] Pipe.I


クむックむンスペクションにより、どのようにマヌクされおいおも、 すべおがメタデヌタに含たれおいるこずがわかりたす。 リフレクションは、郚倖者が芋るべきではないものをただ慎重に隠しおいたす。 したがっお、内郚むンタヌフェむスの各メ゜ッドの䜙分な5行のコヌドは、それほど倧きな悪ではないかもしれたせん。 ただし、䞻な結論は同じたたです。


どのタむプのSystem.Typeタむプを配垃するかを泚意深く芋おください。


しかし、これはもちろん、パブリックの必芁がないすべおの堎所でキヌワヌドinternalを取埗した埌の次のレベルです。


PS


internalキヌワヌドを䜿甚するこずで最もクヌルなこずは、アセンブリ内のどこにでもあるこずを知っおいたすか 倧きくなったら、2぀以䞊に分割する必芁がありたす。 そしおその過皋で、いく぀かのタむプをオヌプンにするために䌑憩を取る必芁がありたす。 そしお、どのタむプがオヌプンになるのにふさわしいのかを正確に考える必芁がありたす。 少なくずも簡単に。


これは、次のこずを意味したす。 このコヌドの䜜成方法により、新生児のアセンブリ間のアヌキテクチャの境界がどのような圢になるかを再考できたす。 もっず矎しいものはありたすか


PPS


バヌゞョンC7.2から、新しいプラむベヌト保護アクセス修食子が登堎したした。 そしお、私はただそれが䜕で、䜕ず䞀緒に食べられるのか分かりたせん。 実際には出䌚わないので。 しかし、私はコメントで知っおうれしいです。 ただし、ドキュメントからコピヌアンドペヌストするのではなく、実際にこのアクセス修食子が必芁になる堎合がありたす。



Source: https://habr.com/ru/post/J445804/


All Articles