【问题标题】:How to rewrite bool TrySomething(List<T> items, out List<T> itemsThatFailed)如何重写 bool TrySomething(List<T> items, out List<T> itemsThatFailed)
【发布时间】:2013-11-20 12:44:32
【问题描述】:

我有这个功能:

bool TrySomething(IEnumerable<T> items, out List<T> listOfProblemItems)
{
    //...

    return listOfProblemItems != null && listOfProblemItems.Count > 0;
}

现在的工作方式与您对“TrySomething”模式的预期相反(例如TryParse):

如果出现问题,它会返回true(表示out 参数已填充了信息)。所以我可以这样称呼它:

if(TrySomething(items, out problemItems))
{
   //handle problems
   //"but.. it returned 'true'! Why are there problems? I'm confused."
}

如果有问题返回true 并不直观,但如果我将其更改为false,那么out 参数将填充false 上的信息,从而打破“TrySomething”模式。

if(TrySomething(items, out problemItems) == false)
{
   //handle problems
   //"the 'out' parameter is used? but it returned false! I'm confused."
}

我不是抛出异常的替代方法,因为这是一个高性能部件,我需要一个明确定义的列表,列出哪些项目无论如何都失败了。

我可以将它返回问题列表并将其命名为TrySomethingAndReturnAnyProblems,但是这个名字很难看,调用代码必须检查返回的列表是否为空和计数,我' d 宁愿在函数中完成,因为无论如何它总是必须完成。

List<T> problems = TrySomethingAndReturnAnyProblems(items);
if(problems != null && problems.Count > 0)
{
    //ugly and introduces duplicate code every time the function is used.
}

如何在保持“TrySomething”模式的简洁性的同时将其变成可读的、自记录的函数定义?

【问题讨论】:

  • return listOfProblemItems == null || listOfProblemItems.Count == 0; 怎么样?顺便说一句,我会删除 bool 返回值并直接返回列表。调用者可以简单地检查一个空列表......
  • 也许你不应该使用TryX 模式。 TryX 方法将 out 参数用于结果,而不是用于报告问题。你马上就打破了这种模式。
  • 首先,自定义异常类型当然可以包含问题列表。其次,异常在不被抛出时并不慢。换句话说:当你的程序不工作时,它真的必须不能快速工作吗?最后但并非最不重要的一点是,异常可能并不像您想象的那么慢。您正在构建一个问题列表,然后循环访问该列表。该过程中的异常成本很可能可以忽略不计。
  • @KrisVandermotten:他正在编写一个预计会经常失败的方法,否则该方法本身将是多余的。
  • int TrySomething(IEnumerable&lt;T&gt; items, Action&lt;T&gt; problemCallback) 怎么样?

标签: c# design-patterns coding-style out


【解决方案1】:

编写一个扩展方法来简单地过滤掉有问题的项目怎么样?这样,您只能取回正常的项目,例如:

public static IEnumerable<T> ExceptProblematic(this IEnumerable<T> items)
{
  foreach (var item in items)
  {
    if (!IsProblematic(item))
      yield return item;
  }
}

这样你可以像这样使用它:

var validItems = items.ExceptProblematic();

或者,您可以编写一个查询以仅返回有问题的项目,然后进行另一个查询以将它们过滤掉,例如:

var problemItems = items.Where(item => IsProblematic(item));
var validItems = items.Except(problemItems);

然后相应地处理问题项:

if (problemItems.Any()) 
{
   // handle problem items
}

【讨论】:

  • 我不确定IsProblematic(item) 会做什么,除了尝试运行操作并查看。在这种情况下,我可以创建一个 bool TrySomething(T item),但我不能,因为它是一个高性能函数,只有在传递大量项目时才有用。
【解决方案2】:

如何创建一个具有bool SuccessT Data 属性的Result&lt;T&gt; 类?

class Result<T>
{
     public bool Success {get; set;}
     public T Data {get; set;}
}

然后让TrySomething返回Result&lt;List&lt;T&gt;&gt;

Result<List<T>> result = TrySomething(enumerable);
if (!result.Success)
{
    List<T> problematicItems = result.Data;
    //...
}

【讨论】:

    【解决方案3】:

    我宁愿为结果创建一个特殊的类(参见 Ahmed KRAIEM 的回答) 但我会以一些不同的(精心设计的)方式来做:

      public sealed class SomeResultWithDescription<TItem>: IReadOnlyList<TItem> {
        private List<TItem> m_Problems;
        private Boolean m_Success = true; // <- the field may be redundant 
    
        // may be redundant: if there's no situation when the result is falure
        // even if there're no problems enlisted
        internal SomeResultWithDescription(List<TItem> problems, Boolean success)
          : this(problems) {
    
          m_Success = success;
        }
    
        internal SomeResultWithDescription(List<TItem> problems)
          : base() {
    
          if (Object.ReferenceEquals(null, problems))
            m_Problems = new List<TItem>();
          else
            m_Problems = problems;
        }
    
        public IReadOnlyList<TItem> Problems {
          get {
            return m_Problems;
          }
        }
    
        public Boolean Success {
          get {
            if (!m_Success)
              return false;
            else if (m_Problems.Count > 0)
              return false;
    
            return true;
          }
        }
    
        public Boolean ToBoolean() {
          return Success;
        }
    
        public static implicit operator Boolean(SomeResultWithDescription<TItem> value) {
          if (Object.ReferenceEquals(null, value))
            return false;
    
          return value.ToBoolean();
        }
    
        public TItem this[int index] {
          get {
            return m_Problems[index];
          }
        }
    
        public int Count {
          get {
            return m_Problems.Count;
          }
        }
    
        public IEnumerator<TItem> GetEnumerator() {
          return m_Problems.GetEnumerator();
        }
    
        System.Collections.IEnumerator System.Collections.IEnumerable.GetEnumerator() {
          return m_Problems.GetEnumerator();
        }
      }
    
      ...
    
      // Your method will be
      SomeResultWithDescription<T> TrySomething(IEnumerable<T> items)
      {
        //...
    
        return new SomeResultWithDescription<T>(listOfProblemItems);
      }
    
      // So you can do
      // 1. Just do if no problems detected
      if (TrySomething(items)) { 
        ...
      }
    
      // 2. Do if no problems, analyze if there're problems
      var result = TrySomething(items);
    
      if (result) { 
        ... // no problems
      }
      else { // <- some problems to analyze
        foreach (var problem in result) {
          ...
        }
      }
    

    【讨论】:

      【解决方案4】:

      如果你担心打破TryX 模式,你为什么不编一个:

      public bool AntiTrySomething(IEnumerable<T> input, out IEnumerable<T> fails)
      {
          // implement me - return true if fail
      }
      

      只要您在整个代码库中保持一致,我认为这样的事情就很清楚,并且足以将您与“标准”区分开来,在保持熟悉的同时不会与它发生冲突。

      【讨论】:

        猜你喜欢
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 2019-05-20
        • 2019-04-13
        • 1970-01-01
        • 2021-11-26
        • 1970-01-01
        • 2021-12-29
        相关资源
        最近更新 更多