Вопрос не про то, как красиво программировать, чтобы этот вопрос не возникал, а про красивую обертку над 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?
Спасибо!
Клюнула идейка, которая позволяет уместиться в один блок.
Использовать можно так:
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 { }
}
}
Я предлагаю наоборот обьеденить 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 {};
Уже как я понял смысла практически не имеет.
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. Тихие исключения опасно использовать, потому что в итоге очень сложно отыскать ошибку.
Можно попробовать функционального подход:
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#
Сборка персонального компьютера от Artline: умный выбор для современных пользователей