#c# #exception #design-patterns #foreach #delegates
Вопрос:
Мне нужен совет или идея о том, как реализовать следующее. У меня есть интерфейс с множеством методов, каждый из которых может выдавать исключение (на самом деле это вызов WCF). Таким образом, каждый вызов должен быть обернут блоком try
public interface ISomeInterface
{
MethodThatCanThrow1(Arg1 arg);
..
MethodThatCanThrow101(Arg2 arg);
}
Теперь у нас есть коллекция объектов
var items = new List<ISomeInterface>();
Теперь я должен вызвать метод methodthatcantrow1 в цикле для каждого объекта. Метод может вызвать исключение, в этом случае мне нужно продолжить для оставшихся объектов
void CallMethodThatCanThrow1()
{
foreach(var item in items)
{
try
{
item.MethodThatCanThrow1(Arg1 arg);
}
catch(Exception ex)
{
// do something
}
}
}
Теперь мне нужно вызвать MethodThatCanThrow2, поэтому для второго метода мне нужно скопировать блок try catch.
void CallMethodThatCanThrow2()
{
foreach(var item in items)
{
try
{
item.MethodThatCanThrow2(Arg2 arg);
}
catch(Exception ex)
{
// remove failed item from items
// continue foreach for the rest
}
}
}
Поэтому для оставшегося 101 метода я должен скопировать и вставить весь блок, изменив только имя метода.
Поэтому я подумываю о его рефакторинге. Что я хочу, так это поместить блок try catch в отдельный метод и передать имя метода, которое необходимо вызвать
void CallMethodofISomeInterfaceForGivenReference(delegate methodProvide)
{
foreach(var item in items)
{
try
{
// take item and call method that is provided
}
catch(Exception ex)
{
// do something
}
}
}
Комментарии:
1. Примечание: в цикле foreach вы не можете изменить список.
2. Вы уже наметили решение, так в чем же ваш вопрос?
3. @CaptainComic Вы могли бы с помощью рефлексии, но это совершенно необязательно, потому что вы можете передать делегата. Например, у вас может быть
void TryCatchWrapper(Action action)
. Или вы могли бы работать с выражением, чтобы даже использовать лямбду …4. ^^ См. dotnetfiddle.net/uczmrI для наивного примера.
5. @Fildor прав: зачем передавать имя метода, которое не является типобезопасным, вместо делегата, который является типобезопасным?
Ответ №1:
Как уже предлагалось в комментариях: вместо использования имен отражений и методов я бы использовал делегатов.
Учитывая, что в вашем интерфейсе есть множество методов в этой форме:
public interface ISomeInterface
{
void MethodThatCanThrow1(Arg1 arg);
..
void MethodThatCanThrow101(Arg2 arg);
}
Из тебя мог бы получиться маленький помощник:
void TryCatchWrap<TParam1>( Action<TParam1> action, TParam1 param1, Action<Excpetion> handleException )
{
try
{
action(param1);
}
catch(Exception ex)
{
handleException(ex)
}
}
это будет называться как:
foreach( var item in collection )
{
tryCatchWrap<Arg1>( item.MethodThatCanThrow1, arg, HandleException )
}
…, где HandleException
будет void HandleException(Exception ex)
доступно в этом контексте.
Вы также можете адаптировать это к нескольким параметрам, спискам параметров или функциям без особых усилий:
TResult TryCatchWrap<TParam1, TResult>( Func<TParam1, TResult> action, TParam1 param1, Action<Excpetion> handleException )
{
try
{
return action(param1);
}
catch(Exception ex)
{
handleException(ex)
}
return default(TResult); // in case of Exception.
}
Из любопытства я попробовал, удалось ли мне вставить петлю в try
.
Вот что у меня есть:
using System;
using System.Linq;
using System.Linq.Expressions;
using System.Collections.Generic;
public class Program
{
public static void Main()
{
Arg1 arg = new Arg1();
// Some test data
IList<IMyType> list = Enumerable
.Range(0, 42)
.Select(x => (IMyType)new MyType(x))
.ToList();
var p = new Program();
p.CollectionTryParseWrapper<IMyType, Arg1>(list, arg,
(item, argument) => item.DoSomething(argument));
}
// Pass Expression instead of delegate so we can loop.
// Pass argument awkwardly to avoid closure.
public void CollectionTryParseWrapper<TInterface, TArgument>(
IList<TInterface> list,
TArgument arg1,
Expression<Action<TInterface, TArgument>> expr)
{
// compile the Expression to an executable delegate
var exec = expr.Compile();
// In case of Exception, we need to know where to continue.
int index = 0;
// Just loop
while(index < list.Count){
try
{
for( ; index < list.Count; index )
{
exec(list[index], arg1); // execute
}
}
catch(Exception ex)
{
// handle ex
Console.WriteLine("Exception" ex.Message);
}
finally
{
index ; // advance index!!
}
}
}
}
public interface IMyType
{
void DoSomething(Arg1 arg);
}
public class Arg1 {
public bool ShouldThrow(int index) => index % 5 == 0;
}
public class MyType : IMyType
{
private readonly int _index;
public MyType(int index)
{
this._index = index;
}
public void DoSomething(Arg1 arg)
{
if( arg.ShouldThrow(_index) ) throw new Exception($" at index {_index}");
Console.WriteLine($"Executing #{_index:#0}");
}
}
Выход:
Исключение при индексе 0 Выполнение #01 Выполнение #02 Выполнение #03 Выполнение #04 Исключение по индексу 5 Выполнение #06 Выполнение #07 Выполнение #08 Выполнение #09 Исключение по индексу 10 Выполнение #11 Выполнение #12 Выполнение #13 ...
Смотрите в действии: https://dotnetfiddle.net/WjoSZF
Комментарии:
1. Muchisimas gracias @Fildor Это довольно просто, но я застрял в этом
2. Интересно, можно ли разместить foreach внутри TryCatchWrap
3. Для этого потребуется еще немного усилий. Я предполагаю, что ваша цель состоит в том, чтобы свести к минимуму накладные расходы на создание контекста trycatch? Поначалу я тоже думал об этом. Но: Для этого вам нужно будет не только поймать исключение, но и выяснить, какой элемент (по какому индексу в коллекции) он произошел, а затем начать с индекса 1. Конечно, выполнимо, но я бы сделал это только в том случае, если это столкнется с реальными проблемами производительности.
4. @CaptainComic Добавил наивную попытку к ответу.