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.


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.


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.


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


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:


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())
  {
    ...
  }

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
}

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.

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.