Sprawdź, czy w pętli foreach nie ma wartości NULL


97

Czy istnieje lepszy sposób na wykonanie następujących czynności:
Potrzebuję sprawdzenia, czy w pliku nie występuje null. Nagłówki przed kontynuowaniem pętli

if (file.Headers != null)
{
  foreach (var h in file.Headers)
  {
   //set lots of properties & some other stuff
  }
}

Krótko mówiąc, pisanie foreach wewnątrz if wygląda trochę brzydko ze względu na poziom wcięć występujących w moim kodzie.

To coś, co by się liczyło

foreach(var h in (file.Headers != null))
{
  //do stuff
}

możliwy?




1
@AdrianFaciu Myślę, że to zupełnie inaczej. Pytanie sprawdza, czy kolekcja jest null, przed wykonaniem for-each. Twój link sprawdza, czy pozycja w kolekcji jest pusta.
rikitikitik


1
C # 8 może po prostu mieć warunek zerowy typu foreach, tj. Składnię taką jak ta: foreach? (var i in collection) {} Myślę, że jest to wystarczająco powszechny scenariusz, aby to uzasadnić, a biorąc pod uwagę niedawne dodatki warunkowe zerowe w języku, dla którego ma to sens?
mms

Odpowiedzi:


125

Jako niewielki kosmetyczny dodatek do sugestii Rune, możesz stworzyć własną metodę rozszerzenia:

public static IEnumerable<T> OrEmptyIfNull<T>(this IEnumerable<T> source)
{
    return source ?? Enumerable.Empty<T>();
}

Następnie możesz napisać:

foreach (var header in file.Headers.OrEmptyIfNull())
{
}

Zmień nazwę według gustu :)


80

Zakładając, że typ elementów w file.Headers to T, możesz to zrobić

foreach(var header in file.Headers ?? Enumerable.Empty<T>()){
  //do stuff
}

spowoduje to utworzenie pustego wyliczalnego T, jeśli file.Headers ma wartość null. Jeśli typ pliku jest typem, którego jesteś właścicielem, rozważyłbym jednak zmianę metody pobierania Headers. nulljest wartością nieznane, więc jeśli to możliwe, zamiast używać null jako „Wiem, że nie ma elementów”, gdy null faktycznie (/ pierwotnie) powinno być interpretowane jako „Nie wiem, czy są jakieś elementy” użyj pustego zestawu, aby pokazać że wiesz, że w zestawie nie ma żadnych elementów. Byłoby to również bardziej SUCHE, ponieważ nie będziesz musiał tak często sprawdzać zerowej wartości.

EDYTUJ jako kontynuację sugestii Jonsa, możesz również utworzyć metodę rozszerzenia zmieniającą powyższy kod na

foreach(var header in file.Headers.OrEmptyIfNull()){
  //do stuff
}

W przypadku, gdy nie możesz zmienić metody pobierającej, byłby to mój własny preferowany sposób, ponieważ wyraźniej wyraża zamiar, nadając operacji nazwę (OrEmptyIfNull)

Wspomniana powyżej metoda rozszerzenia może uniemożliwić wykrywanie niektórych optymalizacji przez optymalizator. W szczególności można wyeliminować te, które są związane z IList przy użyciu przeciążania metod

public static IList<T> OrEmptyIfNull<T>(this IList<T> source)
{
    return source ?? Array.Empty<T>();
}

Odpowiedź @ kjbartel (pod adresem "stackoverflow.com/a/32134295/401246" jest najlepszym rozwiązaniem, ponieważ nie obejmuje: a) degradacji wydajności (nawet jeśli nie null) degeneracji całej pętli do LCD IEnumerable<T>(jak przy użyciu ?? by), b) wymagały dodania metody rozszerzenia do każdego projektu lub c) wymagały unikania null IEnumerables(Pffft! Puh-LEAZE! SMH.) na początku (bo nulloznacza N / A, podczas gdy pusta lista oznacza, że ​​ma zastosowanie, ale obecnie jest, no cóż, puste !, tj. pracownik może mieć prowizje, które nie dotyczą elementów niesprzedażnych lub są puste w przypadku sprzedaży, jeśli nie zarobił).
Tom

@Tom oprócz jednego sprawdzenia zerowego nie ma żadnych kar za przypadki, w których moduł wyliczający nie jest zerowy. Uniknięcie tego sprawdzenia przy jednoczesnym zapewnieniu, że wyliczalny nie jest zerowy, jest niemożliwe. Powyższy kod wymaga, aby nagłówki były IEnumerable bardziej restrykcyjne niż foreachwymagania, ale mniej restrykcyjne niż wymaganie List<T>w odpowiedzi, do której prowadzi łącze. Które mają taki sam spadek wydajności, jak testowanie, czy wyliczalny jest pusty.
Rune FS

Oparłem kwestię "LCD" na komentarzu Erica Lipperta na temat odpowiedzi Vlada Bezdena w tym samym wątku co odpowiedź kjbartela: "@CodeInChaos: Ach, teraz rozumiem twój punkt widzenia. Kiedy kompilator wykryje, że" foreach "jest iterowany przez List <T> lub tablicę, a następnie może zoptymalizować foreach, aby użyć wyliczaczy typu wartości lub faktycznie wygenerować pętlę „for”. Gdy zostanie zmuszony do wyliczenia listy lub pustej sekwencji, musi wrócić do „ najniższy wspólny mianownik „codegen, który w niektórych przypadkach może być wolniejszy i powodować większe obciążenie pamięci…”. Zgadzam się, to wymaga List<T>.
Tom

@tom Założeniem odpowiedzi jest to, że file.Headers to IEnumerable <T>, w którym to przypadku kompilator nie może przeprowadzić optymalizacji. Jednak rozszerzenie metody rozszerzania, aby tego uniknąć, jest raczej proste. Zobacz edycję
Rune FS

19

Szczerze mówiąc, radzę: po prostu zassać nulltest. nullTest jest tylko A brfalselub brfalse.s; wszystko inne będzie obejmować znacznie więcej pracy (testy, zadania, dodatkowe wywołania metod, niepotrzebne GetEnumerator(), MoveNext(), Dispose()na iteracyjnej, etc).

ifBadanie jest proste, oczywiste i skuteczny.


1
Robisz interesującą uwagę Marc, jednak obecnie staram się zmniejszyć poziomy wcięć w kodzie. Ale będę mieć na uwadze twój komentarz, gdy będę musiał zanotować wydajność.
Eminem,

3
Krótka notatka na temat tego Marca… lata po tym, jak zadałem to pytanie i potrzebowałem wprowadzić pewne ulepszenia wydajności, Twoja rada była niezwykle przydatna. Dziękuję
Eminem

13

„jeśli” przed iteracją jest w porządku, niewiele z tych „ładnych” semantyki może sprawić, że kod będzie mniej czytelny.

tak czy inaczej, jeśli wcięcie ci przeszkadza, możesz zmienić if, aby sprawdzić:

if(file.Headers == null)  
   return;

a do pętli foreach dojdziesz tylko wtedy, gdy we właściwości headers znajduje się wartość true.

Inną opcją, o której mogę pomyśleć, jest użycie operatora koalescencji zerowej wewnątrz pętli foreach i całkowite uniknięcie sprawdzania wartości null. próba:

List<int> collection = new List<int>();
collection = null;
foreach (var i in collection ?? Enumerable.Empty<int>())
{
    //your code here
}

(zastąp kolekcję swoim prawdziwym obiektem / typem)


Twoja pierwsza opcja nie zadziała, jeśli poza instrukcją if jest jakiś kod.
rikitikitik

Zgadzam się, instrukcja if jest łatwa i tania w implementacji w porównaniu do tworzenia nowej listy, tylko w celu upiększenia kodu.
Andreas Johansson

11

Korzystanie z operatora warunkowego o wartości null i ForEach (), które działają szybciej niż standardowa pętla foreach.
Musisz jednak przesłać kolekcję do listy.

   listOfItems?.ForEach(item => // ... );

4
Proszę dodać jakieś wyjaśnienie wokół swojej odpowiedzi, stwierdzając wyraźnie, dlaczego to rozwiązanie działa, a nie tylko jeden kod-liner
LordWilmore

najlepsze rozwiązanie w moim przypadku
Josef Henn

3

Używam ładnej, małej metody rozszerzenia dla tych scenariuszy:

  public static class Extensions
  {
    public static IList<T> EnsureNotNull<T>(this IList<T> list)
    {
      return list ?? new List<T>();
    }
  }

Biorąc pod uwagę, że nagłówki są listą typów, możesz wykonać następujące czynności:

foreach(var h in (file.Headers.EnsureNotNull()))
{
  //do stuff
}

1
możesz użyć ??operatora i skrócić instrukcję powrotu doreturn list ?? new List<T>;
Rune FS

1
@wolfgangziegler, Jeśli dobrze rozumiem, test dla nulltwojej próbki file.Headers.EnsureNotNull() != nullnie jest potrzebny, a nawet jest zły?
Remko Jansen

0

W niektórych przypadkach wolałbym nieco inny, ogólny wariant, zakładając, że z reguły domyślne konstruktory kolekcji zwracają puste wystąpienia.

Lepiej byłoby nazwać tę metodę NewIfDefault. Może być przydatny nie tylko w przypadku kolekcji, więc ograniczenie typu IEnumerable<T>może być zbędne.

public static TCollection EmptyIfDefault<TCollection, T>(this TCollection collection)
        where TCollection: class, IEnumerable<T>, new()
    {
        return collection ?? new TCollection();
    }
Korzystając z naszej strony potwierdzasz, że przeczytałeś(-aś) i rozumiesz nasze zasady używania plików cookie i zasady ochrony prywatności.
Licensed under cc by-sa 3.0 with attribution required.