如果string可解析为int,则选择parse int

时间:2011-02-10 19:28:40

标签: c# linq int

所以我有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在单行中过滤可解析值的最佳方法吗?

10 个答案:

答案 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