c# - toint32 - Seleccione int analizado, si la cadena fue parseable a int
string to long c# (9)
De modo que tengo una IEnumerable<string>
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 sintaxis de la consulta, pero no es tan malo en la sintaxis 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;
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 alcance 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 sintaxis 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 sintaxis 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 compilació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?
Inspirado por la respuesta de Carl Walsh, di un paso más para permitir el análisis de propiedades:
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;
}
}
Aquí hay un ejemplo que también se puede encontrar en este 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;}
}
Salida de este programa:
1
2
3
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 declaración LINQ mucho más limpia:
from str in strings
let number = SafeConvert.ToInt32(str)
where number != null
select number.Value;
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[] { };
});
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<string, Func<int,int?>>(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 declaración era un desafío demasiado interesante como para dejarlo pasar.
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<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;
}
}
Uso:
var ints = strings.SelectTry<string, int>(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 explains , los parámetros son en realidad solo parámetros de ref con reglas de escritura antes de leer).
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.
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;