Seleccione int analizado, si la cadena fue parseable a int

De modo que tengo una IEnumerable que puede contener valores que pueden analizarse como int , así como valores que no pueden ser.

Como ya sabe, Int32.Parse lanza una excepción si una cadena no se puede cambiar a una int, mientras que Int32.TryParse se puede usar para verificar y ver si la conversión fue posible sin tener en cuenta la excepción.

Por lo tanto, quiero utilizar una consulta LINQ para analizar un solo linaje las cadenas que se pueden analizar como int, sin lanzar una excepción en el camino. Tengo una solución, pero me gustaría recibir consejos de la comunidad sobre si este es el mejor enfoque.

Esto es lo que tengo:

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

Como puede ver, estoy usando asInt como un espacio cero para llamar a TryParse , solo para determinar si TryParse tendría éxito (return bool). Luego, en la proyección, en realidad estoy realizando el análisis. Eso se siente feo

¿Es esta la mejor manera de filtrar los valores parseables en una línea usando LINQ?

Es difícil hacer eso en la syntax de la consulta, pero no es tan malo en la syntax 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); 

Alternativamente, puede encontrar que vale la pena escribir un método que devuelve un int? :

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

Entonces solo puedes usar:

 var ints = from str in strings let nullable = NullableTryParseInt32(str) where nullable != null select nullable.Value; 

Todavía hay dos líneas de código, pero puedes acortar un poco tu original:

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

Como el TryParse ya se ejecuta en el momento de la selección, la variable asInt se rellena, por lo que puede usarla como su valor de retorno; no es necesario volver a analizarla.

Si no te importa que tus compañeros de trabajo te salten en el estacionamiento, hay una forma de hacerlo en una línea verdadera de linq (sin punto y coma) …

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

No es práctico, pero hacer esto en una statement era un desafío demasiado interesante como para dejarlo pasar.

Probablemente tendré este pequeño método de utilidad en alguna parte (de hecho lo hago en mi base de código actual :-))

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

Luego usas esta statement LINQ mucho más limpia:

 from str in strings let number = SafeConvert.ToInt32(str) where number != null select number.Value; 

Yo diría que esto es LINQ-a-objetos:

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

Luego en la consulta:

 let i = ParseInt32(str) where i != null select i.Value; 

Si quiere definir un método de extensión para hacer esto, crearía una solución general que es simple de usar, en lugar de requerir que escriba un nuevo contenedor de null-on-failure para cada función Try, y requiere que filtre valores nulos.

 public delegate bool TryFunc(TSource arg, out TResult result); public static IEnumerable SelectTry(this IEnumerable source, TryFunc selector) { foreach(var s in source) { TResult r; if (selector(s, out r)) yield return r; } } 

Uso:

 var ints = strings.SelectTry(int.TryParse); 

Es un poco incómodo que C # no pueda inferir los argumentos de tipo genérico de SelectTry .

( TryFunc ‘s TResult no puede ser covariante (es decir, out TResult ) como Func . Como Eric Lippert explica , los parámetros son en realidad solo parámetros de ref con reglas de escritura antes de leer).

Inspirado por la respuesta de Carl Walsh, di un paso más para permitir el análisis de propiedades:

 public static IEnumerable SelectTry( this IEnumerable source, Func selector, TryFunc executor) { foreach (TSource s in source) { TResult r; if (executor(selector(s), out r)) yield return r; } } 

Aquí hay un ejemplo que también se puede encontrar en este violín :

 public class Program { public static void Main() { IEnumerable myClassItems = new List() {new MyClass("1"), new MyClass("2"), new MyClass("InvalidValue"), new MyClass("3")}; foreach (int integer in myClassItems.SelectTry(x => x.MyIntegerAsString, int.TryParse)) { Console.WriteLine(integer); } } } public static class LinqUtilities { public delegate bool TryFunc(TSource arg, out TResult result); public static IEnumerable SelectTry( this IEnumerable source, Func selector, TryFunc 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;} } 

Salida de este progtwig:

1

2

3

Si está buscando una expresión Linq de una línea y está bien con la asignación de un nuevo objeto en cada ciclo, usaría el SelectMany más potente para hacer esto con una sola llamada Linq

 var ints = strings.SelectMany(str => { int value; if (int.TryParse(str, out value)) return new int[] { value }; return new int[] { }; }); 

Estoy de acuerdo en que usar la variable adicional me parece feo .

En función de la respuesta de Jon y la actualización a las soluciones de C # 7.0, se puede utilizar la nueva función var out : ( no mucho más corta, pero no es necesario un scope interno ni variables de consulta de temperatura )

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

y junto con tuplas nombradas:

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

O si sugiere un método para el uso en syntax de consulta:

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

También me gustaría sugerir una syntax de consulta sin un método adicional, pero como se explica en el siguiente enlace out var no es compatible con c # 7.0 y da como resultado el error de comstackción:

Las declaraciones de variable variable y de variable de patrón no están permitidas dentro de una cláusula de consulta

El enlace: variables de expresión en expresiones de consulta


A través de esto es una característica de C # 7.0 uno puede hacer que funcione en versiones anteriores de .NET:

  • Requisitos de versión de C # 7 .NET / CLR / Visual Studio
  • ¿Funciona C # 7.0 para .NET 4.5?