如果string可parsing为int,则selectparsed int

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

如你所知,如果一个string不能被改变为一个int, Int32.Parse会抛出一个exception,而Int32.TryParse可以用来检查转换是否可能而不处理这个exception。

所以我想用LINQ查询来单线parsing那些可以被parsing为int的string,而不会在程序中抛出exception。 我有一个解决scheme,但希望社区的意见,这是否是最好的方法。

这是我有:

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

所以,你可以看到,我使用asInt作为调用TryParse的暂存空间,只是为了确定TryParse是否会成功(返回布尔值)。 然后,在投影中,我实际上正在执行parsing。 那感觉很难看。

这是使用LINQ过滤单行可parsing值的最好方法吗?

在查询语法中很难做到这一点,但在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; 

这仍然是两个代码行,但你可以缩短你的原始一点:

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

由于TryParse已经在select的时候运行, asIntvariables被填充,所以你可以使用它作为你的返回值 – 你不需要再次parsing它。

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

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

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

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

 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; 

我会这是LINQ到对象:

 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; 

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

 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的genericstypes参数有点尴尬。

TryFunc的TResult不能像Func一样协变(即out TResult ),正如Eric Lippert所解释的那样,参数实际上只是具有特殊先入先出(read-before-read)规则的参数。

受到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; } } 

这个例子也可以在这个小提琴中find:

 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

如果你正在寻找一个单行的Linqexpression式,并且在每个循环中都分配一个新的对象,那么我将使用更强大的SelectMany来完成一个Linq调用

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

我同意使用额外的variables感觉很难看

基于Jon的回答并更新到C#7.0解决scheme,可以使用新的var out特性 🙁 不要短得多,但不需要内部作用域或查询tempvariables

 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; } 

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

查询子句中不允许输出variables和模式variables声明

链接: 查询expression式中的expression式variables


通过这是一个C#7.0function,可以让它在早期的.NET版本上工作:

  • C#7 .NET / CLR / Visual Studio版本要求
  • C#7.0是否适用于.NET 4.5?