5

考え

C#/.NET で例外をスローする代わりに、コールバックを使用することを考えています。

長所と短所

利点は

  • チェックされていない例外の制御フローのような隠し goto はありません
  • 特に複数の例外が関係している場合は、よりクリーンなコード
  • スローされた例外はメソッド シグネチャに記載されており、呼び出し元は例外の処理について考える必要がありますが、アプリケーション全体の例外ハンドラー、"UnhandledExceptionHandler"、または null を簡単に渡すことができます。したがって、それらは「ソフト」チェック例外のようなものですが、メソッドをオーバーロードすることで後で例外をスローしたり、例外ハンドラーで「ハンドル」を呼び出さないようにすることで例外を削除したりできるため、より保守しやすくなっています)。
  • 非同期呼び出しにも適しています
  • 例外ハンドラーは、さまざまな場所でスローされるいくつかの例外を処理できます
  • どの例外を処理する必要があるかを明示します。「NotImplementedException」のように処理したくない例外に対しては、通常の方法で例外をスローすることもできます。

デメリットは

  • C# および .NET にとって慣用的ではない
  • throwing メソッドは、すぐに戻り値を返すことによって制御フローを中断する必要があります。戻り値の型が値型の場合、これは困難です。
  • ? (下記の質問を参照)

質問

なぜこれが使用されないのか疑問に思っているので、おそらくいくつかの重大な欠点を見逃しています。私が見逃した欠点は何ですか?

例:

それ以外の

void ThrowingMethod() {
    throw new Exception();
}

void CatchingMethod() {
    try {
         ThrowingMethod();
    } catch(Exception e) {
         //handle exception
    }
}

私はするだろう

void ThrowingMethod(ExceptionHandler exceptionHandler) {
    exceptionHandler.handle(new Exception());
}

void CatchingMethod() {
     ThrowingMethod(exception => */ handle exception */ );
}

delegate void ExceptionHandler(Exception exception);

どこかで定義され、「handle(...)」は null をチェックする拡張メソッドであり、スタック トレースを取得し、例外がスローされたときに例外ハンドラーがまったくない場合は「UnhandledException」をスローする可能性があります。


以前に例外をスローしなかったメソッドで例外をスローする例

void UsedToNotThrowButNowThrowing() {
   UsedToNotThrowButNowThrowing(null);
}

//overloads existing method that did not throw to now throw
void UsedToNotThrowButNowThrowing(ExceptionHandler exceptionHandler) {
    //extension method "handle" throws an UnhandledException if the handler is null
    exceptionHandler.handle(exceptionHandler);
}

値を返すメソッドの例

TResult ThrowingMethod(ExceptionHandler<TResult> exceptionHandler) {
        //code before exception
        return exceptionHandler.handle(new Exception()); //return to interrupt execution
        //code after exception
    }

TResult CatchingMethod() {
     return ThrowingMethod(exception => */ handle exception and return value */ );
}

delegate TResult ExceptionHandler<TResult>(Exception exception);
4

3 に答える 3

0

1 つには、これらのハンドラーをアプリケーションのほとんどすべてのメソッドに渡す必要があるというオーバーヘッドがあります。これは非常に重い依存関係であり、アプリを構築する前に行うか死ぬかの決定です。

第 2 に、システムによってスローされた例外およびサード パーティのアセンブリからのその他の例外の処理の問題があります。

第三に、例外はスローされた時点でプログラムの実行を停止することを意味します。これは、実際には「例外」であり、処理可能な単なるエラーではなく、実行を継続できるためです。

于 2013-06-27T15:42:16.687 に答える
0

スケーラビリティ。

@mungflesh が正しく指摘しているように、これらのハンドラを渡す必要があります。私の最初の懸念は、オーバーヘッドではなくスケーラビリティです。これはメソッド シグネチャに影響します。これはおそらく、Java でチェック済み例外を使用する場合と同じスケーラビリティの問題につながる可能性があります (C# についてはわかりません。C++ と Java しか使用していません)。

深さ 50 のコールのコール スタックを想像してみてください (極端なことは何もありません、IMO)。ある日、変更が発生し、スローされなかったチェーンの奥深くにある呼び出し先の 1 つが、例外をスローできるメソッドになりました。それが未チェックの例外である場合は、新しいエラーに対処するために最上位のコードを変更するだけで済みます。それがチェックされた例外である場合、またはあなたの考えを適用する場合は、呼び出しチェーンを介して関連するすべてのメソッド シグネチャを変更する必要があります。署名の変更が伝播することを忘れないでください。これらのメソッドの署名を変更すると、それらのメソッドが呼び出される他の場所でコードを変更する必要があり、さらに署名の変更が発生する可能性があります。要するに、スケーリングが不十分です。:(


これが私がそれをどのように意味するかを示すいくつかの擬似コードです。非チェック例外を使用すると、深さ 50 のコールスタックの変更を次の方法で処理します。

f1() {
  try {    // <-- This try-catch block is the only change you have to make
    f2();  
  }
  catch(...) {
    // do something with the error
  }
}

f2() { // None of the f2(), f3(), ..., f49() has to be changed
  f3();
}

...

f49() {
  f50();
}

f50() {
  throw SomeNewException; // it was not here before
}

あなたのアプローチで同じ変更に対処する:

f1() {
  ExceptionHandler h;
  f2(h);
}

f2(ExceptionHandler h) { // Signature change
  f3(h); // Calling site change
}

...

f49(ExceptionHandler h) { // Signature change
  f50(h); // Calling site change
}

f50(ExceptionHandler h) {
  h.SomeNewException(); // it was not here before
}

関連するすべてのメソッド (f2...f49) に新しい署名が追加され、呼び出しサイトも更新する必要があります (たとえば、f2() が f2(h) になるなど)。f2...f49この変更についても知らないはずですが、署名と呼び出しサイトの両方を変更する必要があることに注意してください。


別の言い方をすれば、すべての中間呼び出しは、エラー ハンドラーを処理する必要があります。チェックされていない例外を使用すると、これらの詳細を非表示にすることができます。


チェックされていない例外は確かに「制御フローのような隠されたgoto」ですが、少なくともうまくスケーリングされます。間違いなく、すぐに保守不可能な混乱につながる可能性があります...

+1ですが、興味深いアイデアです。

于 2013-06-27T15:56:44.847 に答える