Красивая обертка для try-catch в C#

150
22 февраля 2021, 11:00

Вопрос не про то, как красиво программировать, чтобы этот вопрос не возникал, а про красивую обертку над try-catch.

try
{
}
catch 
{
    // здесь ничего нет
}
  • эта хорошая штука, но но занимает много места, хотелось бы, чтобы умещалось в один блок, но выполняло тот же функционал

Делаю кусок кода, который подстраховочный и вообще не достоин, чтобы на него уделяли большое внимание (сейчас уделяю такое внимание только на будущее, потому, что уже не раз задумывался над этой issue, ну и чтоб отдохнуть). Он не стоит того, чтобы делать рефакторинг, чтоб изящничать. Нужно просто и быстро поставить try, но хотелось бы чтобы это выглядело красиво. Если там что-то не сработает, то ничего страшного, но хотелось бы еще, чтобы это не повлекло за собой появление Exception-а уже в важных местах, поэтому вопрос про try-catch.

Есть вариант сделать метод:

private void Try(Action codeBlock)
{
    try
    {
        codeBlock?.Invoke();
    } 
    catch 
    {
    } 
}

тогда вызов будет в одну строчку, но этот вариант не на 100% нравится:

  • кажется не красивым вызов Try( ()=>DoWork() );, хотелось бы что-то попроще, без наворотов

  • можно в аргумент передать вызов метода напрямую Try(MethodCall), это уже выглядит получше, но тогда в моем конкретном случае придется разбивать этот подстраховочный и второстепенный по важности метод на многие части, например, если случай такой:

    private static void EnsureSomethingWhichFailsAnyway(Someting input)
    {
        try
        {
            foreach (var x in input.GetAllX())
            {
                DoSmallThing(x);
                try
                {
                    x.SetPropertyValue = PossibleValues.BigValue;
                }
                catch { }
            }
        } catch { }
        try
        {
            foreach (var y in input.GetllY())
            {
                try
                {
                    y.Validate(StaticVars.A, StaticVars.B, StaticVars.C);
                }
                catch { }
            }
        }
        catch { }
    }
    
  • то есть, если разбивать такой метод на подметоды для вызова типа Try(MethodCall) то игра не будет стоить свеч.

  • можно ли как-то поместить в using?

  • может быть нет идеального решения, тогда интересно просто услышать конструктивные мысли.

Спасибо!

Answer 1

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

Использовать можно так:

    Try.AutoRunAction = () =>
    {
        //code block
    };

Вот сырая реализация:

    public class Try
    {
        // runs on set
        public static Action AutoRunAction
        {
            set
            {
                try { value?.Invoke(); }
                catch { }
            }
        }
    }
  • то же самое, что из метода, но меньше скобок
  • раньше был геттер, который посоветовали убрать
  • можно еще назвать AutoTryAction
  • принимаю критику

Пример:

    private static void SetAttributesNormal(DirectoryInfo dir)
    {
        AutoTryAction = () =>
        {
            foreach (var subDir in dir.GetDirectories())
            {
                SetAttributesNormal(subDir);
                AutoTryAction = () => subDir.Attributes = FileAttributes.Normal;
            }
        };
        AutoTryAction = () =>
        {
            foreach (var file in dir.GetFiles())
            {
                AutoTryAction = () => file.Attributes = FileAttributes.Normal;
            }
        };
    }
    private static Action AutoTryAction
    {
        set
        {
            try { value?.Invoke(); }
            catch { }
        }
    }
Answer 2

Я предлагаю наоборот обьеденить try-catch вместе, т.к. построение фрейма для ловли ошибок считаю затратной операцией. Сделать это можно через цикл. Пусть у вас 10 случаев. Вместо try{}catch{} прийдется писать case x:; break; боюсь сомнительный выигрыш. Разве что... авто-редактор кода не будет превращать это в 5 строк.

int nmax = 10;
int step = 0;
while (step < nmax) 
    try { // общий try
    for (int istep = step; istep < nmax; istep++)
       switch (istep) {
         case 0:;
             break;
         case 1:;
             break;
         // .....
          }
    } catch { step++;}

Обвертка вроде небольшая. Считаю что повысит быстродействие если не будет исключений... но незначительно. Более компактный вариант

 for (int step=0;step<10;step++) try { switch (step) { 
     case 0:; break;
      //.... 
 } } catch {};

Уже как я понял смысла практически не имеет.

  • Вариант с Try(MethodCall) - чуть более затратный по времени.
  • Вариант с using - не получится реализовать... явно, ну развечто если реализовать его так-само как и предыдущий вариант using (var x=new Try(MethodCall)) - что будет изврат. Что бы не было изврата... теоретически можно раскопать il-позицию... но на практике сделать проброс врядли выйдет.

Вариант 2. Базируясь на варианте 1, и зная номер линии кода, можно шаманить. Но опять же, "условно". Если считать, что каждая строка выполняется один раз, то можно сделать так:

bool ready = false;
int step= 0;
while (!ready) try {
     if (trysafe(ref step)) {  method1; /*шаг 1*/ };
     if (trysafe(ref step)) {  method2; /*шаг 2*/};
     ready = true;
     } catch {
     };
   }
//  отсекатель
bool trysafe(int ref kkey, [CallerLineNumber] int line = -1){
   if (kkey < line ) return false;
   kkey = line;
   return true;
   }

При исключении, произойдет цикл, и методы которые прошли в if будут пропущены независимо от того было исключение или нет. Отсекатель можно сделать через Dictionary (будет более "умный"). Но код должен предполагать разбивку на шаги как и в предыдущем случае, но шаги можно более "вольно" располагать. Промежутков между шагами не должно быть. Вызов trysafe - должен всегда происходить в разных строках программы. Аргумент kkey можно тоже сократить, и вынести в глобальную область, в зависимости от ситуации.

P.S. Тихие исключения опасно использовать, потому что в итоге очень сложно отыскать ошибку.

Answer 3

Можно попробовать функционального подход:

public readonly struct Try<T>
{
    private readonly Lazy<(T, Exception)> factory;
    public Try(Func<(T, Exception)> factory) =>
        this.factory = new Lazy<(T, Exception)>(() =>
        {
            try
            {
                return factory();
            }
            catch (Exception exception)
            {
                return (default, exception);
            }
        });
}
public static class TryExtensions
{
    public static Try<TResult> SelectMany<TSource, TSelector, TResult>(
        this Try<TSource> source,
        Func<TSource, Try<TSelector>> selector,
        Func<TSource, TSelector, TResult> resultSelector) =>
            new Try<TResult>(() =>
            {
                if (source.HasException)
                {
                    return (default, source.Exception);
                }
                Try<TSelector> result = selector(source.Value);
                if (result.HasException)
                {
                    return (default, result.Exception);
                }
                return (resultSelector(source.Value, result.Value), (Exception)null);
            });
    public static Try<TSource> Try<TSource>(this TSource value) => value;
    public static Try<TResult> Select<TSource, TResult>(
        this Try<TSource> source, Func<TSource, TResult> selector) =>
            source.SelectMany(value => selector(value).Try(), (value, result) => result);
    public static Try<T> Throw<T>(
        this Exception exception) => 
            new Try<T>(() => (default, exception));
    public static Try<T> Try<T>(Func<T> function) =>
        new Try<T>(() => (function(), (Exception)null));
    public static Try<T> Catch<T, TException>(
        this Try<T> source, Func<TException, Try<T>> handler, Func<TException, bool> when = null)
        where TException : Exception =>
            new Try<T>(() =>
            {
                if (source.HasException && 
                    source.Exception is TException exception && 
                    exception != null && (
                    when == null || 
                    when(exception)))
                {
                    source = handler(exception);
                }
                return source.HasException ? (default, source.Exception) : (source.Value, (Exception)null);
            });
    public static Try<T> Catch<T>(
        this Try<T> source, 
        Func<Exception, Try<T>> handler, 
        Func<Exception, bool> when = null) =>
            Catch<T, Exception>(source, handler, when);
    public static TResult Finally<T, TResult>(
        this Try<T> source, 
        Func<Try<T>, TResult> action) => action(source);
    public static void Finally<T>(
        this Try<T> source, 
        Action<Try<T>> action) => action(source);
}

Пример:

internal static Try<int> Example(int? value)
{
    if (value == null)
    {
        return Throw<int>(new ArgumentNullException(nameof(value)));
    }
}

Немного про то откуда это: Category Theory via C# Fundamentals

Видео: uDev Tech Meetup #10: Функциональный C#

READ ALSO
Информация о OnTriggerEnter2D

Информация о OnTriggerEnter2D

Есть такие методы как OnTriggerEnter2D и OnTriggerStay2DКак узнать когда объект входит в триггер, а когда начинается фаза Stay, я имею ввиду, есть ли какой-то...

143
Поиск по БД using entityframework in mvc project

Поиск по БД using entityframework in mvc project

Есть таблица в базе данных для нее есть контроллер

208
Mysql Округление decimal

Mysql Округление decimal

Поясните в чем проблема, тип данных поля decimal (106)

174
Как проверить url на изображение?

Как проверить url на изображение?

У меня есть база данныхОдин из столбцов в ней image

174