C#での複数のディスパッチ

C# 動的機能が予期しないコード動作を引き起こす可能性のある2つの 記事を既に検討しました。

今回は、動的なディスパッチにより厳密に型指定されたままコードを単純化できるというプラス面を示したいと思います。



この投稿では、次のことを学びます。





必要ですか?



メソッドのオーバーロードを選択する問題が発生する場合があります。 例:

public static void Sanitize(Node node) { Node node = new Document(); new Sanitizer().Cleanup(node); // void Cleanup(Node node) } class Sanitizer { public void Cleanup(Node node) { } public void Cleanup(Element element) { } public void Cleanup(Attribute attribute) { } public void Cleanup(Document document) { } }
      
      





[クラス階層]
 class Node { } class Attribute : Node { } class Document : Node { } class Element : Node { } class Text : Node { } class HtmlElement : Element { } class HtmlDocument : Document { }
      
      







ご覧のとおり、 void Cleanup(Node node)



メソッドのみが選択されます。 この問題は、OOPアプローチで解決するか、型キャストを使用できます。



簡単なものから始めましょう:

[型キャスト]
 public static void Sanitize(Node node) { var sanitizer = new Sanitizer(); var document = node as Document; if (document != null) { sanitizer.Cleanup(document); } var element = node as Element; if (element != null) { sanitizer.Cleanup(element); } /* *     */ { //  - sanitizer.Cleanup(node); } }
      
      







見た目は「きれい」ではありません。

したがって、OOPを適用します。
 public static void Sanitize(Node node) { var sanitizer = new Sanitizer(); switch (node.NodeType) { case NodeType.Node: sanitizer.Cleanup(node); break; case NodeType.Element: sanitizer.Cleanup((Element)node); break; case NodeType.Document: sanitizer.Cleanup((Document)node); break; case NodeType.Text: sanitizer.Cleanup((Text)node); break; case NodeType.Attribute: sanitizer.Cleanup((Attribute)node); break; default: throw new ArgumentOutOfRangeException(); } } enum NodeType { Node, Element, Document, Text, Attribute } abstract class Node { public abstract NodeType NodeType { get; } } class Attribute : Node { public override NodeType NodeType { get { return NodeType.Attribute; } } } class Document : Node { public override NodeType NodeType { get { return NodeType.Document; } } } class Element : Node { public override NodeType NodeType { get { return NodeType.Element; } } } class Text : Node { public override NodeType NodeType { get { return NodeType.Text; } } }
      
      







さて、 NodeType列挙を宣言し、 Nodeクラスに同名の抽象プロパティを導入しました。 問題は解決しました。 ご清聴ありがとうございました



このようなテンプレートは、クロスプラットフォームの移植性が必要な場合に役立ちます。 プログラミング言語でもランタイムでもかまいません。 このようにして、たとえば標準のW3C DOMが使用されました。



複数のディスパッチパターン



複数ディスパッチまたはマルチメソッド(複数ディスパッチ)は、コンパイルではなく、実行時に呼び出されるメソッドを選択するためのOOPの概念のバリエーションです。



アイデアを得るために、単純なものから始めましょう:ダブルディスパッチ(詳細はこちら )。

ダブルディスパッチ
 class Program { interface ICollidable { void CollideWith(ICollidable other); } class Asteroid : ICollidable { public void CollideWith(Asteroid other) { Console.WriteLine("Asteroid collides with Asteroid"); } public void CollideWith(Spaceship spaceship) { Console.WriteLine("Asteroid collides with Spaceship"); } public void CollideWith(ICollidable other) { other.CollideWith(this); } } class Spaceship : ICollidable { public void CollideWith(ICollidable other) { other.CollideWith(this); } public void CollideWith(Asteroid asteroid) { Console.WriteLine("Spaceship collides with Asteroid"); } public void CollideWith(Spaceship spaceship) { Console.WriteLine("Spaceship collides with Spaceship"); } } static void Main(string[] args) { var asteroid = new Asteroid(); var spaceship = new Spaceship(); asteroid.CollideWith(spaceship); asteroid.CollideWith(asteroid); } }
      
      







ダブルディスパッチの本質は、メソッドのバインドが特定の呼び出しの場所ではなく、クラス階層の継承者によって行われることです。 欠点は拡張性の問題です。システム内の要素の数を増やすと、コピーと貼り付けを処理する必要があります。



- では C#の動的な問題はどこにあるのでしょうか?! -お願いします。

型変換の例では、ダブルメソッドとは異なり、必要なメソッドのオーバーロードの選択が特定の呼び出しの場所で発生する、マルチメソッドテンプレートのプリミティブな実装に既に慣れています。



しかし、風水にない ifsを常に書くのは悪いことです!



もちろん、常にではありません。 上記の例は単なる合成です。 したがって、より現実的なものを検討してください。



私は2つを取る



先に進む前に、エンタープライズライブラリとは何かを思い出しましょう。



エンタープライズライブラリは、アプリケーションを構築するための再利用可能なコンポーネント/ブロック(ロギング、検証、データアクセス、例外処理など)のセットです。 仕事のすべての詳細が考慮される別のがあります。



各ブロックは、XMLとコード自体の両方で構成できます。



今日検討するエラー処理ブロック。



ASP.NETのようなパイプラインパターンを使用するアプリケーションを開発している場合、例外処理ブロック(以降、単に「EHB」)を使用すると、人生が大幅に簡素化されます。 結局のところ、礎石は常に言語/フレームワークなどのエラー処理モデルです。



ポリシーテンプレート(戦略テンプレートのバリエーション)を使用して命令コードをより多くのOOPコードに置き換えたコードがあるとします。



それは:

 try { // code to throw exception } catch (InvalidCastException invalidCastException) { // log ex // rethrow if needed } catch (Exception e) { // throw new Exception with inner }
      
      





なった(EHBを使用):



 var policies = new List<ExceptionPolicyDefinition>(); var myTestExceptionPolicy = new List<ExceptionPolicyEntry> { { new ExceptionPolicyEntry(typeof (InvalidCastException), PostHandlingAction.NotifyRethrow, new IExceptionHandler[] {new LoggingExceptionHandler(...),}) }, { new ExceptionPolicyEntry(typeof (Exception), PostHandlingAction.NotifyRethrow, new IExceptionHandler[] {new ReplaceHandler(...)}) } }; policies.Add(new ExceptionPolicyDefinition("MyTestExceptionPolicy", myTestExceptionPolicy)); ExceptionManager manager = new ExceptionManager(policies); try { // code to throw exception } catch (Exception e) { manager.HandleException(e, "Exception Policy Name"); }
      
      





まあ、それはより「企業」に見えます。 しかし、大規模な依存関係を避け、C#言語自体の機能に制限することは可能ですか?



命令型アプローチは、言語のまさに可能性です 」と主張することができます。

ただし、それだけではありません



Exception Handling Blockを書いてみましょうが、簡単です。



これを行うには、EHB自体の例外ハンドラーのプロモーションの実装を検討してください。

それで、ソースコードをもう一度:



 ExceptionManager manager = new ExceptionManager(policies); try { // code to throw exception } catch (Exception e) { manager.HandleException(e, "Exception Policy Name"); }
      
      





から始まるコールチェーン

manager.HandleException(e, "Exception Policy Name")





ExceptionPolicyDefinition.FindExceptionPolicyEntry
 private ExceptionPolicyEntry FindExceptionPolicyEntry(Type exceptionType) { ExceptionPolicyEntry policyEntry = null; while (exceptionType != typeof(object)) { policyEntry = this.GetPolicyEntry(exceptionType); if (policyEntry != null) { return policyEntry; } exceptionType = exceptionType.BaseType; } return policyEntry; }
      
      







ExceptionPolicyEntry.Handle
 public bool Handle(Exception exceptionToHandle) { if (exceptionToHandle == null) { throw new ArgumentNullException("exceptionToHandle"); } Guid handlingInstanceID = Guid.NewGuid(); Exception chainException = this.ExecuteHandlerChain(exceptionToHandle, handlingInstanceID); return this.RethrowRecommended(chainException, exceptionToHandle); }
      
      







ExceptionPolicyEntry.ExecuteHandlerChain
 private Exception ExecuteHandlerChain(Exception ex, Guid handlingInstanceID) { string name = string.Empty; try { foreach (IExceptionHandler handler in this.handlers) { name = handler.GetType().Name; ex = handler.HandleException(ex, handlingInstanceID); } } catch (Exception exception) { // rest of implementation } return ex; }
      
      









そして、これは氷山の一角にすぎません。



重要なインターフェイスはIExceptionHandlerです。



 namespace Microsoft.Practices.EnterpriseLibrary.ExceptionHandling { public interface IExceptionHandler { Exception HandleException(Exception ex, Guid handlingInstanceID); } }
      
      





それを基礎として、それ以上のものはありません。




2つのインターフェイスを宣言します(なぜこれが必要なのか-少し後で見てください):



 public interface IExceptionHandler { void HandleException<T>(T exception) where T : Exception; } public interface IExceptionHandler<T> where T : Exception { void Handle(T exception); }
      
      







また、I / O例外のハンドラー:
 public class FileSystemExceptionHandler : IExceptionHandler, IExceptionHandler<Exception>, IExceptionHandler<IOException>, IExceptionHandler<FileNotFoundException> { public void HandleException<T>(T exception) where T : Exception { var handler = this as IExceptionHandler<T>; if (handler != null) handler.Handle(exception); else this.Handle((dynamic) exception); } public void Handle(Exception exception) { OnFallback(exception); } protected virtual void OnFallback(Exception exception) { // rest of implementation Console.WriteLine("Fallback: {0}", exception.GetType().Name); } public void Handle(IOException exception) { // rest of implementation Console.WriteLine("IO spec"); } public void Handle(FileNotFoundException exception) { // rest of implementation Console.WriteLine("FileNotFoundException spec"); } }
      
      







該当するもの:



 IExceptionHandler defaultHandler = new FileSystemExceptionHandler(); defaultHandler.HandleException(new IOException()); // Handle(IOException) overload defaultHandler.HandleException(new DirectoryNotFoundException()); // Handle(IOException) overload defaultHandler.HandleException(new FileNotFoundException()); // Handle(FileNotFoundException) overload defaultHandler.HandleException(new FormatException()); // Handle(Exception) => OnFallback
      
      





すべてがうまくいきました! しかし、どのように? 結局、例外の種類などを解決するためのコードを1行も書いていません。



スキームを考えてみましょう




したがって、対応するIExceptionHandlerの実装がある場合は、それを使用します。

そうでない場合、 dynamicを介した複数のディスパッチ



したがって、例1は1行のコードで解決できます。

 public static void Sanitize(Node node) { new Sanitizer().Cleanup((dynamic)node); }
      
      





まとめると



一見したところ、パターン全体が1つの言語コンストラクトのみに適合することはあまり明らかではありませんが、そうです。

よく調べてみると、単純なポリシーベースの例外ハンドラーを構築できることがわかりました。




All Articles