71

So I have an IEnumerable<string> which can contain values that can be parsed as int, as well as values that cannot be.

As you know, Int32.Parse throws an exception if a string cannot be changed to an int, while Int32.TryParse can be used to check and see if the conversion was possible without dealing with the exception.

So I want to use a LINQ query to one-liner parse those strings which can be parsed as int, without throwing an exception along the way. I have a solution, but would like advice from the community about whether this is the best approach.

Here's what I have:

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

So as you can see, I'm using asInt as a scratch space for the call to TryParse, just to determine if TryParse would succeed (return bool). Then, in the projection, I'm actually performing the parse. That feels ugly.

Is this the best way to filter the parseable values in one-line using LINQ?

Gilad Green
  • 36,708
  • 7
  • 61
  • 95
dreadwail
  • 15,098
  • 21
  • 65
  • 96

12 Answers12

116

It's hard to do that in query syntax, but it's not too bad in lambda syntax:

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

Alternatively, you may find it worth writing a method which returns an int?:

public static int? NullableTryParseInt32(string text)
{
    int value;
    return int.TryParse(text, out value) ? (int?) value : null;
}

Then you can just use:

var ints = from str in strings
           let nullable = NullableTryParseInt32(str)
           where nullable != null
           select nullable.Value;
Jon Skeet
  • 1,421,763
  • 867
  • 9,128
  • 9,194
  • Seems like pretty much the same idea though, as the query syntax. Good to see in this form though. – dreadwail Feb 10 '11 at 19:33
  • 3
    @byte: The difference is that your version has side-effects in touching another variable... so you couldn't run it in parallel, for example. – Jon Skeet Feb 10 '11 at 19:37
  • 4
    I wish the framework had an int? int.TryParse(string) method for such cases – vc 74 Feb 10 '11 at 19:47
  • AH yes, for concurrency I can see how this would be problematic. I'd probably use your lambda syntax in that case. – dreadwail Feb 12 '11 at 20:52
  • @JonSkeet - how to do this when dealing with entities from Entity framework? To avoid the Not a store method exception. – Shiva Naru Dec 22 '15 at 20:58
  • @dotnetter: Search for similar posts with the error message... I'm sure there are plenty. If you can't find a related post, please post a new question. – Jon Skeet Dec 22 '15 at 21:48
20

It's still two codelines, but you can shorten up your original a little:

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

Since the TryParse already runs at the time of the select, the asInt variable is populated, so you can use that as your return value - you don't need to parse it again.

Joe Enos
  • 39,478
  • 11
  • 80
  • 136
  • 2
    This is not best practise. The moment when asInt is initialized is not predictable since it will happen when this LINQ query gets executed(possibly somewhere else in a different method). So you should not rely on asInt being initialized after this query. Better do it as Jon has shown in his answer. – Tim Schmelter Jul 01 '22 at 09:40
11

If you don't mind your coworkers jumping you in the parking lot there is a way to do this in one true line of linq (no semicolons) ....

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

It isn't practical, but doing this in one statement was far too interesting a challenge to pass up.

Kelly Robins
  • 7,168
  • 6
  • 43
  • 66
5

I'd probably have this little utility method somewhere (I actually do in my current codebase :-))

public static class SafeConvert
{
    public static int? ToInt32(string value) 
    {
        int n;
        if (!Int32.TryParse(value, out n))
            return null;
        return n;
    }
}

Then you use this much cleaner LINQ statement:

from str in strings
let number = SafeConvert.ToInt32(str)
where number != null
select number.Value;
driis
  • 161,458
  • 45
  • 265
  • 341
4

I agree that using the extra variable feels ugly.

Based on Jon's answer and updating to C# 7.0 solutions one can use the new var out feature: (not much shorter but no need for an inner scope or out of query temp variables)

var result = strings.Select(s => new { Success = int.TryParse(s, out var value), value })
                    .Where(pair => pair.Success)
                    .Select(pair => pair.value);

and together with named tuples:

var result = strings.Select(s => (int.TryParse(s, out var value), value))
                    .Where(pair => pair.Item1)
                    .Select(pair => pair.value);

Or if suggesting a method for it for the use in query syntax:

public static int? NullableTryParseInt32(string text)
{
    return int.TryParse(text, out var value) ? (int?)value : null;
}

I'd love to also suggest a query syntax without an extra method for it but as discussed in the following link out var is not supported by c# 7.0 and results in the compilation error:

Out variable and pattern variable declarations are not allowed within a query clause

The link: Expression variables in query expressions


Through this is a C# 7.0 feature one can get it to work on earlier .NET versions:

Gilad Green
  • 36,708
  • 7
  • 61
  • 95
  • Why don't make it shorter? What about `var numbers = values.Select(x => int.TryParse(x, out int value) ? (int?) value : null);` – Marcus.D Oct 10 '17 at 08:07
  • 1
    @MarcusDock - that is good too. The reason I did not go for that is that the original result is `IEnumerable` and not `IEnumerable` and in order to do that with your suggestion I'd also need to add `Where(x => x != null) and to convert to `int` instead of `int?` – Gilad Green Oct 10 '17 at 08:11
  • @MarcusDock - see that in question itself there is a filtering of all non numeric results – Gilad Green Oct 10 '17 at 08:16
3

If you want to define an extension method to do this, I'd create a general solution that is simple to use, instead of requiring you to write a new null-on-failure wrapper for each Try function, and requires you to filter out null values.

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

Usage:

var ints = strings.SelectTry<string, int>(int.TryParse);

It's a little awkward that C# can't infer SelectTry's generic type arguments.

(TryFunc's TResult can't be covariant (i.e. out TResult) like Func. As Eric Lippert explains out parameters are actually just ref parameters with fancy write-before-read rules.)

Community
  • 1
  • 1
Carl Walsh
  • 6,100
  • 2
  • 46
  • 50
2

I'd this is LINQ-to-objects:

static int? ParseInt32(string s) {
    int i;
    if(int.TryParse(s,out i)) return i;
    return null;
}

Then in the query:

let i = ParseInt32(str)
where i != null
select i.Value;
Marc Gravell
  • 1,026,079
  • 266
  • 2,566
  • 2,900
1

Inspired by Carl Walsh's answer, I took it one step further to allow parsing of properties:

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

Here's an example which also can be found in this 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;}
}

Output of this program:

1

2

3

Community
  • 1
  • 1
hbulens
  • 1,872
  • 3
  • 24
  • 45
1

There's no need to use tuples or anonymous classes if you have to use pipeline syntax (e.g. due to your project's style guidelines prohibiting query syntax).

You can do this:

var ints = strings.Select(s => int.TryParse(s, out int n) ? n : default(int?))
                  .Where(n => n.HasValue);

If you need an IEnumerable<int> instead of an IEnumerable<int?>, append .Select(n => n.Value)

Tom
  • 11
  • 1
0

If you're looking for a one-line Linq expression and fine with allocating a new object on every loop, I'd use the more powerful SelectMany to do this with one Linq call

var ints = strings.SelectMany(str => {
    int value;
    if (int.TryParse(str, out value))
        return new int[] { value };
    return new int[] { };
});
Carl Walsh
  • 6,100
  • 2
  • 46
  • 50
0

Here a (more less easy) true one-liner (using C#7 syntax, without temporary "pair" variable):

strings.Select(s => Int32.TryParse(s, out var i) ? (int?)i : null).Where(i => i != null)

If you need int as return type (instead of int?), check out 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);
}

Output

1200
0
Jack Miller
  • 6,843
  • 3
  • 48
  • 66
-1

I use this little extension method:

public static class EnumerableExtensions
{
    public static IEnumerable<TResult> SelectWhere<TSource, TResult>(this IEnumerable<TSource> source, Func<TSource, (bool, TResult)> whereSelector)
    {
        foreach (var item in source)
        {
            if (whereSelector(item) is (true, var result))
            {
                yield return result;
            }
        }
    }
}

Unlike this answer this is easy to pass in a lambda, rather than requiring a method with an out parameter.

Yair Halberstadt
  • 5,733
  • 28
  • 60
  • That doesn't answer the question. They want to get parsed values in one statement. Also, it's very awkward to pass this type of `whereSelector` and it isn't necessary. There already are other answers that exactly do what is asked here. – Gert Arnold Oct 26 '21 at 07:01