Шаблон, чтобы избежать вложенных попробовать ловить блоки? - PullRequest
112 голосов
/ 17 октября 2011

Рассмотрим ситуацию, когда у меня есть три (или более) способа выполнения вычислений, каждый из которых может дать сбой за исключением. Чтобы попытаться выполнить каждый расчет, пока мы не найдем тот, который был успешным, я делал следующее:

double val;

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

Есть ли какой-либо принятый шаблон, который добивается этого более хорошим способом? Конечно, я мог бы обернуть каждое вычисление во вспомогательный метод, который возвращает ноль при сбое, а затем просто использовать оператор ??, но есть ли способ сделать это более широко (т.е. без необходимости писать вспомогательный метод для каждого метода I хотите использовать)? Я думал о написании статического метода с использованием обобщений, который оборачивает любой данный метод в try / catch и возвращает null при сбое, но я не уверен, как бы я поступил об этом. Есть идеи?

Ответы [ 16 ]

1 голос
/ 24 марта 2018

Кажется, что целью ОП было найти хороший образец для решения своей проблемы и решения текущей проблемы, с которой он боролся в тот момент.

ОП: «Я мог бы обернуть каждый расчетв вспомогательном методе, который возвращает null при ошибке, а затем просто использует оператор ??, но есть ли способ сделать это в более общем смысле (т.е. без необходимости писать вспомогательный метод для каждого метода, который я хочу использовать)? I 'Мы думали о написании статического метода с использованием обобщений, который оборачивает любой данный метод в try / catch и возвращает null при ошибке, но я не уверен, как бы я поступил об этом. Любые идеи? "

Я видел много хороших шаблонов, которые избегают вложенных блоков try catch , опубликованных в этом фиде, но не нашел решения указанной выше проблемы.Итак, вот решение:

Как уже упоминалось выше, OP хотел создать объект-оболочку , который возвращает null при ошибке .Я бы назвал это 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;

Но что, если вы хотите создать безопасный модуль для результата типа ссылки , возвращаемого функциями / методами CalcN ().

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}";
}

Итак, вы можете заметить, что нет необходимости "для записивспомогательный метод для каждого метода, который вы хотите использовать ".

Два типа контейнеров (для ValueTypeResult с и ReferenceTypeResult с): достаточно .


Вот код SafePod.Это не контейнер, хотя.Вместо этого создает исключительную оболочку делегата для исключений как ValueTypeResult s, так и ReferenceTypeResult s.

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;
    }
}

Таким образом вы можете использовать оператор слияния нулей?? в сочетании с властью первоклассного гражданина субъектов (delegate с).

1 голос
/ 01 ноября 2012
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;
        }
    }
}

И используйте это так:

using Utility;

...

TryHelper.TryUntilSuccessful(
    () =>
    {
        /* some code */
    },
    () =>
    {
        /* more code */
    },
    calc1,
    calc2,
    calc3,
    () =>
    {
        throw NotImplementedException();
    },
    ...
);
1 голос
/ 17 октября 2011

Если фактический тип сгенерированного исключения не имеет значения, вы можете просто использовать блок перехвата типа:

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();
0 голосов
/ 19 октября 2011

Вы правы в отношении каждого вычисления, но вы должны выполнять его в соответствии с принципом "говорите, не спрашивайте".

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();
    }
}

Операции просты и могут независимо изменять свое поведение. И не важно, почему они по умолчанию. В качестве доказательства вы можете реализовать calc1DefaultingToCalc2 как:

double calc1DefaultingToCalc2(){
    try { 
        val = calc2(); 
        if(specialValue(val)){
            val = calc2DefaultingToCalc3WithConvertedException()
        }
    }
    catch (Calc1Exception e1)
    {
        //defaulting to simpler method
        return calc2defaultingToCalc3WithConvertedException();
    }
}
0 голосов
/ 17 октября 2011

Похоже, ваши расчеты содержат больше достоверной информации, чем просто сами расчеты.Возможно, им было бы более разумно выполнить свою собственную обработку исключений и вернуть класс «results», который содержит информацию об ошибках, информацию о значениях и т. Д. Подумайте, как класс AsyncResult выполняет следующие асинхронные шаблоны.Затем вы можете оценить реальный результат расчета.Вы можете рационализировать это, думая с точки зрения, что если расчет не удастся, он будет таким же информационным, как если бы он прошел.Следовательно, исключение - это часть информации, а не «ошибка».

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

...

Конечно, мне интересно больше об общей архитектуре, которую вы используете для выполнения этих вычислений.

0 голосов
/ 17 октября 2011

Как насчет отслеживания действий, которые вы делаете ...

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 );
}
...