【问题标题】:Select parsed int, if string was parseable to int选择解析 int,如果字符串可解析为 int
【发布时间】:2011-06-25 02:29:10
【问题描述】:

所以我有一个IEnumerable<string>,它可以包含可以被解析为int的值,以及不能被解析的值。

如您所知,Int32.Parse 会在字符串无法更改为 int 时引发异常,而 Int32.TryParse 可用于检查是否可以在不处理异常的情况下进行转换。

所以我想使用 LINQ 查询来单行解析那些可以解析为 int 的字符串,而不会在此过程中引发异常。我有一个解决方案,但希望社区提供有关这是否是最佳方法的建议。

这是我所拥有的:

int asInt = 0;
var ints = from str in strings
           where Int32.TryParse(str, out asInt)
           select Int32.Parse(str);

如您所见,我使用asInt 作为调用TryParse 的临时空间,只是为了确定TryParse 是否会成功(返回布尔值)。然后,在投影中,我实际上是在执行解析。感觉很丑。

这是使用 LINQ 在一行中过滤可解析值的最佳方法吗?

【问题讨论】:

  • 可以直接使用asInt作为选择值。
  • 对;看起来乔的回答抓住了这一点。实际上,我将其更改为我接受的答案,因为它比其他一些更简洁。

标签: c# linq int


【解决方案1】:

在查询语法中很难做到这一点,但在 lambda 语法中还不错:

var ints = strings.Select(str => {
                             int value;
                             bool success = int.TryParse(str, out value);
                             return new { value, success };
                         })
                  .Where(pair => pair.success)
                  .Select(pair => pair.value);

或者,您可能会发现值得编写一个返回 int? 的方法:

public static int? NullableTryParseInt32(string text)
{
    int value;
    return int.TryParse(text, out value) ? (int?) value : null;
}

然后你可以使用:

var ints = from str in strings
           let nullable = NullableTryParseInt32(str)
           where nullable != null
           select nullable.Value;

【讨论】:

  • 看起来与查询语法几乎相同。不过很高兴看到这种形式。
  • @byte:不同之处在于您的版本在接触另一个变量时会产生副作用......例如,您无法并行运行它。
  • 我希望框架有一个int?此类情况的 int.TryParse(string) 方法
  • 啊,是的,对于并发性,我可以看到这会有什么问题。在这种情况下,我可能会使用您的 lambda 语法。
  • @JonSkeet - 在处理来自实体框架的实体时如何做到这一点?避免 Not a store 方法异常。
【解决方案2】:

它仍然是两条代码行,但您可以稍微缩短一下原始代码:

int asInt = 0;
var ints = from str in strings
           where Int32.TryParse(str, out asInt)
           select asInt;

由于在选择时 TryParse 已经运行,asInt 变量已被填充,因此您可以将其用作返回值 - 您无需再次解析它。

【讨论】:

    【解决方案3】:

    如果你不介意你的同事在停车场跳你,那么有一种方法可以在一个真正的 linq 行(没有分号)中做到这一点......

    strings.Select<string, Func<int,int?>>(s => (n) => int.TryParse(s, out n) ? (int?)n : (int?)null ).Where(λ => λ(0) != null).Select(λ => λ(0).Value);
    

    这是不实际的,但在一个声明中这样做是一个非常有趣的挑战,不能放弃。

    【讨论】:

      【解决方案4】:

      我可能在某个地方有这个小实用方法(我实际上在我当前的代码库中这样做:-))

      public static class SafeConvert
      {
          public static int? ToInt32(string value) 
          {
              int n;
              if (!Int32.TryParse(value, out n))
                  return null;
              return n;
          }
      }
      

      然后你使用这个更干净的 LINQ 语句:

      from str in strings
      let number = SafeConvert.ToInt32(str)
      where number != null
      select number.Value;
      

      【讨论】:

        【解决方案5】:

        如果你想定义一个扩展方法来做到这一点,我会创建一个简单易用的通用解决方案,而不是要求你为每个 Try 函数编写一个新的 null-on-failure 包装器,并且要求您过滤掉空值。

        public delegate bool TryFunc<in TSource, TResult>(TSource arg, out TResult result);
        
        public static IEnumerable<TResult> SelectTry<TSource, TResult>(this IEnumerable<TSource> source, TryFunc<TSource, TResult> selector)
        {
            foreach(var s in source) {
                TResult r;
                if (selector(s, out r))
                    yield return r;
            }
        }
        

        用法:

        var ints = strings.SelectTry<string, int>(int.TryParse);
        

        C# 不能推断 SelectTry 的泛型类型参数有点尴尬。

        TryFunc 的 TResult 不能像 Func 那样协变(即 out TResult)。正如 Eric Lippert explains 输出参数是 actually just ref parameters 具有花哨的先写后读规则。)

        【讨论】:

        • 我喜欢在这里创建一个扩展方法
        【解决方案6】:

        我想这是 LINQ-to-objects:

        static int? ParseInt32(string s) {
            int i;
            if(int.TryParse(s,out i)) return i;
            return null;
        }
        

        然后在查询中:

        let i = ParseInt32(str)
        where i != null
        select i.Value;
        

        【讨论】:

          【解决方案7】:

          受 Carl Walsh 回答的启发,我更进一步,允许解析属性:

          public static IEnumerable<TResult> SelectTry<TSource, TValue, TResult>(
               this IEnumerable<TSource> source, 
               Func<TSource, TValue> selector, 
               TryFunc<TValue, TResult> executor)
          {
              foreach (TSource s in source)
              {
                  TResult r;
                  if (executor(selector(s), out r))
                      yield return r;
              }
          }
          

          这是一个示例,也可以在此 fiddle 中找到:

          public class Program
          {
              public static void Main()
              {       
                  IEnumerable<MyClass> myClassItems = new List<MyClass>() {new MyClass("1"), new MyClass("2"), new MyClass("InvalidValue"), new MyClass("3")};
              
                  foreach (int integer in myClassItems.SelectTry<MyClass, string, int>(x => x.MyIntegerAsString, int.TryParse))
                  {
                      Console.WriteLine(integer);
                  }
              }
          }
          
          public static class LinqUtilities
          {
              public delegate bool TryFunc<in TSource, TResult>(TSource arg, out TResult result);
          
              public static IEnumerable<TResult> SelectTry<TSource, TValue, TResult>(
                  this IEnumerable<TSource> source, 
                  Func<TSource, TValue> selector, 
                  TryFunc<TValue, TResult> executor)
              {
                  foreach (TSource s in source)
                  {
                      TResult r;
                      if (executor(selector(s), out r))
                          yield return r;
                  }
              }
          }
          
          public class MyClass
          {
              public MyClass(string integerAsString)
              {
                  this.MyIntegerAsString = integerAsString;
              }
          
               public string MyIntegerAsString{get;set;}
          }
          

          这个程序的输出:

          1

          2

          3

          【讨论】:

            【解决方案8】:

            我同意使用额外的变量感觉很丑

            基于Jon's answer更新到C# 7.0 解决方案可以使用新的var out feature:(不会短很多,但不需要内部范围或查询外临时变量)

            var result = strings.Select(s => new { Success = int.TryParse(s, out var value), value })
                                .Where(pair => pair.Success)
                                .Select(pair => pair.value);
            

            并与命名元组一起:

            var result = strings.Select(s => (int.TryParse(s, out var value), value))
                                .Where(pair => pair.Item1)
                                .Select(pair => pair.value);
            

            或者,如果建议在查询语法中使用它的方法:

            public static int? NullableTryParseInt32(string text)
            {
                return int.TryParse(text, out var value) ? (int?)value : null;
            }
            

            我还想建议一个没有额外方法的查询语法,但正如以下链接中所讨论的,c# 7.0 不支持out var 并导致编译错误:

            查询子句中不允许有输出变量和模式变量声明

            链接:Expression variables in query expressions


            这是 C# 7.0 的一项功能,可以让它在早期的 .NET 版本上运行:

            【讨论】:

            • 为什么不让它更短呢? var numbers = values.Select(x =&gt; int.TryParse(x, out int value) ? (int?) value : null); 呢?
            • @MarcusDock - 这也很好。我没有这样做的原因是原始结果是IEnumerable&lt;int&gt; 而不是IEnumerable&lt;int?&gt;,为了根据您的建议做到这一点,我还需要添加Where(x =&gt; x != null) and to convert to int` 而不是int?跨度>
            • @MarcusDock - 看到问题本身存在对所有非数字结果的过滤
            【解决方案9】:

            如果您正在寻找单行 Linq 表达式并且可以在每个循环上分配一个新对象,我会使用更强大的 SelectMany 来通过一次 Linq 调用来做到这一点

            var ints = strings.SelectMany(str => {
                int value;
                if (int.TryParse(str, out value))
                    return new int[] { value };
                return new int[] { };
            });
            

            【讨论】:

              【解决方案10】:

              这是一个(更不容易)真正的单行(使用 C#7 语法,没有临时“对”变量):

              strings.Select(s => Int32.TryParse(s, out var i) ? (int?)i : null).Where(i => i != null)
              

              如果您需要int 作为返回类型(而不是int?),请查看this full example

              var strings = new string[] { "12", "abc", "1b", "0" };
              var ints = strings.Select(s => Int32.TryParse(s, out var i) ? (int?)i : null).Where(i => i != null).Select(i => (int)i);
              foreach (var i in ints)
              {
                  Console.WriteLine(i * 100);
              }
              

              输出

              1200
              0
              

              【讨论】:

                【解决方案11】:

                我使用这个小扩展方法:

                public static class EnumerableExtensions
                {
                    public static IEnumerable<TResult> SelectWhere<TSource, TResult>(this IEnumerable<TSource> source, Func<TSource, (bool, TResult)> whereSelector)
                    {
                        foreach (var item in source)
                        {
                            if (whereSelector(item) is (true, var result))
                            {
                                yield return result;
                            }
                        }
                    }
                }
                

                this answer 不同,这很容易传入 lambda,而不需要带有 out 参数的方法。

                【讨论】:

                • 这不能回答问题。他们希望在一个语句中获得解析值。另外,传递这种类型的whereSelector 非常尴尬,也没有必要。已经有其他答案完全符合这里的要求。
                猜你喜欢
                • 1970-01-01
                • 1970-01-01
                • 1970-01-01
                • 1970-01-01
                • 1970-01-01
                • 1970-01-01
                • 2016-06-29
                • 2010-09-07
                相关资源
                最近更新 更多