【问题标题】:How to omit a value in a select lambda?如何省略选择 lambda 中的值?
【发布时间】:2013-02-11 20:56:52
【问题描述】:

我想做一个简单的 CSV 解析器。它应该通过逗号分隔值列表并将它们放在IList<int> 中。这些值应为整数。如果一个值不可解析,我只想省略它。

这是我目前的代码:

csv.Split(',').Select(item =>
{
    int parsed;
    if (int.TryParse(item, out parsed))
    {
        return parsed;
    }
    continue; //is not allowed here
}).ToList();

但是,这里(当然)不允许使用continue如何在我的选择实现中省略一个值?

注意:我当然可以使用 foreach 或 LINQ 表达式,但我想知道如何使用 lambda。

【问题讨论】:

    标签: c# linq select lambda


    【解决方案1】:

    怎么样:

    public static IEnumerable<int> ExtractInt32(this IEnumerable<string> values) {
        foreach(var s in values) {
            int i;
            if(int.TryParse(s, out i)) yield return i;
        }
    }
    

    然后:

    var vals = csv.Split(',').ExtractInt32().ToList();
    

    这里的好东西:

    • 避免使用神奇的“前哨”数字(如int.MinValue
    • 避免了单独且断开连接的“它是有效的”/“解析”步骤(因此没有重复)

    【讨论】:

    • 其实,再想一想,这是我认为最好的答案。
    • @Laurant 关于“魔术”的好处是你可以看看幕后,看看这个技巧是如何完成的。 IIRC Jon 的书“C# in Depth”第 6 章详细介绍了这一点。而且那个章节可能仍然是免费的(很长一段时间都是演示章节)。
    • 虽然没有使用 lambda,但我的第一个意图是,这是最优雅的解决方案。谢谢@MarcGravell
    • @Marcel 有时,如果问题是“什么是最好的打螺丝的锤子?”,最合适的答案是改用螺丝刀。
    【解决方案2】:

    Select 转换一个值。它不过滤。 Where 正在这样做:

    csv.Split(',')
       .Select(item =>
               {
                   int parsed;
                   return new { IsNumber = int.TryParse(item, out parsed), 
                                Value = parsed };
               })
       .Where(x => x.IsNumber)
       .Select(x => x.Value);
    

    此外,请参阅this answer 了解一个聪明、简短的方法。请注意,“聪明”的含义在这里并不完全是肯定的。

    【讨论】:

    • 您的解决方案应该可以工作,但是,使用新的匿名类型让我感到尴尬。这似乎比 foreach 替代方案要贵得多。
    • @Marcel:确实。我永远不会使用这样的代码。我会在这里循环。但是您想在 LINQ 中执行此操作(请参阅您的最后一句话)。
    • 如果不喜欢匿名类型,您可以使用可空类型,如.Select(item =&gt; { int parsed; return int.TryParse(item, out parsed) ? parsed : (int?)null; })。不过,可能不会比匿名类型快。您以令人震惊的方式使用“关闭”的链接答案令人着迷。
    • @JeppeStigNielsen:另一个答案的重点实际上是 LINQ 对对象的延迟性质。可空类型的好主意。
    【解决方案3】:

    一种方法是返回一些默认值,然后跳过它。

    errorInt = int.MinValue;
    csv.Split(',').Select(item =>
    {
        int parsed;
        if (int.TryParse(item, out parsed))
        {
            return parsed;
        }
        else
        {
            return errorInt;
        }
    
    }).Where(val => val != errorInt).ToList();
    

    【讨论】:

      【解决方案4】:

      我认为你有三个选择:

      1. 请改用SelectMany,这样您就可以将希望省略的元素作为空可枚举返回(否则返回长度为 1 的可枚举)。
      2. 使用一个你确定不会在集合中的 int 值(例如-1)来表示“省略”,然后将它们过滤掉。这种方法很脆弱,因为您可能会选择一个随后出现在集合中的值,这将导致一个微妙的错误。 (您可以通过使用更大的数据类型来缓解这种情况,例如long 并选择一个超出int 范围的值,但随后您需要转换回int。)
      3. 改用Nullable&lt;int&gt; (int?),然后过滤掉null 值。

      1:

      csv.Split(',').SelectMany(item =>
          {
              int parsed;
              if (int.TryParse(item, out parsed))
              {
                  return new[] {parsed};
              }
      
              return Enumerable.Empty<int>();   
          }
      

      3:

      csv.Split(',').Select(item =>
          {
              int parsed;
              if (int.TryParse(item, out parsed))
              {
                  return (int?) parsed;
              }
      
              return (int?) null;
           }
          .Where(item => item.HasValue)
          .Select(item => item.Value);
      

      【讨论】:

      • 好主意,但无论哪种方式,这仍然使整个事情变得比我认为的更复杂。
      【解决方案5】:

      试试这个:

      int dummy;
      sv.Split(',').Where(c => int.TryParse(c,out dummy)).Select(c => int.Parse(c));
      

      int.TryParse(..) 只是检查它是否是要转换为 int 的有效字符串。 out 参数被忽略了——我们不再需要它了。

      我们知道,只有那些“生成”到 Select() 的字符串值才是可以安全地解析为 int 的值。

      【讨论】:

      • 一个好主意。但是不好吃的是两次调用 parse 方法,一次调用就可以提供我需要的所有信息。
      • 我不介意 :) 必须有大量的数据才有意义。如果是这样的话 - 也许Parallel.ForEach() 会是答案
      【解决方案6】:

      为什么不在数组上使用Where,然后才选择正确的整数

      csv.Split(',')
          .Where(item => 
                { 
                    int parsed; 
                    return int.TryParse(item, out parsed); 
                })
          .Select(item => Convert.ToInt32(item));
      

      【讨论】:

      • 呃,char 不是只对字符起作用,而不是对字符串起作用吗?我的号码可以有多个数字。
      【解决方案7】:

      我可能只会使用:

      csv.Split(',').Where(item => isValid(item)).Select(item => TransformationExpression(item));
      

      或者,

      csv.Split(',').Select(item => ReturnsDummyValueIfInvalid(item)).Where(item => item != DummyValue);
      

      【讨论】:

        【解决方案8】:
        int TempInt;
        List<int> StuffIWant = csv.Split(',').Where(item => int.TryParse(item, TempInt)).ToList();
        

        【讨论】:

        • 这会给你一个List&lt;string&gt; - 它不会保留解析的值。此外,如果没有out,它将无法编译。
        • 好点@MarcGravell - 我错过了问题中的 IList 位。
        猜你喜欢
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 2016-12-17
        • 1970-01-01
        • 2011-01-22
        • 1970-01-01
        相关资源
        最近更新 更多