Вызов метода по имени в цикле, повторяемом по объектам, в поисках шаблона проектирования

#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 Добавил наивную попытку к ответу.