Czy istnieje singleton „Pusta lista” w C #?

80

W C # dobrze używam LINQ i IEnumerable. I wszystko jest w porządku (a przynajmniej w większości).

Jednak w wielu przypadkach okazuje się, że IEnumerable<X>domyślnie potrzebuję pustego . To znaczy chciałbym

for (var x in xs) { ... }

do pracy bez konieczności sprawdzania zerowej wartości. Teraz robię to, w zależności od szerszego kontekstu:

var xs = f() ?? new X[0];              // when xs is assigned, sometimes
for (var x in xs ?? new X[0]) { ... }  // inline, sometimes

Teraz, podczas gdy powyższe jest dla mnie w porządku - to znaczy, jeśli jest jakiś „dodatkowy narzut” związany z tworzeniem obiektu tablicy, po prostu nie obchodzi mnie to - zastanawiałem się:

Czy istnieje singleton „pusty niezmienny IEnumerable / IList” w C # / .NET? (A nawet jeśli nie, czy istnieje „lepszy” sposób rozwiązania opisanego powyżej przypadku?)

Java ma Collections.EMPTY_LISTniezmienny singleton - „dobrze wpisany” via Collections.emptyList<T>()- który służy temu celowi, chociaż nie jestem pewien, czy podobna koncepcja mogłaby działać nawet w C #, ponieważ typy generyczne są obsługiwane inaczej.

Dzięki.


źródło
Cóż, cholera :) To jest to, co dostaję za skupienie się na List / IList, a nie Enumerable / IEnumerable, dzięki wszystkim - głosy dookoła.
2
public static class Array<T> { public static readonly T[] Empty = new T[0]; }i można go nazwać jak: Array<string>.Empty. Zapytałem o to tutaj w CodeReview .
Şafak Gür

Odpowiedzi:

98

Szukasz Enumerable.Empty<T>().

W innych wiadomościach pusta lista Java jest do bani, ponieważ interfejs List udostępnia metody dodawania elementów do listy, które generują wyjątki.

Stilgar
źródło
To bardzo ważny punkt! Może to uzasadnia nowe pytanie SO?
1
Byłeś kilka sekund wolno i nie było linku do dokumentacji * potrząśnij palcem * ;-) Ale zaakceptuj rozszerzenie po komentarzu svicks.
Również inni faceci dostali więcej głosów za, więc jesteśmy równi :) Zadałbym pytanie, ale idę teraz do łóżka i nie będę mógł odpowiedzieć na pytanie. Dlaczego nie opublikować tego samodzielnie?
Stilgar,
32
Ponieważ metoda klasy może wymagać zwrócenia List(kolekcji, do której można uzyskać dostęp za pomocą indeksu). Tych Listmoże być tylko do odczytu. I czasami trzeba zwrócić taki readonly Listz zerowym elementem. Tak więc Collections.emptyList()metoda. Nie możesz po prostu zwrócić pustej iterowalnej, ponieważ zaimplementowany interfejs określa, że ​​metoda zwraca listę. Duży problem polega na tym, że nie ma interfejsu ImmutableList, który można zwrócić. Ten sam problem występuje w .NET: każdy IListmoże być tylko do odczytu.
Laurent Bourgault-Roy
6
Na marginesie, każda tablica jest IList <correspondingType> w C #, a to zostanie zgłoszone podczas próby dodania nowych elementów.
Grzenio,
52

Enumerable.Empty<T>() jest dokładnie tym.

Jon
źródło
3
Cóż, nie do końca . :) Ponieważ pytano o „listę”, Array.Empty<T>()jest dokładniejsza, ponieważ jest to IList<T>. Ma tę szczęśliwą zaletę, że jest również satysfakcjonująca IReadOnlyCollection<T>. Oczywiście, gdy IEnumerable<T> robi wystarczyć Enumerable.Empty<T>()pasuje idealnie.
Timo,
3
@Timo to pytanie i wiele odpowiedzi, w tym ta, zostało napisanych około 3 lata przed Array.Empty<T>udostępnieniem. :) W każdym razie, niezależnie od tytułu, pytanie wyraźnie wskazuje, że jest IEnumerable<T>on dobry do celu.
Jon
19

Myślę, że szukasz Enumerable.Empty<T>() .

Pusta lista singletona nie ma większego sensu, ponieważ listy są często zmienne.

svick
źródło
14

W oryginalnym przykładzie używasz pustej tablicy, aby zapewnić pustą wyliczalną. Chociaż używanie Enumerable.Empty<T>()jest całkowicie słuszne, mogą istnieć inne przypadki: jeśli musisz użyć tablicy (lub IList<T>interfejsu), możesz użyć metody

System.Array.Empty<T>()

co pomaga uniknąć niepotrzebnych przydziałów.

Uwagi / odniesienia:

ventiseis
źródło
Dzięki za wskazanie, że dokumentacja jest niezgodna z analizatorami Roslyn. Czy kiedykolwiek otworzyłeś problem w GitHub, aby to naprawić?
Jan Zabroski
10

Myślę, że dodanie metody rozszerzenia jest czystą alternatywą dzięki ich zdolności do obsługi wartości null - coś takiego:

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

  foreach(var x in xs.EmptyIfNull())
  {
    ...
  }
Andrew Hanlon
źródło
1

Microsoft zaimplementował `` Any () '' w ten sposób ( źródło )

public static bool Any<TSource>(this IEnumerable<TSource> source)
{
    if (source == null) throw new ArgumentNullException("source");
    using (IEnumerator<TSource> e = source.GetEnumerator())
    {
        if (e.MoveNext()) return true;
    }
    return false;
}

Jeśli chcesz zapisać wywołanie na stosie wywołań, zamiast pisać wywoływaną metodę rozszerzającą !Any(), po prostu przepisz, wprowadź te trzy zmiany:

public static bool IsEmpty<TSource>(this IEnumerable<TSource> source) //first change (name)
{
    if (source == null) throw new ArgumentNullException("source");
    using (IEnumerator<TSource> e = source.GetEnumerator())
    {
        if (e.MoveNext()) return false; //second change
    }
    return true; //third change
}
user2023861
źródło
1

Używanie Enumerable.Empty<T>()z listami ma wadę. Jeśli przekażesz Enumerable.Empty<T>konstruktor listy, zostanie przydzielona tablica o rozmiarze 4. Ale jeśli podasz pusty Collectiondo konstruktora listy, nie nastąpi alokacja. Jeśli więc używasz tego rozwiązania w całym kodzie, najprawdopodobniej jeden z nich IEnumerablezostanie użyty do skonstruowania listy, co spowoduje niepotrzebne alokacje.

sjb-sjb
źródło