如何将字符串列表转换为int而忽略非整数

时间:2012-05-28 14:00:03

标签: linq lambda c#-3.0

我习惯于在不使用Linq或Lambda语句的情况下编写C#,但我想提高自己的理解力。我的代码看起来像用C#2.0编写,带有foreach循环:

        List<string> strings = new List<string>();
        strings.Add("1");
        strings.Add("blah");

        List<int> ints1 = new List<int>();
        foreach (string x in strings)
        {
            int r;
            if (int.TryParse(x, out r))
                ints1.Add(r);
        }

它正在执行一项简单的任务 - 从字符串列表中填充一个int列表,忽略任何实际不存在的int。 在我有限的经验中,Linq和Lambda的陈述似乎能够将foreach语句简化为非常简洁和可读的方式来做同样的事情。所以我想我会尝试使用C#3.0这个小小的。但我能想到的最好的是:

       IEnumerable<int> ints2 = strings.Select(x =>
                                     {
                                         int r;
                                         if (int.TryParse(x, out r))
                                             return r as int?;
                                         return null;
                                     })
                                     .Where<int?>(x => x != null)
                                     .Select(x => x.Value);

呸!我找不到在一次调用中将转换和过滤结合起来的方法,最终看起来比原来的更糟糕。有没有办法做到这一点,还是我应该坚持我的foreach?

编辑:总结一下调查结果,如果您使用的是.NET 4.0,有两种方法可以使用LINQ:

IEnumerable<int> ints1 = strings
.Where(x => new Int32Converter().IsValid(x))
.Select(x => int.Parse(x));//Broken in .NET 3.5 due to bug in Int32Converter


IEnumerable<int> int2 = strings
.Where(x => { int r; return int.TryParse(x, out r); })
.Select(x => int.Parse(x));

第二种方法更快,并且在.NET 3.5中没有被破坏。并且根据已接受的答案而不是foreach,可能没有理由不将其置于扩展方法中。 有关Int32Converter错误,请参阅connect issue

5 个答案:

答案 0 :(得分:3)

你可以用简单的linq

来做到这一点
int num = 0;
var ints = (from str in strings
            where int.TryParse(str,out num)
            select num).ToList();

答案 1 :(得分:3)

使用 LINQ 获取int的另一种方法是:

var integerList =
    strings
        .Where(x => new Int32Converter().IsValid(x))
        .Select(x => int.Parse(x));

[更新]

我使用LINQPad做了performance test

这里我报告代码和结果:

void Main()
{
    List<string> strings = new List<string>() { "1", "a", "3", "b" };

    Func<IEnumerable<string>, IEnumerable<int>> func1 =
        list => list.Where(x => new Int32Converter().IsValid(x)).Select(x => int.Parse(x));

    Func<IEnumerable<string>, IEnumerable<int>> func2 =
        list => { var ret = 0; return list.Where(x => int.TryParse(x, out ret)).Select(x => ret); };

    Benchmark
        .For(1000)
            .Execute("Int32Converter", () => func1(strings).Iterate())
            .Execute("int.TryParse", () => func2(strings).Iterate())
        .Report(4).Dump();
}

int.TryParseInt32Converter快约165倍。

[更新2]

TypeConverter.IsValid类继承的Int32Converter包含在.NET 4.0中修复的bug

答案 2 :(得分:1)

就像那样:

from s in strings
let parsed = s.TryParseInt32()
where parsed != null
select parsed.Value;

public static int? TryParseInt32(this string str)
{
    int i;
    if (!int.TryParse(str, NumberStyles.Integer, CultureInfo.InvariantCulture, out i))
        return null;
    return i;
}

TryParseInt32扩展名是可重用的。

这是另一种选择:

from s in strings
select s.TryParseInt32() into parsed
where parsed != null
select parsed.Value;

答案 3 :(得分:1)

我得到了一些这样的辅助方法

public static class CollectionUtilities {
    public static IEnumerable<int> ParseInt32(this IEnumerable<string> source) {
        return ParseInt32(source, NumberStyles.Integer, null);
    }

    public static IEnumerable<int?> TryParseInt32(this IEnumerable<string> source) {
        return TryParseInt32(source, NumberStyles.Integer, null);
    }

    public static IEnumerable<int> ParseInt32(this IEnumerable<string> source, IFormatProvider provider) {
        return ParseInt32(source, NumberStyles.Integer, provider);
    }

    public static IEnumerable<int?> TryParseInt32(this IEnumerable<string> source, IFormatProvider provider) {
        return TryParseInt32(source, NumberStyles.Integer, provider);
    }

    public static IEnumerable<int> ParseInt32(this IEnumerable<string> source, NumberStyles style) {
        return ParseInt32(source, style, null);
    }

    public static IEnumerable<int?> TryParseInt32(this IEnumerable<string> source, NumberStyles style) {
        return TryParseInt32(source, style, null);
    }

    public static IEnumerable<int> ParseInt32(this IEnumerable<string> source, NumberStyles style, IFormatProvider provider) {
        if (source == null)
            throw new ArgumentNullException("source");

        return ParseInt32Iterator(source, style, provider);
    }

    public static IEnumerable<int?> TryParseInt32(this IEnumerable<string> source, NumberStyles style, IFormatProvider provider) {
        if (source == null)
            throw new ArgumentNullException("source");

        return TryParseInt32Iterator(source, style, provider);
    }

    private static IEnumerable<int> ParseInt32Iterator(this IEnumerable<string> source, NumberStyles style, IFormatProvider provider) {
        foreach (string element in source) {
            yield return int.Parse(element, style, provider);
        }
    }

    private static IEnumerable<int?> TryParseInt32Iterator(this IEnumerable<string> source, NumberStyles style, IFormatProvider provider) {
        foreach (string element in source) {
            int value;
            bool success = int.TryParse(element, style, provider, out value);
            yield return success ? (int?)value : null;
        }
    }
}

使用这些帮助程序,您的代码将如下所示:

var ints = strings.TryParseInt32().Where(x => x != null).Select(x => x.Value).ToList();

答案 4 :(得分:1)

我将按如下方式定义自己的扩展方法:

public static IEnumerable<int> AsIntegers (this IEnumerable<string> strings) {
   foreach (var s in strings) {
      int r; if (int.TryParse (s, r)) yield return r;
   }
}

...

List<int> intList = new List (stringList.AsIntegers ());

var intList = stringList.AsIntegers ().ToList ();