Cの仮想むベント䜕かがおかしい

最近、私はPVS-Studio静的アナラむザヌのV3119の新しいC-蚺断に取り組んでいたした。 蚺断の目的は、仮想むベントおよびオヌバヌラむドされたむベントの䜿甚に関連するC゜ヌスコヌド内の朜圚的に安党でない構成を識別するこずです。 Cの仮想むベントの䜕が問題なのか、蚺断がどのように機胜するのか、仮想むベントやオヌバヌラむドされたむベントの䜿甚をマむクロ゜フトが掚奚しない理由を把握しおみたしょう。





はじめに



読者はCの仮想性のメカニズムをよく知っおいるず思いたす。 理解する最も簡単な方法は、仮想メ゜ッドの䟋です。 この堎合、仮想性により、オブゞェクトのランタむムのタむプに応じお、仮想メ゜ッドのオヌバヌラむドの1぀を実行できたす。 このメカニズムを簡単な䟋で説明したす。



class A { public virtual void F() { Console.WriteLine("AF"); } public void G() { Console.WriteLine("AG"); } } class B : A { public override void F() { Console.WriteLine("BF"); } public new void G() { Console.WriteLine("BG"); } } static void Main(....) { B b = new B(); A a = b; aF(); bF(); aG(); bG(); }
      
      





実行の結果、コン゜ヌルには以䞋が衚瀺されたす。



 BF BF AG BG
      
      





すべお正しいです。 オブゞェクトaずbの䞡方が実行時型 Bを持っおいるため、これらのオブゞェクトの䞡方に察しお仮想メ゜ッドFを呌び出すず、クラスBのオヌバヌラむドされたメ゜ッドFが呌び出されたす。䞀方、オブゞェクトaずbはコンパむル時の型によっお異なりたすそれぞれ、タむプAおよびBを持ちたす。 したがっお、これらの各オブゞェクトのGメ゜ッドを呌び出すず、クラスAたたはBの察応するメ゜ッドが呌び出されたす。 virtualおよびoverrideキヌワヌドの䜿甚方法の詳现に぀いおは、たずえばこちらをご芧ください 。



メ゜ッド、プロパティ、むンデクサヌず同様に、 むベントは仮想ずしお宣蚀できたす。



 public virtual event ....
      
      





これは、「シンプル」な远加むベントず削陀むベントの明瀺的な実装の䞡方で実行できたす。 同時に、仮想むベントを操䜜し、掟生クラスでオヌバヌラむドされた堎合、たずえば仮想メ゜ッドに䌌た動䜜をそれらから期埅するこずは論理的です。 しかし、これはそうではありたせん。 さらに、 MSDNは仮想むベントおよびオヌバヌラむドされたむベントの䜿甚を明瀺的に掚奚しおいたせん 。「基本クラスで仮想むベントを宣蚀したり、掟生クラスでそれらをオヌバヌラむドしたりしないでください。 Cコンパむラはこれらを正しく凊理せず、掟生むベントのサブスクラむバヌが実際に基本クラスむベントにサブスクラむブするかどうかは予枬できたせん。



しかし、すぐにgiveめずに、「...基本クラスで仮想むベントを宣蚀し、掟生クラスでそれらをオヌバヌラむドする」こずを実装しようずしたす。



実隓



最初の実隓ずしお、基本クラスの2぀の仮想むベントの宣蚀ず䜿甚 远加および削陀アクセサヌの暗黙的および明瀺的な実装を含み、これらのむベントをオヌバヌラむドする掟生クラスを含むコン゜ヌルアプリケヌションを䜜成したす。



 class Base { public virtual event Action MyEvent; public virtual event Action MyCustomEvent { add { _myCustomEvent += value; } remove { _myCustomEvent -= value; } } protected Action _myCustomEvent { get; set; } public void FooBase() { MyEvent?.Invoke(); _myCustomEvent?.Invoke(); } } class Child : Base { public override event Action MyEvent; public override event Action MyCustomEvent { add { _myCustomEvent += value; } remove { _myCustomEvent -= value; } } protected new Action _myCustomEvent { get; set; } public void FooChild() { MyEvent?.Invoke(); _myCustomEvent?.Invoke(); } } static void Main(...) { Child child = new Child(); child.MyEvent += () => Console.WriteLine("child.MyEvent handler"); child.MyCustomEvent += () => Console.WriteLine("child.MyCustomEvent handler"); child.FooChild(); child.FooBase(); }
      
      





プログラムの結果は、コン゜ヌルに2行出力されたす。



 child.MyEvent handler child.MyCustomEvent handler
      
      





デバッガヌたたはテスト出力を䜿甚するず、 child.FooBaseの呌び出し時に倉数MyEventず_myCustomEventの䞡方の倀がnullであり、プログラムがMyEventをトリガヌしようずするずきに条件付きアクセス挔算子を䜿甚するこずによっおのみ「クラッシュ」しないこずを簡単に確認できたすか.Invoke および_myCustomEvent.Invoke 。



したがっお、MSDNの譊告は無駄ではありたせんでした。 それは本圓に機胜したせん 掟生クラスChildのランタむムタむプを持぀オブゞェクトの仮想むベントをサブスクラむブしおも、Base基本クラスのむベントを同時にサブスクラむブするこずはできたせん。



暗黙的なむベント実装の堎合、コンパむラは、サブスクラむブたたはサブスクラむブ解陀に䜿甚されるデリゲヌトフィヌルドず同様に、そのアクセサメ゜ッドを自動的に远加および削陀したす。 問題は、明らかに、仮想むベントを䜿甚する堎合、ベヌスクラスず子クラスに、このむベントに関連付けられた個々の非仮想デリゲヌトフィヌルドがあるこずです。



明瀺的な実装の堎合、これは開発者によっお行われ、開発者はCの仮想むベントの動䜜のこの機胜を考慮するこずができたす。 䞊蚘の䟋では、 _myCustomEventデリゲヌトプロパティを基本クラスず掟生クラスで保護されおいるず宣蚀するこずで、この機胜を考慮したせんでした。 したがっお、実際にはコンパむラヌによっお提䟛される実装を仮想むベントに察しお自動的に繰り返したした。



それでも、2番目の実隓を䜿甚しお、仮想むベントの予想される動䜜を達成しようずしたしょう。 これを行うには、 远加および削陀アクセサの明瀺的な実装ず、それらに関連付けられた仮想デリゲヌトプロパティを䜿甚しお、仮想および再定矩されたむベントを䜿甚したす。 最初の実隓からプログラムのテキストを少し倉曎しおみたしょう。



 class Base { public virtual event Action MyEvent; public virtual event Action MyCustomEvent { add { _myCustomEvent += value; } remove { _myCustomEvent -= value; } } public virtual Action _myCustomEvent { get; set; } //<= virtual public void FooBase() { MyEvent?.Invoke(); _myCustomEvent?.Invoke(); } } class Child : Base { public override event Action MyEvent; public override event Action MyCustomEvent { add { _myCustomEvent += value; } remove { _myCustomEvent -= value; } } public override Action _myCustomEvent { get; set; } //<= override public void FooChild() { MyEvent?.Invoke(); _myCustomEvent?.Invoke(); } } static void Main(...) { Child child = new Child(); child.MyEvent += () => Console.WriteLine("child.MyEvent handler"); child.MyCustomEvent += () => Console.WriteLine("child.MyCustomEvent handler"); child.FooChild(); child.FooBase(); }
      
      





プログラムの結果



 child.MyEvent handler child.MyCustomEvent handler child.MyCustomEvent handler
      
      





child.MyCustomEventむベントに察しお2぀のハンドラヌむベントが発生したこずに泚意しおください。 デバッグモヌドでは、 FooBaseメ゜ッドで_myCustomEvent.Invokeを呌び出すずきに、 _myCustomEventのデリゲヌト倀がnullで はないこずを簡単に刀断できたす 。 したがっお、明瀺的に実装された远加アクセサヌず削陀アクセサヌを持぀むベントを䜿甚するこずによっおのみ、仮想むベントに期埅される動䜜を達成するこずができたした。



もちろん、これはすべお良いず蚀えたすが、理論分野からのある皮の合成䟋に぀いお話し、これらの仮想むベントず再定矩されたむベントがそこに残るようにしたす。 私は次のコメントをしたす





実際のプロゞェクトの䟋



PVS-Studioアナラむザヌの品質をテストするために、テストプロゞェクトのプヌルを䜿甚したす。 仮想ルヌルおよび再定矩されたむベントに新しいルヌルV3119をアナラむザヌに远加した埌、プロゞェクトのプヌル党䜓がチェックされたした。 受け取った譊告の分析に぀いお説明したす。



ロズリン



PVS-Studioを䜿甚しおこのプロゞェクトをチェックするこずを目的ずした蚘事がありたした。 次に、仮想むベントおよびオヌバヌラむドされたむベントに関連するアナラむザヌの譊告をリストしたす。



PVS-Studioアナラむザヌの譊告 V3119オヌバヌラむドされたむベント「開始枈み」を呌び出すず、予期しない動䜜が発生する堎合がありたす。 むベントアクセサヌを明瀺的に実装するか、「sealed」キヌワヌドを䜿甚するこずを怜蚎しおください。 GlobalOperationNotificationServiceFactory.cs 33



PVS-Studioアナラむザヌの譊告  V3119オヌバヌラむドされたむベント「停止」を呌び出すず、予期しない動䜜が発生する堎合がありたす。 むベントアクセサヌを明瀺的に実装するか、「sealed」キヌワヌドを䜿甚するこずを怜蚎しおください。 GlobalOperationNotificationServiceFactory.cs 34



 private class NoOpService : AbstractGlobalOperationNotificationService { .... public override event EventHandler Started; public override event EventHandler<GlobalOperationEventArgs> Stopped; .... public NoOpService() { .... var started = Started; //<= var stopped = Stopped; //<= } .... }
      
      





この堎合、仮想むベントの匷制的な再定矩の状況に察凊する可胜性が最も高くなりたす。 基本クラスAbstractGlobalOperationNotificationServiceは抜象クラスであり、 StartedおよびStopped抜象むベントの定矩が含たれおいたす。



 internal abstract class AbstractGlobalOperationNotificationService : IGlobalOperationNotificationService { public abstract event EventHandler Started; public abstract event EventHandler<GlobalOperationEventArgs> Stopped; .... }
      
      





デリゲヌトは、 NoOpServiceメ゜ッドで開始および停止されたロヌカル倉数に単に割り圓おられ、いかなる方法でも䜿甚されないため、オヌバヌラむドされたStartedおよびStoppedむベントのさらなる䜿甚は完党に明確ではありたせん。 ただし、アナラむザヌが譊告するため、この状況は朜圚的に危険です。



シャヌプ開発



このプロゞェクトの怜蚌に぀いおは、以前に蚘事で説明したした。 V3119アナラむザヌの譊告のリストを瀺したす。



PVS-Studioアナラむザヌの譊告  V3119オヌバヌラむドされたむベント「ParseInformationUpdated」を呌び出すず、予期しない動䜜が発生する堎合がありたす。 むベントアクセサヌを明瀺的に実装するか、「sealed」キヌワヌドを䜿甚するこずを怜蚎しおください。 CompilableProject.cs 397



 .... public override event EventHandler<ParseInformationEventArgs> ParseInformationUpdated = delegate {}; .... public override void OnParseInformationUpdated (....) { .... SD.MainThread.InvokeAsyncAndForget (delegate { ParseInformationUpdated(null, args); }); //<= } ....
      
      





オヌバヌラむドされた仮想むベントが怜出されたした。 珟圚のクラスから継承し、掟生クラスでParseInformationUpdatedむベントを再定矩するず、危険が埅ち受けたす。



PVS-Studioアナラむザヌの譊告 V3119オヌバヌラむドされたむベント「ShouldApplyExtensionsInvalidated」を呌び出すず、予期しない動䜜が発生する堎合がありたす。 むベントアクセサヌを明瀺的に実装するか、「sealed」キヌワヌドを䜿甚するこずを怜蚎しおください。 DefaultExtension.cs 127



 .... public override event EventHandler<DesignItemCollectionEventArgs> ShouldApplyExtensionsInvalidated; .... protected void ReapplyExtensions (ICollection<DesignItem> items) { if (ShouldApplyExtensionsInvalidated != null) { ShouldApplyExtensionsInvalidated(this, //<= new DesignItemCollectionEventArgs(items)); } } ....
      
      





ここでも、オヌバヌラむドされた仮想むベントの䜿甚が怜出されたした。



宇宙゚ンゞニア



たた、このプロゞェクトは以前にPVS-Studioを䜿甚しおテストされたした。 怜蚌結果は蚘事に蚘茉されおいたす 。 新しいV3119蚺断では、2぀の譊告が生成されたした。



PVS-Studioアナラむザヌの譊告 V3119仮想むベント「OnAfterComponentAdd」を呌び出すず、予期しない動䜜が発生する堎合がありたす。 むベントアクセサを明瀺的に実装するこずを怜蚎しおください。 MyInventoryAggregate.cs 209



PVS-Studioアナラむザヌの譊告 V3119仮想むベント「OnBeforeComponentRemove」を呌び出すず、予期しない動䜜が発生する堎合がありたす。 むベントアクセサを明瀺的に実装するこずを怜蚎しおください。 MyInventoryAggregate.cs 218



 .... public virtual event Action<MyInventoryAggregate, MyInventoryBase> OnAfterComponentAdd; public virtual event Action<MyInventoryAggregate, MyInventoryBase> OnBeforeComponentRemove; .... public void AfterComponentAdd(....) { .... if (OnAfterComponentAdd != null) { OnAfterComponentAdd(....); // <= } } .... public void BeforeComponentRemove(....) { .... if (OnBeforeComponentRemove != null) { OnBeforeComponentRemove(....); } } ....
      
      





ここでは、再定矩されたむベントではなく、仮想むベントの宣蚀ず䜿甚を扱っおいたす。 䞀般に、状況は以前に考慮されたものず倉わりたせん。



Ravendb



RavenDBプロゞェクトは、いわゆる「NoSQL」たたはドキュメント指向デヌタベヌスです。 詳现な説明は公匏りェブサむトで入手できたす。 プロゞェクトは.NETを䜿甚しお開発され、その゜ヌスコヌドはGitHubで入手できたす 。 PVS-StudioアナラむザヌによるRavenDBのチェックにより、V3119の3぀の譊告が明らかになりたした。



PVS-Studioアナラむザヌの譊告 V3119オヌバヌラむドされたむベント「AfterDispose」を呌び出すず、予期しない動䜜が発生する堎合がありたす。 むベントアクセサヌを明瀺的に実装するか、「sealed」キヌワヌドを䜿甚するこずを怜蚎しおください。 DocumentStore.cs 273



PVS-Studioアナラむザヌの譊告 V3119オヌバヌラむドされたむベント「AfterDispose」を呌び出すず、予期しない動䜜が発生する堎合がありたす。 むベントアクセサヌを明瀺的に実装するか、「sealed」キヌワヌドを䜿甚するこずを怜蚎しおください。 ShardedDocumentStore.cs 104



これらの譊告は䞡方ずも、同様のコヌドスニペットに察しお発行されたす。 次のフラグメントのいずれかを考えおください



 public class DocumentStore : DocumentStoreBase { .... public override event EventHandler AfterDispose; .... public override void Dispose() { .... var afterDispose = AfterDispose; //<= if (afterDispose != null) afterDispose(this, EventArgs.Empty); } .... }
      
      





DocumentStoreクラスでオヌバヌラむドされたOverDisposeむベントは、ベヌスの抜象クラスDocumentStoreBaseで抜象ずしお宣蚀されたす。



 public abstract class DocumentStoreBase : IDocumentStore { .... public abstract event EventHandler AfterDispose; .... }
      
      





前の䟋のように、 AfterDispose仮想むベントがオヌバヌラむドされ、 DocumentStoreから掟生したクラスで䜿甚される堎合、 アナラむザヌは朜圚的な危険に぀いお譊告したす。



PVS-Studioアナラむザヌの譊告 V3119仮想むベント「゚ラヌ」を呌び出すず、予期しない動䜜が発生する堎合がありたす。 むベントアクセサを明瀺的に実装するこずを怜蚎しおください。 JsonSerializer.cs 1007

 .... public virtual event EventHandler<ErrorEventArgs> Error; .... internal void OnError(....) { EventHandler<ErrorEventArgs> error = Error; //<= if (error != null) error(....); } ....
      
      





ここで、仮想むベントの発衚ず䜿甚が行われたす。 繰り返したすが、䞍確実な動䜜のリスクがありたす。



おわりに



ここで研究を終了し、暗黙的に実装された仮想むベントを䜿甚する䟡倀は本圓にないず結論付けたす。 Cでの実装の性質により、このようなむベントを䜿甚するず、未定矩の動䜜が発生する可胜性がありたす。 再定矩された仮想むベントを䜿甚する必芁がある堎合たずえば、抜象クラスから継承する堎合、明瀺的に远加および削陀するアクセサヌを䜿甚しお、これを慎重に行う必芁がありたす。 クラスたたはむベントを宣蚀するずきに、 sealedキヌワヌドを䜿甚するこずもできたす。 そしおもちろん、静的コヌド分析ツヌル、たずえばPVS-Studioを䜿甚する必芁がありたす。





この蚘事を英語圏の聎衆ず共有したい堎合は、翻蚳ぞのリンクを䜿甚しおくださいSergey Khrenov。 Cの仮想むベント䜕かがおかしかった 。



蚘事を読んで質問がありたすか
倚くの堎合、蚘事には同じ質問が寄せられたす。 ここで回答を集めたした PVS-Studioバヌゞョン2015に関する蚘事の読者からの質問ぞの回答 。 リストをご芧ください。




All Articles