2011-02-10 14 views
41

Quindi ho uno IEnumerable<string> che può contenere valori che possono essere analizzati come int, nonché valori che non possono essere.Selezionare pars int, se stringa è stata analizzabile in int

Come noto, Int32.Parse genera un'eccezione se una stringa non può essere modificata in int, mentre Int32.TryParse può essere utilizzata per controllare e verificare se la conversione fosse possibile senza trattare l'eccezione.

Quindi voglio utilizzare una query LINQ per analizzare un solo liner quelle stringhe che possono essere analizzate come int, senza generare un'eccezione lungo la strada. Ho una soluzione, ma vorrei chiedere consiglio alla comunità se questo è l'approccio migliore.

Ecco quello che ho:

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

Quindi, come potete vedere, sto usando asInt come spazio scratch per la chiamata a TryParse, proprio per determinare se TryParse sarebbe successo (bool ritorno). Quindi, nella proiezione, sto effettivamente eseguendo l'analisi. È brutto.

È questo il modo migliore per filtrare i valori analizzabili in una riga utilizzando LINQ?

+2

È possibile utilizzare 'asInt' direttamente come il valore di selezione. –

+0

Giusto; assomiglia alla risposta di Joe. In realtà ho cambiato la mia risposta accettata dal momento che è più concisa di alcuni degli altri. –

risposta

68

E 'difficile farlo in sintassi di query, ma non è troppo male nella sintassi 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); 

In alternativa, si può trovare la pena di scrivere un metodo che restituisce un int?:

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

Poi si può semplicemente utilizzare:

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

Sembra quasi la stessa idea, come la sintassi della query. Bello vedere in questo modulo però. –

+2

@byte: la differenza è che la tua versione ha effetti collaterali nel toccare un'altra variabile ... quindi non puoi eseguirla in parallelo, per esempio. –

+3

Vorrei che il framework avesse un int? metodo int.TryParse (stringa) per questi casi –

3

avrei probabilmente questo piccolo metodo di utilità da qualche parte (io in realtà faccio nel mio attuale base di codice :-))

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

Poi si utilizza questo molto più pulito LINQ dichiarazione:

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

farei questo è LINQ-to-oggetti:

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

Poi nella query:

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

E 'ancora due codeline, ma è possibile ridurre il vostro originale un po':

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

Dal momento che l'TryParse corre già al momento della selezione, la variabile asInt è popolata, in modo da poter utilizzarlo come valore di ritorno - non è necessario analizzarlo di nuovo.

6

Se non ti dispiace vostri colleghi si saltare nel parcheggio c'è un modo per fare questo in una vera linea di LINQ (nessun punto e virgola) ....

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

Non è pratico, ma farlo in una dichiarazione è stata una sfida troppo interessante da abbandonare.

0

Se siete alla ricerca di una sola linea di espressione Linq e bene con l'assegnazione di un nuovo oggetto per ogni ciclo, mi piacerebbe utilizzare la più potente SelectMany a che fare questo con una chiamata LINQ

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

Se vuoi definire un metodo di estensione per farlo, creo una soluzione generale che sia semplice da usare, invece di richiedere di scrivere un nuovo wrapper null-on-failure per ogni funzione Try, e richiede di filtrare fuori valori nulli.

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

E 'un po' imbarazzante che C# non può dedurre SelectTry 's argomenti di tipo generico.

(TryFunc s' TResult non può essere covariante (cioè out TResult) come Func. Come Eric Lippert explains fuori parametri sono actually just ref parameters con le regole di scrittura-prima-di lettura di fantasia.)

+0

Mi piace creare qui un metodo di estensione – Riscie

1

Ispirato dalla risposta di Carl Walsh, io ha preso un ulteriore passo avanti per consentire l'analisi delle proprietà:

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

Ecco un esempio che può anche essere trovato in questo 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;} 
} 

uscita di questo programma:

1

2

3

0

Sono d'accordo che usando la variabile più sente brutta.

Sulla base Jon's answer e aggiornamento per C# 7.0 soluzioni si può utilizzare il nuovo var out feature: (non molto più breve, ma c'è bisogno di un ambito interno o fuori di query variabili temporanee)

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

e insieme con le tuple di nome:

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

o se suggerendo un metodo per esso per l'utilizzo nella sintassi di query:

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

mi piacerebbe suggerire anche una sintassi di query, senza un metodo in più per esso, ma come discusso nel seguente link out var non è supportato da C# 7.0 e provoca l'errore di compilazione:

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

Il link: Expression variables in query expressions


Attraverso questa è una caratteristica C# 7.0 è possibile farlo funzionare su Earl Ier.Versioni NET:

+0

Perché non renderlo più breve? Che dire di 'var numbers = values.Select (x => int.TryParse (x, out int value)? (Int?) Value: null);' –

+0

@MarcusDock - anche questo è buono. Il motivo per cui non sono andato a quello è che il risultato originale è 'IEnumerable ' e non 'IEnumerable ' e per fare ciò con il tuo suggerimento dovrei anche aggiungere 'Where (x => x! = Null) e per convertire in 'int' invece di' int? ' –

+0

@MarcusDock - vedi che nella domanda stessa c'è un filtraggio di tutti i risultati non numerici –