如果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的时候运行, asInt
variables被填充,所以你可以使用它作为你的返回值 – 你不需要再次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?