Select parsed int, if string was parseable to int
C#LinqIntC# Problem Overview
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?
C# Solutions
Solution 1 - C#
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;
Solution 2 - C#
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.
Solution 3 - C#
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.
Solution 4 - C#
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;
Solution 5 - C#
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.)
Solution 6 - C#
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;
Solution 7 - C#
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:
Solution 8 - C#
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
Solution 9 - C#
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[] { };
});
Solution 10 - C#
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
Solution 11 - C#
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.