【问题标题】:Call method by name in loop iterating by objects, looking for design pattern在按对象迭代的循环中按名称调用方法,寻找设计模式
【发布时间】:2021-05-27 06:55:15
【问题描述】:

我需要关于如何实施以下内容的建议或想法。我有一个接口,其中包含许多方法,每个方法都可以抛出异常(实际上是 WCF 调用)。所以每个调用必须用 try 块包装

public interface ISomeInterface
{
 MethodThatCanThrow1(Arg1 arg);
 ..
 MethodThatCanThrow101(Arg2 arg);
}

现在我们有一个对象集合

var items = new List<ISomeInterface>();

现在我必须为每个对象循环调用 MethodThatCanThrow1 方法。方法可能会抛出异常,在这种情况下我需要继续处理剩余的对象

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块放在单独的Method中并传递需要调用的Method Name

void CallMethodofISomeInterfaceForGivenReference(delegate methodProvide)
{ 
 foreach(var item in items)
 {
  try
  {
     // take item and call method that is provided
  }
  catch(Exception ex)
  {
   // do something
  }
 }
}

【问题讨论】:

  • 注意:在foreach循环中不能修改列表。
  • 您已经提出了解决方案,那么您的问题是什么?
  • @CaptainComic 您可以使用反射,但这完全没有必要,因为您可以通过委托。例如,您可以拥有void TryCatchWrapper(Action action)。或者您可以使用表达式,因此您可以使用 lambda,甚至 ...
  • ^^ 见dotnetfiddle.net/uczmrI 一个简单的例子。
  • @Fildor 是对的:为什么要传递一个不是类型安全的方法名称,而不是一个 类型安全的委托?

标签: c# exception design-patterns foreach delegates


【解决方案1】:

正如 cmets 中已经建议的那样:我不使用反射和方法名称,而是使用委托。

鉴于,您的界面有很多这种形式的方法:

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

【讨论】:

  • Muchisimas gracias @Fildor 这很简单,但我被困在了这个
  • 我想知道这是否可以将 foreach 放在 TryCatchWrap 中
  • 这需要更多的努力。我猜你的目标是尽量减少 trycatch-Context 创建开销?我一开始也在考虑这个问题。但是:这不仅需要您捕获异常,还需要确定它发生在哪个项目(集合中的哪个索引处),然后从 index+1 重新开始。当然可行,但只有在遇到实际性能问题时我才会这样做。
  • @CaptainComic 为答案添加了一个天真的尝试。
猜你喜欢
  • 1970-01-01
  • 2011-01-13
  • 1970-01-01
  • 1970-01-01
  • 2013-10-15
  • 2011-11-25
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
相关资源
最近更新 更多