2011-02-10 20 views
41

Mam więc IEnumerable<string>, który może zawierać wartości, które można przeanalizować jako int, a także wartości, które nie mogą być.Wybierz sparsowany int, jeśli ciąg znaków można sparsować do int

Jak wiadomo, Int32.Parse zgłasza wyjątek, jeśli nie można zmienić ciągu znaków na int, a można użyć Int32.TryParse do sprawdzenia, czy konwersja była możliwa bez czynienia wyjątku.

Tak więc chcę użyć kwerendy LINQ do jednego linijka parsować te ciągi, które mogą być przetwarzane jako int, bez rzucania wyjątku po drodze. Mam rozwiązanie, ale chciałbym uzyskać poradę od społeczności, czy jest to najlepsze podejście.

Oto co mam:

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

Więc jak widać, używam asInt jako przestrzeń zarysowania na wezwanie do TryParse, żeby ustalić, czy TryParse uda (Bool powrotny). Następnie, w projekcji, faktycznie wykonuję analizę. To jest brzydkie.

Czy jest to najlepszy sposób filtrowania wartości parsowanych w jednym wierszu za pomocą LINQ?

+2

Można użyć 'asInt' bezpośrednio jako wybierz wartość. –

+0

Prawo; wygląda na to, że odpowiedź Joe chwyta to. Właściwie to zmieniłem ją na moją akceptowaną odpowiedź, ponieważ jest bardziej zwięzła niż niektóre inne. –

Odpowiedz

68

Jest to trudne do zrobienia, że ​​w składni zapytania, ale nie jest tak źle w składni 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); 

Alternatywnie, może się okazać, że warto napisać metodę, która zwraca int?:

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

Następnie można po prostu użyć:

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

Wydaje się jednak prawie taki sam pomysł, jak składnia zapytania. Dobrze widzieć w tej formie. –

+2

@byte: Różnica polega na tym, że twoja wersja ma skutki uboczne w dotykaniu innej zmiennej ... więc nie możesz na przykład uruchamiać jej równolegle. –

+3

Chciałbym, aby ramy miały int? Int.TryParse (ciąg) metoda dla takich przypadków –

3

Prawdopodobnie miałbym tę małą metodę użytkową gdzieś (I faktycznie w moim obecnym Codebase :-))

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

Następnie użyć tego znacznie czystszy oświadczenie LINQ:

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

bym to LINQ-obiektów:

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

Następnie w zapytaniu:

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

To jeszcze dwa codelines, ale można skrócić nawet oryginał trochę:

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

Ponieważ TryParse już biegnie w czasie select, zmienna asInt jest wypełniana, dzięki czemu można używać go jako wartość zwracana - nie musisz ponownie analizować.

6

Jeśli nie przeszkadza ci swoich współpracowników skoki na parkingu istnieje sposób, aby to zrobić w jednej prawdziwej linii LINQ (bez średnikami) ....

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

Nie praktyczne, ale zrobienie tego w jednym oświadczeniu było zdecydowanie zbyt ciekawym wyzwaniem, aby się przepuścić.

0

Jeśli szukasz LINQ wypowiedzi jednego wiersza i grzywny z przeznaczeniem nowy obiekt na każdej pętli, użyję mocniejszy SelectMany to zrobić z jednej Linq rozmowy

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

If chcesz zdefiniować metodę rozszerzenia, aby to zrobić, stworzyłem ogólne rozwiązanie, które jest proste w użyciu, zamiast wymagać napisania nowej funkcji pakowania o zerowym niepowodzeniu dla każdej funkcji Wypróbuj, i wymaga filtrowania out wartości puste.

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

Zastosowanie:

var ints = strings.SelectTry<string, int>(int.TryParse); 

Jest to trochę niewygodne, że C# nie można wywnioskować SelectTry „s rodzajowe argumenty typu.

(TryFunc „s TResult nie może być kowariantna (tj out TResult) jak Func. Jak Eric Lippert explains się parametry są actually just ref parameters z fantazyjnymi zapisu przed-przeczytaj regulamin).

+0

Lubię tworzyć metodę rozszerzenia tutaj – Riscie

1

Zainspirowany odpowiedzi Carla Walsha, ja wziął go o krok dalej, aby umożliwić analizowania właściwości:

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

Oto przykład, który również znajduje się w tym 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;} 
} 

Wyjście z tego programu:

1

2

3

0

Zgadzam się, że za pomocą dodatkowej zmiennej czuje brzydki.

podstawie Jon's answer i aktualizacji do C# 7.0 rozwiązań można wykorzystać nowy var out feature: (nie znacznie krótsze, ale nie potrzeba zakresie wewnętrznej lub z zmiennych temp zapytań)

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

i wraz z nazwanych krotek:

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

lub jeśli sugeruje sposobu jej do użytku w składni zapytania:

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

Chciałbym również zasugerować składni zapytań bez dodatkowego sposobu niego, ale jak to opisano w poniższym linku out var nie jest obsługiwany przez C# 7.0 i powoduje błąd kompilacji:

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

Link: Expression variables in query expressions


Przez to C# 7.0 cecha można zmusić go do pracy na hrabiego ier.NET wersje:

+0

Dlaczego nie skracać? A co z 'var numbers = values.Select (x => int.TryParse (x, out int value)? (Int?) Value: null);' –

+0

@MarcusDock - to też jest dobre. Powodem, dla którego tego nie zrobiłem, jest to, że oryginalny wynik to 'IEnumerable ' a nie "IEnumerable " i aby to zrobić z twoją sugestią, musiałbym dodać 'Where (x => x! = Null) i konwertować na 'int' zamiast' int? ' –

+0

@MarcusDock - zobacz, że samo pytanie dotyczy filtrowania wszystkich wyników innych niż liczbowe –