MyClass[] array;
List<MyClass> list;
Jakie są scenariusze, w których jeden jest lepszy od drugiego? I dlaczego?
MyClass[] array;
List<MyClass> list;
Jakie są scenariusze, w których jeden jest lepszy od drugiego? I dlaczego?
Odpowiedzi:
W rzeczywistości rzadko jest potrzebne użycie tablicy. Zdecydowanie użyć List<T>
w dowolnym momencie dodawać / dane usunąć, ponieważ zmiana rozmiaru tablic jest drogie. Jeśli wiesz, że dane mają ustaloną długość i chcesz przeprowadzić mikrooptymalizację z jakiegoś bardzo konkretnego powodu (po analizie porównawczej), tablica może być przydatna.
List<T>
oferuje znacznie więcej funkcji niż tablica (chociaż LINQ nieco ją wyrówna) i prawie zawsze jest właściwym wyborem. params
Oczywiście z wyjątkiem argumentów. ;-p
Jako licznik - List<T>
jest jednowymiarowy; gdzie-jak masz prostokątne (itp.) tablice, takie jak int[,]
lub string[,,]
- ale istnieją inne sposoby modelowania takich danych (jeśli potrzebujesz) w modelu obiektowym.
Zobacz też:
To powiedziawszy, często używam tablic w mojej sieci protobuf projekcie ; całkowicie za realizację:
byte[]
jest prawie niezbędny do kodowania;byte[]
bufora który wypełniam przed przesłaniem do strumienia bazowego (i vv); szybciej niż BufferedStream
itp .;Foo[]
zamiastList<Foo>
), ponieważ rozmiar jest ustalany po zbudowaniu i musi być bardzo szybki.Ale to zdecydowanie wyjątek; w przypadku ogólnego przetwarzania biznesowego List<T>
wygrywa za każdym razem.
Naprawdę sama odpowiedź, aby dodać link, który mnie zaskakuje, nie została jeszcze wspomniana: wpis Erica Lipperta na blogu „Tablice uważane za nieco szkodliwe”.
Możesz sądzić po tytule, że sugeruje stosowanie kolekcji wszędzie tam, gdzie jest to praktyczne - ale jak słusznie zauważa Marc, istnieje wiele miejsc, w których tablica jest naprawdę jedynym praktycznym rozwiązaniem.
Niezależnie od innych zalecających odpowiedzi List<T>
, będziesz chciał używać tablic podczas obsługi:
List<T>
tablicy tutaj zamiast bajtów?
Chyba że naprawdę interesujesz się wydajnością, a przez to mam na myśli: „Dlaczego używasz .Net zamiast C ++?” powinieneś trzymać się Listy <>. Łatwiej jest go utrzymać i wykonuje całą brudną robotę polegającą na zmianie rozmiaru tablicy za kulisami. (W razie potrzeby Lista <> jest całkiem sprytna w wyborze rozmiarów tablic, więc zwykle nie musi).
Tablice powinny być stosowane zamiast List, gdy niezmienność samej kolekcji jest częścią umowy między kodem klienta i dostawcy (niekoniecznie niezmienność elementów w kolekcji) ORAZ, gdy IEnumerable nie jest odpowiedni.
Na przykład,
var str = "This is a string";
var strChars = str.ToCharArray(); // returns array
Oczywiste jest, że modyfikacja „strChars” nie spowoduje mutacji oryginalnego obiektu „str”, niezależnie od poziomu wiedzy na temat implementacji typu „str”.
Ale załóżmy, że
var str = "This is a string";
var strChars = str.ToCharList(); // returns List<char>
strChars.Insert(0, 'X');
W takim przypadku z samego fragmentu kodu nie jest jasne, czy metoda wstawiania zmutuje oryginalny obiekt „str”. Wymagana jest znajomość ciągu znaków na poziomie implementacji, aby dokonać tego ustalenia, co przełamuje podejście Projekt na podstawie umowy. W przypadku String nie jest to wielka sprawa, ale może to być ważna sprawa w prawie każdym innym przypadku. Ustawienie listy jako tylko do odczytu pomaga, ale powoduje błędy w czasie wykonywania, a nie w czasie kompilacji.
To
ma na celu utworzenie obiektu, który nie ma możliwości modyfikacji oryginalnej instancji, w przeciwieństwie do strChars as char[]
tego, jeśli poprawna sugeruje, że możesz teraz zmodyfikować oryginalny obiekt.
str
wewnętrznie korzysta z tablicy i ToCharArray
zwraca odwołanie do tej tablicy, klient może mutować str
, zmieniając elementy tej tablicy, nawet jeśli rozmiar pozostaje stały. Jednak piszesz „Oczywiste jest, że modyfikacja„ strChars ”nie spowoduje mutacji oryginalnego obiektu„ str ”. Czego tu brakuje? Z tego co widzę, w obu przypadkach klient może mieć dostęp do wewnętrznej reprezentacji i, niezależnie od rodzaju, pozwoliłoby to na jakąś mutację.
W większości List
przypadków wystarczyłoby użyć . A List
wykorzystuje wewnętrzną tablicę do obsługi swoich danych i automatycznie zmienia rozmiar tablicy podczas dodawania większej liczby elementów List
niż jej obecna pojemność, co sprawia, że jest łatwiejsza w użyciu niż tablica, w której trzeba wcześniej poznać pojemność.
Zobacz http://msdn.microsoft.com/en-us/library/ms379570(v=vs.80).aspx#datastructures20_1_topic5, aby uzyskać więcej informacji na temat list w języku C # lub po prostu dekompilować System.Collections.Generic.List<T>
.
Jeśli potrzebujesz danych wielowymiarowych (na przykład za pomocą matrycy lub w programowaniu graficznym), prawdopodobnie skorzystasz z nich array
.
Jak zawsze, jeśli problemem jest pamięć lub wydajność, zmierz to! W przeciwnym razie możesz przyjmować fałszywe założenia dotyczące kodu.
Tablice vs. Listy to klasyczny problem związany z utrzymywaniem a wydajnością. Zasadą, którą przestrzegają prawie wszyscy programiści, jest to, że powinieneś strzelać dla obu, ale gdy wejdą w konflikt, wybierz łatwość konserwacji zamiast wydajności. Wyjątkiem od tej reguły jest sytuacja, w której wydajność już okazała się problemem. Jeśli zastosujesz tę zasadę do Arrays vs. Listy, a następnie otrzymujesz to:
Używaj mocno wpisywanych list, dopóki nie napotkasz problemów z wydajnością. Jeśli napotkasz problem z wydajnością, podejmij decyzję, czy rezygnacja z macierzy przyniesie korzyści Twojemu rozwiązaniu z wydajnością bardziej niż będzie to niekorzystne dla rozwiązania pod względem konserwacji.
Inną sytuacją, o której jeszcze nie wspomniano, jest sytuacja, w której będziemy mieli dużą liczbę elementów, z których każdy składa się ze stałej wiązki powiązanych, ale niezależnych zmiennych, sklejonych razem (np. Współrzędne punktu lub wierzchołki trójkąta 3d). Szereg struktur pola odsłoniętego pozwoli skutecznie zmodyfikować jego elementy „na miejscu” - co jest niemożliwe w przypadku jakiegokolwiek innego typu kolekcji. Ponieważ tablica struktur utrzymuje swoje elementy kolejno w pamięci RAM, sekwencyjny dostęp do elementów tablicy może być bardzo szybki. W sytuacjach, gdy kod będzie musiał wykonać wiele kolejnych przejść przez tablicę, tablica struktur może przewyższać tablicę lub inny zbiór odwołań do obiektów klasy o współczynnik 2: 1; dalej,
Chociaż tablic nie można zmieniać rozmiaru, nie jest trudne, aby kod przechowywał odwołanie do tablicy wraz z liczbą używanych elementów i zamieniał tablicę na większą, w razie potrzeby. Alternatywnie, można łatwo napisać kod dla typu, który zachowuje się bardzo podobnie, List<T>
ale ujawnia swój magazyn zaplecza, umożliwiając w ten sposób powiedzenie albo, MyPoints.Add(nextPoint);
albo MyPoints.Items[23].X += 5;
. Zauważ, że ten ostatni niekoniecznie rzuciłby wyjątek, gdyby kod próbował uzyskać dostęp poza końcem listy, ale użycie byłoby inaczej koncepcyjnie podobne do List<T>
.
Point[] arr;
. Kod może powiedzieć np arr[3].x+=q;
. Używając np List<Point> list
. Należałoby zamiast tego powiedzieć Point temp=list[3]; temp.x+=q; list[3]=temp;
. Byłoby pomocne, gdyby List<T>
miał metodę Update<TP>(int index, ActionByRefRef<T,TP> proc, ref TP params)
. kompilatory i może okazać list[3].x+=q;
się {list.Update(3, (ref int value, ref int param)=>value+=param, ref q);
, ale nie ma takiego funkcja istnieje.
list[0].X += 3;
doda 3 do właściwości X pierwszego elementu listy. I list
to List<Point>
i Point
to klasa z X i Y właściwości
Listy w .NET są opakowaniami w tablice i używają tablicy wewnętrznie. Złożoność czasowa operacji na listach jest taka sama, jak w przypadku tablic, jednak jest nieco więcej narzutu przy całej dodatkowej funkcjonalności / łatwości korzystania z list (takich jak automatyczne zmienianie rozmiaru i metody, które są dostarczane z klasą listy). W zasadzie polecam używanie list we wszystkich przypadkach, chyba że istnieje ważny powód, aby tego nie robić, na przykład jeśli musisz napisać bardzo zoptymalizowany kod lub pracujesz z innym kodem zbudowanym wokół tablic.
Myślę, że zamiast pragnienia porównania funkcji każdego typu danych, najbardziej pragmatyczną odpowiedzią jest to, że „różnice prawdopodobnie nie są tak ważne z punktu widzenia tego, co należy osiągnąć, zwłaszcza że oba programy implementują IEnumerable
, więc przestrzegaj popularnej konwencji i używaj List
dopóki nie masz powodu, aby tego nie robić, w którym to momencie prawdopodobnie będziesz miał powód do używania tablicy nad List
”.
Przez większość czasu w zarządzanym kodzie będziesz chciał preferować kolekcje, które są tak łatwe w obsłudze, jak to możliwe, zamiast martwić się mikrooptymalizacjami.
Mogą być niepopularne, ale jestem fanem tablic w projektach gier. - Szybkość iteracji może być ważna w niektórych przypadkach, foreach na tablicy ma znacznie mniejszy narzut, jeśli nie robisz wiele na element - Dodawanie i usuwanie nie jest trudne z funkcjami pomocniczymi - Jest wolniejsze, ale w przypadkach, gdy budujesz go tylko raz to może nie mieć znaczenia - W większości przypadków marnuje się mniej dodatkowej pamięci (ma to znaczenie tylko w przypadku tablic struktur) - Nieco mniej śmieci i wskaźników i pogoni za wskaźnikami
To powiedziawszy, w praktyce korzystam z Listy znacznie częściej niż z Tablic, ale każde z nich ma swoje miejsce.
Byłoby miło, gdyby List zawierał wbudowany typ, aby mogli zoptymalizować koszty opakowania i wyliczenia.
Wypełnianie listy jest łatwiejsze niż tablica. W przypadku tablic musisz znać dokładną długość danych, ale w przypadku list wielkość danych może być dowolna. I możesz przekonwertować listę na tablicę.
List<URLDTO> urls = new List<URLDTO>();
urls.Add(new URLDTO() {
key = "wiki",
url = "https://...",
});
urls.Add(new URLDTO()
{
key = "url",
url = "http://...",
});
urls.Add(new URLDTO()
{
key = "dir",
url = "https://...",
});
// convert a list into an array: URLDTO[]
return urls.ToArray();
Ponieważ nikt nie wspomina: w języku C # tablica jest listą. MyClass[]
i List<MyClass>
oba wdrażają IList<MyClass>
. (np. void Foo(IList<int> foo)
można nazwać jak Foo(new[] { 1, 2, 3 })
lub Foo(new List<int> { 1, 2, 3 })
)
Tak więc, jeśli piszesz metodę, która akceptuje List<MyClass>
argument jako argument, ale używa tylko podzbioru funkcji, możesz zadeklarować jako IList<MyClass>
dla wygody osoby dzwoniącej.
Detale:
List
, tylko implementuje IList
interfejs.
Zależy to całkowicie od kontekstów, w których potrzebna jest struktura danych. Na przykład, jeśli tworzysz elementy, które mają być używane przez inne funkcje lub usługi za pomocą Listy, to idealny sposób na osiągnięcie tego.
Teraz, jeśli masz listę przedmiotów i chcesz je tylko wyświetlić, powiedzmy, że na tablicy strony internetowej jest pojemnik, którego musisz użyć.
IEnumerable<T>
- wtedy mogę przesyłać strumieniowo obiekty niż je buforować.
Warto wspomnieć o zdolności do rzucania na miejscu.
interface IWork { }
class Foo : IWork { }
void Test( )
{
List<Foo> bb = new List<Foo>( );
// Error: CS0029 Cannot implicitly convert type 'System.Collections.Generic.List<Foo>' to 'System.Collections.Generic.List<IWork>'
List<IWork> cc = bb;
Foo[] bbb = new Foo[4];
// Fine
IWork[] ccc = bbb;
}
Tak więc tablica oferuje nieco większą elastyczność, gdy jest używana jako typ zwracany lub argument funkcji.
IWork[] GetAllWorks( )
{
List<Foo> fooWorks = new List<Foo>( );
return fooWorks.ToArray( ); // Fine
}
void ExecuteWorks( IWork[] works ) { } // Also accept Foo[]