Страницы

Поиск по вопросам

четверг, 2 января 2020 г.

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

#c_sharp


Вопрос не про то, как красиво программировать, чтобы этот вопрос не возникал, а про
красивую обертку над 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?
может быть нет идеального решения, тогда интересно просто услышать конструктивные мысли.


Спасибо!
    


Ответы

Ответ 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 { } } }

Ответ 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. Тихие исключения опасно использовать, потому что в итоге очень сложно отыскать ошибку.

Ответ 3



Можно попробовать функционального подход: public readonly struct Try { 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 SelectMany( this Try source, Func> selector, Func resultSelector) => new Try(() => { if (source.HasException) { return (default, source.Exception); } Try result = selector(source.Value); if (result.HasException) { return (default, result.Exception); } return (resultSelector(source.Value, result.Value), (Exception)null); }); public static Try Try(this TSource value) => value; public static Try Select( this Try source, Func selector) => source.SelectMany(value => selector(value).Try(), (value, result) => result); public static Try Throw( this Exception exception) => new Try(() => (default, exception)); public static Try Try(Func function) => new Try(() => (function(), (Exception)null)); public static Try Catch( this Try source, Func> handler, Func when = null) where TException : Exception => new Try(() => { 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 Catch( this Try source, Func> handler, Func when = null) => Catch(source, handler, when); public static TResult Finally( this Try source, Func, TResult> action) => action(source); public static void Finally( this Try source, Action> action) => action(source); } Пример: internal static Try Example(int? value) { if (value == null) { return Throw(new ArgumentNullException(nameof(value))); } } Немного про то откуда это: Category Theory via C# Fundamentals Видео: uDev Tech Meetup #10: Функциональный C#

Комментариев нет:

Отправить комментарий