所以我有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
是否成功(返回bool)。然后,在投影中,我实际上正在执行解析。那感觉很难看。
这是使用LINQ在单行中过滤可解析值的最佳方法吗?
答案 0 :(得分:78)
在查询语法中很难做到这一点,但在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;
答案 1 :(得分:13)
它仍然是两个代码行,但您可以稍微缩短原始内容:
int asInt = 0;
var ints = from str in strings
where Int32.TryParse(str, out asInt)
select asInt;
由于TryParse已经在select时运行,因此填充了asInt
变量,因此您可以将其用作返回值 - 您无需再次解析它。
答案 2 :(得分:6)
如果你不介意你的同事在停车场跳你,有一种方法可以用一个真正的linq(没有分号)......
strings.Select<string, Func<int,int?>>(s => (n) => int.TryParse(s, out n) ? (int?)n : (int?)null ).Where(λ => λ(0) != null).Select(λ => λ(0).Value);
这是不切实际的,但在一个声明中这样做是一个非常有趣的挑战。
答案 3 :(得分: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;
答案 4 :(得分:2)
我这是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;
答案 5 :(得分:2)
如果你想定义一个扩展方法来做到这一点,我会创建一个易于使用的通用解决方案,而不是要求你为每个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 :(得分:1)
受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
答案 7 :(得分:0)
如果您正在寻找单行Linq表达式并且在每个循环上分配新对象都很好,我会使用功能更强大的SelectMany来执行一次Linq调用< / p>
var ints = strings.SelectMany(str => {
int value;
if (int.TryParse(str, out value))
return new int[] { value };
return new int[] { };
});
答案 8 :(得分:0)
我同意使用额外的变量感觉很难看。
根据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版本上运行:
答案 9 :(得分:0)
这里是一个(不太容易)真实的单行代码(使用C#7语法,没有临时的“ pair”变量):
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