Muster zur Vermeidung verschachtelter Try-Catch-Blöcke?


114

Stellen Sie sich eine Situation vor, in der ich drei (oder mehr) Möglichkeiten habe, eine Berechnung durchzuführen, von denen jede mit einer Ausnahme fehlschlagen kann. Um jede Berechnung zu versuchen, bis wir eine finden, die erfolgreich ist, habe ich Folgendes getan:

double val;

try { val = calc1(); }
catch (Calc1Exception e1)
{ 
    try { val = calc2(); }
    catch (Calc2Exception e2)
    {
        try { val = calc3(); }
        catch (Calc3Exception e3)
        {
            throw new NoCalcsWorkedException();
        }
    }
}

Gibt es ein akzeptiertes Muster, das dies auf schönere Weise erreicht? Natürlich könnte ich jede Berechnung in eine Hilfsmethode einschließen, die bei einem Fehler null zurückgibt, und dann einfach den ??Operator verwenden, aber es gibt eine Möglichkeit, dies allgemeiner zu tun (dh ohne für jede Methode, die ich verwenden möchte, eine Hilfsmethode schreiben zu müssen )? Ich habe darüber nachgedacht, eine statische Methode mit Generika zu schreiben, die eine bestimmte Methode in einen Versuch / Fang einschließt und bei einem Fehler null zurückgibt, bin mir aber nicht sicher, wie ich vorgehen soll. Irgendwelche Ideen?


Können Sie einige Details zur Berechnung angeben?
James Johnson

2
Sie sind im Grunde nur verschiedene Methoden zum Lösen / Annähern einer PDE. Sie stammen aus einer Bibliothek eines Drittanbieters, daher kann ich sie nicht so ändern, dass Fehlercodes oder Null zurückgegeben werden. Das Beste, was ich tun kann, ist, jedes einzeln in eine Methode zu verpacken.
Jjoelson

Sind die Berechnungsmethoden Teil Ihres Projekts (anstelle einer Bibliothek eines Drittanbieters)? In diesem Fall können Sie die Logik, die Ausnahmen auslöst, herausziehen und damit entscheiden, welche Berechnungsmethode aufgerufen werden muss.
Chris

1
Es gibt einen anderen Anwendungsfall dafür, auf den ich in Java gestoßen bin: Ich muss a Stringzu einem Dateusing analysieren SimpleDateFormat.parseund mehrere verschiedene Formate der Reihe nach ausprobieren, um zum nächsten überzugehen, wenn eine Ausnahme ausgelöst wird.
Elende Variable

Antworten:


125

Verwenden Sie nach Möglichkeit keine Ausnahmen für den Kontrollfluss oder außergewöhnliche Umstände.

Aber um Ihre Frage direkt zu beantworten (vorausgesetzt, alle Ausnahmetypen sind gleich):

Func<double>[] calcs = { calc1, calc2, calc3 };

foreach(var calc in calcs)
{
   try { return calc(); }
   catch (CalcException){  }
} 

throw new NoCalcsWorkedException();

15
Dies setzt voraus, dass Calc1Exception, Calc2Exceptionund Calc3Exceptioneine gemeinsame Basisklasse gemeinsam haben.
Wyzard

3
Darüber hinaus nimmt er eine gemeinsame Signatur an - die nicht wirklich so weit entfernt ist. Gute Antwort.
TomTom

1
Außerdem habe ich continueim catch-Block und breaknach dem catch-Block hinzugefügt , damit die Schleife endet, wenn eine Berechnung funktioniert (danke an Lirik für dieses Bit)
jjoelson

6
+1 nur, weil dort "Keine Ausnahmen für den Kontrollfluss verwenden" steht, obwohl ich "NIE NIE" anstelle von "So weit wie möglich" verwendet hätte.
Bill K

1
@jjoelson: Eine breakAussage calc();innerhalb der try(und Nrcontinue Aussage) könnte etwas idiomatischer sein.
Adam Robinson

38

Nur um eine "außerhalb der Box" -Alternative anzubieten, wie wäre es mit einer rekursiven Funktion ...

//Calling Code
double result = DoCalc();

double DoCalc(int c = 1)
{
   try{
      switch(c){
         case 1: return Calc1();
         case 2: return Calc2();
         case 3: return Calc3();
         default: return CalcDefault();  //default should not be one of the Calcs - infinite loop
      }
   }
   catch{
      return DoCalc(++c);
   }
}

HINWEIS: Ich sage keineswegs, dass dies der beste Weg ist, um die Arbeit zu erledigen, nur ein anderer Weg


6
Ich musste "On Error Resume Next" einmal in einer Sprache implementieren, und der von mir generierte Code sah sehr ähnlich aus.
Jacob Krall

4
Bitte verwenden Sie niemals eine switch-Anweisung, um eine for-Schleife zu erstellen.
Jeff Ferland

3
Es ist nicht wartbar, switch-Anweisung für Schleifen zu haben
Mohamed Abed

1
Ich weiß, dass meine Antwort nicht der effizienteste Code ist, aber die Verwendung von Try / Catch-Blöcken für solche Dinge ist sowieso nicht der beste Weg. Leider verwendet das OP eine Bibliothek eines Drittanbieters und muss sein Bestes geben, um den Erfolg sicherzustellen. Im Idealfall könnte die Eingabe zuerst validiert und die richtige Berechnungsfunktion ausgewählt werden, um sicherzustellen, dass sie nicht fehlschlägt - natürlich können Sie all dies dann aus Sicherheitsgründen versuchen / fangen;)
musefan

1
return DoCalc(c++)entspricht return DoCalc(c)- nach dem Inkrementieren wird der Wert nicht tiefer übergeben. Damit es funktioniert (und mehr Dunkelheit einführt), könnte es eher so sein return DoCalc((c++,c)).
Artur Czajka

37

Sie können die Verschachtelung reduzieren, indem Sie sie in eine Methode wie die folgende einfügen:

private double calcStuff()
{
  try { return calc1(); }
  catch (Calc1Exception e1)
  {
    // Continue on to the code below
  }

  try { return calc2(); }
  catch (Calc2Exception e1)
  {
    // Continue on to the code below
  }

  try { return calc3(); }
  catch (Calc3Exception e1)
  {
    // Continue on to the code below
  }

  throw new NoCalcsWorkedException();
}

Ich vermute jedoch, dass das eigentliche Designproblem darin besteht, dass drei verschiedene Methoden existieren, die im Wesentlichen dasselbe tun (aus Sicht des Anrufers), aber unterschiedliche, nicht verwandte Ausnahmen auslösen.

Dies setzt voraus, dass die drei Ausnahmen nichts miteinander zu tun haben. Wenn alle eine gemeinsame Basisklasse haben, ist es besser, eine Schleife mit einem einzelnen catch-Block zu verwenden, wie Ani vorgeschlagen hat.


1
+1: Dies ist die sauberste und sachlichste Lösung für das Problem. Die anderen Lösungen, die ich hier sehe, versuchen nur süß zu sein, IMO. Wie der OP sagte, hat er die API nicht geschrieben, also bleibt er bei den Ausnahmen, die ausgelöst werden.
Nate CK

19

Versuchen Sie, die Logik nicht anhand von Ausnahmen zu steuern. Beachten Sie auch, dass Ausnahmen nur in Ausnahmefällen ausgelöst werden sollten. Berechnungen sollten in den meisten Fällen keine Ausnahmen auslösen, es sei denn, sie greifen auf externe Ressourcen zu oder analysieren Zeichenfolgen oder ähnliches. Befolgen Sie im schlimmsten Fall den TryMethod-Stil (wie TryParse ()), um die Ausnahmelogik zu kapseln und Ihren Kontrollfluss wartbar und sauber zu machen:

bool TryCalculate(out double paramOut)
{
  try
  {
    // do some calculations
    return true;
  }
  catch(Exception e)
  { 
     // do some handling
    return false;
  }

}

double calcOutput;
if(!TryCalc1(inputParam, out calcOutput))
  TryCalc2(inputParam, out calcOutput);

Eine weitere Variante, die das Try-Muster verwendet und eine Liste von Methoden anstelle von verschachtelten kombiniert, wenn:

internal delegate bool TryCalculation(out double output);

TryCalculation[] tryCalcs = { calc1, calc2, calc3 };

double calcOutput;
foreach (var tryCalc in tryCalcs.Where(tryCalc => tryCalc(out calcOutput)))
  break;

und wenn der foreach ein wenig kompliziert ist, können Sie es klar machen:

        foreach (var tryCalc in tryCalcs)
        {
            if (tryCalc(out calcOutput)) break;
        }

Ehrlich gesagt denke ich, dass dies nur unnötige Abstraktionen verursacht. Dies ist keine schreckliche Lösung, aber ich würde dies in den meisten Fällen nicht verwenden.
user606723

Wenn Sie sich nicht für den Ausnahmetyp interessieren und nur bedingten Code verarbeiten möchten, ist es in Bezug auf Abstraktion und Wartbarkeit definitiv besser, ihn auf diese Weise in eine bedingte Methode mit Rückgabe zu konvertieren, unabhängig davon, ob dies erfolgreich ist oder nicht Sie verbergen die Ausnahmebehandlung der chaotischen Syntax mit einer klar beschreibenden Methode. Dann wird Ihr Code sie behandeln, da es sich um eine reguläre bedingte Methode handelt.
Mohamed Abed

Ich kenne die Punkte und sie sind gültig. Wenn man diese Art der Abstraktion (Verstecken von Unordnung / Komplexität) fast überall verwendet, wird es jedoch lächerlich und es wird viel schwieriger, eine Software für das zu verstehen, was sie ist. Wie gesagt, es ist keine schreckliche Lösung, aber ich würde es nicht leichtfertig verwenden.
user606723

9

Erstellen Sie eine Liste der Delegaten für Ihre Berechnungsfunktionen und führen Sie anschließend eine while-Schleife durch:

List<Func<double>> calcMethods = new List<Func<double>>();

// Note: I haven't done this in a while, so I'm not sure if
// this is the correct syntax for Func delegates, but it should
// give you an idea of how to do this.
calcMethods.Add(new Func<double>(calc1));
calcMethods.Add(new Func<double>(calc2));
calcMethods.Add(new Func<double>(calc3));

double val;
for(CalcMethod calc in calcMethods)
{
    try
    {
        val = calc();
        // If you didn't catch an exception, then break out of the loop
        break;
    }
    catch(GenericCalcException e)
    {
        // Not sure what your exception would be, but catch it and continue
    }

}

return val; // are you returning the value?

Das sollte Ihnen eine allgemeine Vorstellung davon geben, wie es geht (dh es ist keine exakte Lösung).


1
Außer natürlich, dass man dann normalerweise nie fangen sollte Exception. ;)
DeCaf

@DeCaf wie gesagt: "sollte Ihnen eine allgemeine Vorstellung davon geben, wie es geht (dh keine exakte Lösung)." Das OP kann also die entsprechende Ausnahme abfangen ... es ist nicht erforderlich, das Generikum abzufangen Exception.
Kiril

Ja, tut mir leid, ich hatte nur das Bedürfnis, das da rauszubringen.
DeCaf

1
@DeCaf, es ist eine gültige Klarstellung für diejenigen, die mit den Best Practices möglicherweise nicht so vertraut sind. Danke :)
Kiril

9

Das sieht nach einem Job für ... MONADS aus! Insbesondere die Vielleicht-Monade. Beginnen Sie mit der hier beschriebenen Vielleicht-Monade . Fügen Sie dann einige Erweiterungsmethoden hinzu. Ich habe diese Erweiterungsmethoden speziell für das von Ihnen beschriebene Problem geschrieben. Das Schöne an Monaden ist, dass Sie die genauen Erweiterungsmethoden schreiben können, die für Ihre Situation erforderlich sind.

public static Maybe<T> TryGet<T>(this Maybe<T> m, Func<T> getFunction)
{
    // If m has a value, just return m - we want to return the value
    // of the *first* successful TryGet.
    if (m.HasValue)
    {
        return m;
    }

    try
    {
        var value = getFunction();

        // We were able to successfully get a value. Wrap it in a Maybe
        // so that we can continue to chain.
        return value.ToMaybe();
    }
    catch
    {
        // We were unable to get a value. There's nothing else we can do.
        // Hopefully, another TryGet or ThrowIfNone will handle the None.
        return Maybe<T>.None;
    }
}

public static Maybe<T> ThrowIfNone<T>(
    this Maybe<T> m,
    Func<Exception> throwFunction)
{
    if (!m.HasValue)
    {
        // If m does not have a value by now, give up and throw.
        throw throwFunction();
    }

    // Otherwise, pass it on - someone else should unwrap the Maybe and
    // use its value.
    return m;
}

Verwenden Sie es so:

[Test]
public void ThrowIfNone_ThrowsTheSpecifiedException_GivenNoSuccessfulTryGet()
{
    Assert.That(() =>
        Maybe<double>.None
            .TryGet(() => { throw new Exception(); })
            .TryGet(() => { throw new Exception(); })
            .TryGet(() => { throw new Exception(); })
            .ThrowIfNone(() => new NoCalcsWorkedException())
            .Value,
        Throws.TypeOf<NoCalcsWorkedException>());
}

[Test]
public void Value_ReturnsTheValueOfTheFirstSuccessfulTryGet()
{
    Assert.That(
        Maybe<double>.None
            .TryGet(() => { throw new Exception(); })
            .TryGet(() => 0)
            .TryGet(() => 1)
            .ThrowIfNone(() => new NoCalcsWorkedException())
            .Value,
        Is.EqualTo(0));
}

Wenn Sie diese Art von Berechnungen häufig durchführen, sollte die Monade möglicherweise die Menge an Boilerplate-Code reduzieren, die Sie schreiben müssen, und gleichzeitig die Lesbarkeit Ihres Codes verbessern.


2
Ich liebe diese Lösung. Es ist jedoch ziemlich undurchsichtig für jeden, der zuvor noch keinen Monaden ausgesetzt war, was bedeutet, dass dies in c # alles andere als idiomatisch ist. Ich möchte nicht, dass einer meiner Mitarbeiter Monaden lernt, nur um diesen einen dummen Code in Zukunft zu modifizieren. Dies ist jedoch ideal für zukünftige Referenzzwecke.
Jjoelson

1
+1 für Sinn für Humor, um die stumpfeste und ausführlichste Lösung für dieses Problem zu schreiben und dann zu sagen, dass dadurch "die Menge an Boilerplate-Code reduziert wird, die Sie schreiben müssen, während die Lesbarkeit Ihres Codes erhöht wird".
Nate CK

1
Hey, wir beschweren uns nicht über die enormen Mengen an Code, die in System.Linq lauern, und verwenden diese Monaden gerne den ganzen Tag. Ich denke, @ fre0n bedeutet nur, dass, wenn Sie bereit sind, die Vielleicht-Monade in Ihr Toolkit aufzunehmen, diese Art von verketteten Bewertungen einfacher zu betrachten und zu begründen sind. Es gibt verschiedene Implementierungen, die leicht zu greifen sind.
Sebastian Good

Nur weil es verwendet Maybewird, ist dies keine monadische Lösung. Es verwendet null der monadischen Eigenschaften von Maybeund kann daher auch nur verwendet werden null. Außerdem, "monadisch" verwendet, wäre dies die Umkehrung der Maybe. Eine echte monadische Lösung müsste eine staatliche Monade verwenden, die den ersten nicht außergewöhnlichen Wert als Zustand beibehält, aber das wäre übertrieben, wenn die normale verkettete Bewertung funktioniert.
Dax Fohl

7

Eine andere Version des Ansatzes der try- Methode. Dieser erlaubt typisierte Ausnahmen, da es für jede Berechnung einen Ausnahmetyp gibt:

    public bool Try<T>(Func<double> func, out double d) where T : Exception
    {
      try
      {
        d = func();
        return true;
      }
      catch (T)
      {
        d = 0;
        return false;
      }
    }

    // usage:
    double d;
    if (!Try<Calc1Exception>(() = calc1(), out d) && 
        !Try<Calc2Exception>(() = calc2(), out d) && 
        !Try<Calc3Exception>(() = calc3(), out d))

      throw new NoCalcsWorkedException();
    }

Sie können die verschachtelten ifs tatsächlich vermeiden, indem Sie stattdessen &&zwischen den einzelnen Bedingungen verwenden.
DeCaf

4

In Perl können Sie tun foo() or bar(), was ausgeführt wird, bar()wenn foo()fehlschlägt. In C # sehen wir dieses Konstrukt "if fail, then" nicht, aber es gibt einen Operator, den wir für diesen Zweck verwenden können: den Null-Coalesce-Operator?? , der nur fortgesetzt wird, wenn der erste Teil null ist.

Wenn Sie die Signatur Ihrer Berechnungen ändern können und wenn Sie entweder die Ausnahmen (wie in den vorherigen Beiträgen gezeigt) umbrechen oder sie neu schreiben, um sie zurückzugeben null, wird Ihre Codekette immer kürzer und dennoch leicht zu lesen:

double? val = Calc1() ?? Calc2() ?? Calc3() ?? Calc4();
if(!val.HasValue) 
    throw new NoCalcsWorkedException();

Ich habe die folgenden Ersetzungen für Ihre Funktionen verwendet, was zu dem Wert 40.40in führt val.

static double? Calc1() { return null; /* failed */}
static double? Calc2() { return null; /* failed */}
static double? Calc3() { return null; /* failed */}
static double? Calc4() { return 40.40; /* success! */}

Mir ist klar, dass diese Lösung nicht immer anwendbar ist, aber Sie haben eine sehr interessante Frage gestellt, und ich glaube, obwohl der Thread relativ alt ist, ist dies ein Muster, das es wert ist, in Betracht gezogen zu werden, wenn Sie die Änderungen vornehmen können.


1
Ich möchte nur "Danke" sagen. Ich habe versucht umzusetzen, worüber Sie gesprochen haben . Ich hoffe, dass ich es richtig verstanden habe.
AlexMelw

3

Da die Berechnungsmethoden dieselbe parameterlose Signatur haben, können Sie sie in einer Liste registrieren, diese Liste durchlaufen und die Methoden ausführen. Wahrscheinlich ist es für Sie sogar noch besser, die Func<double>Bedeutung "eine Funktion, die ein Ergebnis vom Typ zurückgibt double" zu verwenden.

using System;
using System.Collections.Generic;

namespace ConsoleApplication1
{
  class CalculationException : Exception { }
  class Program
  {
    static double Calc1() { throw new CalculationException(); }
    static double Calc2() { throw new CalculationException(); }
    static double Calc3() { return 42.0; }

    static void Main(string[] args)
    {
      var methods = new List<Func<double>> {
        new Func<double>(Calc1),
        new Func<double>(Calc2),
        new Func<double>(Calc3)
    };

    double? result = null;
    foreach (var method in methods)
    {
      try {
        result = method();
        break;
      }
      catch (CalculationException ex) {
        // handle exception
      }
     }
     Console.WriteLine(result.Value);
   }
}

3

Sie können eine Aufgabe / ContinueWith verwenden und nach der Ausnahme suchen. Hier ist eine nette Erweiterungsmethode, um es hübsch zu machen:

    static void Main() {
        var task = Task<double>.Factory.StartNew(Calc1)
            .OrIfException(Calc2)
            .OrIfException(Calc3)
            .OrIfException(Calc4);
        Console.WriteLine(task.Result); // shows "3" (the first one that passed)
    }

    static double Calc1() {
        throw new InvalidOperationException();
    }

    static double Calc2() {
        throw new InvalidOperationException();
    }

    static double Calc3() {
        return 3;
    }

    static double Calc4() {
        return 4;
    }
}

static class A {
    public static Task<T> OrIfException<T>(this Task<T> task, Func<T> nextOption) {
        return task.ContinueWith(t => t.Exception == null ? t.Result : nextOption(), TaskContinuationOptions.ExecuteSynchronously);
    }
}

1

Wenn der tatsächliche Typ der ausgelösten Ausnahme keine Rolle spielt, können Sie einfach einen typenlosen Catch-Block verwenden:

var setters = new[] { calc1, calc2, calc3 };
bool succeeded = false;
foreach(var s in setters)
{
    try
    {
            val = s();
            succeeded = true;
            break;
    }
    catch { /* continue */ }
}
if (!suceeded) throw new NoCalcsWorkedException();

Ruft das nicht immer jede Funktion in der Liste auf? Vielleicht möchten Sie (Wortspiel nicht beabsichtigt) in so etwas wie if(succeeded) { break; }Post-Catch werfen .
Ein Lebenslauf vom

1
using System;

namespace Utility
{
    /// <summary>
    /// A helper class for try-catch-related functionality
    /// </summary>
    public static class TryHelper
    {
        /// <summary>
        /// Runs each function in sequence until one throws no exceptions;
        /// if every provided function fails, the exception thrown by
        /// the final one is left unhandled
        /// </summary>
        public static void TryUntilSuccessful( params Action[] functions )
        {
            Exception exception = null;

            foreach( Action function in functions )
            {
                try
                {
                    function();
                    return;
                }
                catch( Exception e )
                {
                    exception   = e;
                }
            }

            throw exception;
        }
    }
}

Und benutze es so:

using Utility;

...

TryHelper.TryUntilSuccessful(
    () =>
    {
        /* some code */
    },
    () =>
    {
        /* more code */
    },
    calc1,
    calc2,
    calc3,
    () =>
    {
        throw NotImplementedException();
    },
    ...
);

1

Es scheint, dass die Absicht des OP darin bestand, ein gutes Muster für die Lösung seines Problems und die Lösung des aktuellen Problems zu finden, mit dem er in diesem Moment zu kämpfen hatte.

OP: "Ich könnte jede Berechnung in eine Hilfsmethode einbinden, die bei einem Fehler null zurückgibt, und dann einfach den ??Operator verwenden. Es gibt jedoch eine Möglichkeit, dies allgemeiner zu tun (dh ohne für jede Methode, die ich möchte, eine Hilfsmethode schreiben zu müssen." Ich habe darüber nachgedacht, eine statische Methode mit Generika zu schreiben, die eine bestimmte Methode in einen Versuch / Fang einschließt und bei einem Fehler null zurückgibt, bin mir aber nicht sicher, wie ich vorgehen soll. Irgendwelche Ideen? "

Ich habe viele gute Muster gesehen, die verschachtelte Try-Catch-Blöcke vermeiden , die in diesem Feed veröffentlicht wurden, aber keine Lösung für das oben genannte Problem gefunden. Hier ist also die Lösung:

Wie oben erwähnt, wollte er ein Wrapper-Objekt erstellen, das nullbei einem Fehler zurückkehrt . Ich würde es einen Pod nennen ( ausnahmesicherer Pod ).

public static void Run()
{
    // The general case
    // var safePod1 = SafePod.CreateForValueTypeResult(() => CalcX(5, "abc", obj));
    // var safePod2 = SafePod.CreateForValueTypeResult(() => CalcY("abc", obj));
    // var safePod3 = SafePod.CreateForValueTypeResult(() => CalcZ());

    // If you have parameterless functions/methods, you could simplify it to:
    var safePod1 = SafePod.CreateForValueTypeResult(Calc1);
    var safePod2 = SafePod.CreateForValueTypeResult(Calc2);
    var safePod3 = SafePod.CreateForValueTypeResult(Calc3);

    var w = safePod1() ??
            safePod2() ??
            safePod3() ??
            throw new NoCalcsWorkedException(); // I've tested it on C# 7.2

    Console.Out.WriteLine($"result = {w}"); // w = 2.000001
}

private static double Calc1() => throw new Exception("Intentionally thrown exception");
private static double Calc2() => 2.000001;
private static double Calc3() => 3.000001;

Was aber, wenn Sie einen sicheren Pod für ein Referenztyp-Ergebnis erstellen möchten, das von CalcN () -Funktionen / -Methoden zurückgegeben wird?

public static void Run()
{
    var safePod1 = SafePod.CreateForReferenceTypeResult(Calc1);
    var safePod2 = SafePod.CreateForReferenceTypeResult(Calc2);
    var safePod3 = SafePod.CreateForReferenceTypeResult(Calc3);

    User w = safePod1() ?? safePod2() ?? safePod3();

    if (w == null) throw new NoCalcsWorkedException();

    Console.Out.WriteLine($"The user object is {{{w}}}"); // The user object is {Name: Mike}
}

private static User Calc1() => throw new Exception("Intentionally thrown exception");
private static User Calc2() => new User { Name = "Mike" };
private static User Calc3() => new User { Name = "Alex" };

class User
{
    public string Name { get; set; }
    public override string ToString() => $"{nameof(Name)}: {Name}";
}

Möglicherweise stellen Sie fest, dass "für jede Methode, die Sie verwenden möchten, keine Hilfsmethode geschrieben werden muss" .

Die zwei Arten von Pods (für ValueTypeResults und ReferenceTypeResults) sind ausreichend .


Hier ist der Code von SafePod. Es ist jedoch kein Container. Stattdessen wird ein ausnahmesicherer Delegate-Wrapper für ValueTypeResults und ReferenceTypeResults erstellt.

public static class SafePod
{
    public static Func<TResult?> CreateForValueTypeResult<TResult>(Func<TResult> jobUnit) where TResult : struct
    {
        Func<TResult?> wrapperFunc = () =>
        {
            try { return jobUnit.Invoke(); } catch { return null; }
        };

        return wrapperFunc;
    }

    public static Func<TResult> CreateForReferenceTypeResult<TResult>(Func<TResult> jobUnit) where TResult : class
    {
        Func<TResult> wrapperFunc = () =>
        {
            try { return jobUnit.Invoke(); } catch { return null; }
        };

        return wrapperFunc;
    }
}

So können Sie die Null-Koaleszenz nutzen Betreiber ??mit der Kraft des kombinierten Bürger erster Klasse Einheiten ( delegaten).


0

Sie haben Recht, jede Berechnung zu verpacken, aber Sie sollten nach dem Tell-Don't-Ask-Prinzip einbrechen.

double calc3WithConvertedException(){
    try { val = calc3(); }
    catch (Calc3Exception e3)
    {
        throw new NoCalcsWorkedException();
    }
}

double calc2DefaultingToCalc3WithConvertedException(){
    try { val = calc2(); }
    catch (Calc2Exception e2)
    {
        //defaulting to simpler method
        return calc3WithConvertedException();
    }
}


double calc1DefaultingToCalc2(){
    try { val = calc2(); }
    catch (Calc1Exception e1)
    {
        //defaulting to simpler method
        return calc2defaultingToCalc3WithConvertedException();
    }
}

Die Operationen sind einfach und können ihr Verhalten unabhängig ändern. Und es spielt keine Rolle, warum sie Standard sind. Als Beweis können Sie calc1DefaultingToCalc2 wie folgt implementieren:

double calc1DefaultingToCalc2(){
    try { 
        val = calc2(); 
        if(specialValue(val)){
            val = calc2DefaultingToCalc3WithConvertedException()
        }
    }
    catch (Calc1Exception e1)
    {
        //defaulting to simpler method
        return calc2defaultingToCalc3WithConvertedException();
    }
}

-1

Es hört sich so an, als hätten Ihre Berechnungen mehr gültige Informationen als nur die Berechnung selbst. Vielleicht wäre es für sie sinnvoller, ihre eigene Ausnahmebehandlung durchzuführen und eine "Ergebnis" -Klasse zurückzugeben, die Fehlerinformationen, Wertinformationen usw. enthält. Denken Sie daran, dass die AsyncResult-Klasse dem asynchronen Muster folgt. Sie können dann das tatsächliche Ergebnis der Berechnung auswerten. Sie können dies rationalisieren, indem Sie sich vorstellen, dass eine fehlgeschlagene Berechnung genauso informativ ist, als ob sie bestanden würde. Daher ist eine Ausnahme eine Information, kein "Fehler".

internal class SomeCalculationResult 
{ 
     internal double? Result { get; private set; } 
     internal Exception Exception { get; private set; }
}

...

SomeCalculationResult calcResult = Calc1();
if (!calcResult.Result.HasValue) calcResult = Calc2();
if (!calcResult.Result.HasValue) calcResult = Calc3();
if (!calcResult.Result.HasValue) throw new NoCalcsWorkedException();

// do work with calcResult.Result.Value

...

Natürlich frage ich mich mehr über die Gesamtarchitektur, mit der Sie diese Berechnungen durchführen.


Dies ist in Ordnung - ähnlich wie OP vorgeschlagen hat, was die Berechnungen angeht. Ich würde einfach so etwas bevorzugen while (!calcResult.HasValue) nextCalcResult(), anstatt einer Liste von Calc1, Calc2, Calc3 usw.
Kirk Broadhurst

-3

Was ist mit der Verfolgung der Aktionen, die Sie ausführen ...

double val;
string track = string.Empty;

try 
{ 
  track = "Calc1";
  val = calc1(); 

  track = "Calc2";
  val = calc2(); 

  track = "Calc3";
  val = calc3(); 
}
catch (Exception e3)
{
   throw new NoCalcsWorkedException( track );
}

4
Wie hilft das? Wenn calc1 () fehlschlägt, wird cals2 niemals ausgeführt!
DeCaf

Dies löst das Problem nicht. Führen Sie calc1 nur aus, wenn calc2 fehlschlägt, führen Sie calc3 nur aus, wenn calc1 && calc2 fehlschlägt.
Jason

+1 orn. Das ist was ich mache. Ich muss nur einen Fang codieren , die an mich gesendete Nachricht ( trackin diesem Fall), und ich weiß genau, durch welches Segment in meinem Code der Block fehlgeschlagen ist. Vielleicht hätten Sie Mitglieder wie DeCaf darüber informieren sollen, dass die trackNachricht an Ihre benutzerdefinierte Fehlerbehandlungsroutine gesendet wird, mit der Sie Ihren Code debuggen können. Klingt so, als hätte er deine Logik nicht verstanden.
JP2-Code

Nun, @DeCaf ist korrekt, mein Codesegment führt nicht weiter die nächste Funktion aus, nach der jjoelson gefragt hat, da meine Lösung nicht realisierbar ist
Orn Kristjansson
Durch die Nutzung unserer Website bestätigen Sie, dass Sie unsere Cookie-Richtlinie und Datenschutzrichtlinie gelesen und verstanden haben.
Licensed under cc by-sa 3.0 with attribution required.