2011-02-10 14 views
41

Tengo un IEnumerable<string> que puede contener valores que se pueden analizar como int, así como valores que no pueden ser.Seleccionar int analizado, si la cadena fue parseable a int

Como usted sabe, Int32.Parse arroja una excepción si una cadena no se puede cambiar a un int, mientras que Int32.TryParse se puede utilizar para verificar y ver si la conversión era posible sin tener en cuenta la excepción.

Así que quiero utilizar una consulta LINQ para analizar las cadenas que pueden analizarse 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); 

Así como se puede ver, estoy usando asInt como un espacio reservado para la llamada a TryParse, sólo para determinar si tendrían éxito TryParse (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 utilizando LINQ?

+2

puede usar 'asInt' directamente como el valor de selección. –

+0

Derecha; parece que la respuesta de Joe capta eso. De hecho, lo cambié a mi respuesta aceptada, ya que es más conciso que algunos de los otros. –

Respuesta

68

Es difícil hacer eso en la sintaxis de 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 que le 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; 
} 

A continuación, puede simplemente usar:

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

Parece más o menos la misma idea, como la sintaxis de la consulta. Aunque es bueno verlo de esta forma. –

+2

@byte: La diferencia es que su versión tiene efectos secundarios al tocar otra variable ... por lo que no puede ejecutarla en paralelo, por ejemplo. –

+3

Ojalá el marco tuviera un int? int.TryParse (cadena) método para tales casos –

3

probablemente tendría utilidad de este método poco en alguna parte (De hecho, me gusta hacer en tu :-) 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; 
    } 
} 

continuación, se utiliza esta declaración mucho más limpio LINQ:

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

me había 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; 
12

Sigue siendo dos codelines, pero se puede acortar hasta el original un poco:

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

Desde el TryParse ya funciona en el momento de la selección, se rellena la variable asInt, por lo que puede usar eso como su valor de retorno - No es necesario analizarlo de nuevo.

6

Si no le importa que sus compañeros de trabajo saltando en el estacionamiento hay una manera de hacer esto en una verdadera línea de LINQ (no hay 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.

0

Si usted está buscando una expresión LINQ una línea fina y con la asignación de un nuevo objeto en cada bucle, que haría uso de la más potente SelectMany hacer esto con una llamada LINQ

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

Si Si desea 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 fuera de 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 se puede inferir SelectTry 's argumentos de tipo genérico.

(TryFunc 's TResult no puede ser covariante (es decir out TResult) como Func. Como Eric Lippert explains cabo parámetros son actually just ref parameters con las reglas de escritura antes leídos de lujo.)

+0

Me gusta crear un método de extensión aquí – Riscie

1

Inspirado por la respuesta de Carl Walsh, me tomó un paso más para permitir el análisis de las 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; 
    } 
} 

He aquí 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

0

estoy de acuerdo que el uso de la variable adicional siente fea.

Basado en Jon's answer y actualización a C# 7.0 soluciones uno puede utilizar el nuevo var out feature: (no mucho más corto, pero hay necesidad de un alcance interior o fuera de las variables temporales de consulta)

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 con nombre:

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 él para el uso en la sintaxis de la consulta:

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

me gustaría sugerir también una sintaxis de consulta sin un método adicional para él, pero como se explica en el siguiente enlace out var no está soportado por C# 7.0 y los resultados en el error de compilación:

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

El enlace: Expression variables in query expressions


a través de esta es una característica de C# 7.0 se puede conseguir que funcione el conde ier.versiones neto:

+0

¿Por qué no lo acorto? ¿Qué pasa con 'var numbers = values.Select (x => int.TryParse (x, out int value)?? (Int?) Value: null);' –

+0

@MarcusDock - eso también está bien. La razón por la que no fui para eso es que el resultado original es 'IEnumerable ' y no 'IEnumerable ' y para hacer eso con su sugerencia también necesitaría agregar 'Where (x => x! = Null) y para convertir a 'int' en lugar de' int'' –

+0

@MarcusDock - ver que en cuestión sí hay un filtrado de todos los resultados no numéricos –