2008-11-08 12 views
54

Właśnie zauważyłem, że wielowymiarowa tablica w C#, nie implementuje IEnumerable<T>, a implementuje IEnumerable. W przypadku tablic jednowymiarowych zaimplementowane są oba IEnumerable<T> i IEnumerable.Dlaczego wielowarstwowe tablice C# nie implementują IEnumerable <T>?

Dlaczego ta różnica? Jeśli tablica wielowymiarowa to IEnumerable, to z pewnością powinna również implementować ogólną wersję? Zauważyłem to, ponieważ próbowałem użyć metody Extension w wielowymiarowej tablicy, która kończy się niepowodzeniem, chyba że używasz Cast<T> lub podobnego; więc na pewno widzę argument, że wielowymiarowe macierze implementują IEnumerable<T>.

Aby wyjaśnić moje pytanie w kodzie, spodziewałbym poniższy kod, aby wydrukować true cztery razy, podczas gdy faktycznie drukuje true, false, true, true:

int[] singleDimensionArray = new int[10]; 
int[,] multiDimensional = new int[10, 10]; 

Debug.WriteLine(singleDimensionArray is IEnumerable<int>); 
Debug.WriteLine(multiDimensional is IEnumerable<int>); 
Debug.WriteLine(singleDimensionArray is IEnumerable); 
Debug.WriteLine(multiDimensional is IEnumerable); 
+3

także brzydkie: 'multiDimensional' jest niejawnie zamienny do ** ** non-rodzajowego typu' System.Collections.IList' (po prostu dlatego, że 'System.Array' implementuje ten interfejs). Możesz więc powiedzieć: 'System.Collections.IList mdCast = multiDimensional;'. Następnie użycie indeksatora jednoparametrowego na 'mdCast' zakończy się niepowodzeniem tylko w środowisku wykonawczym. Zobacz [dokument w witrynie MSDN] (http://msdn.microsoft.com/en-us/library/bb494734.aspx). Zwróć uwagę na typ wyjątku, 'ArgumentException'. Naprawdę brzydko. –

Odpowiedz

39

CLR ma dwa rodzaje tablic: wektorów które są zagwarantowane jednowymiarowy z dolną granicę 0, a bardziej ogólne macierzy, które mogą posiadać niezerowe granic i stopień inne niż 0.

z sekcji 8.9.1 spec CLI:

Dodatkowo utworzony wektora elementu typu T, realizuje interfejs System.Collections.Generic.IList<U> (§8.7), w którym U: = T.

Muszę przyznać, że wydaje mi się to dość dziwne.Biorąc pod uwagę, że implementuje już IEnumerable, nie widzę powodu, dla którego nie powinien on implementować IEnumerable<T>. Wdrażanie tego nie byłoby zbyt sensowne, ale prosty ogólny interfejs byłby w porządku.

Jeśli chcesz, możesz zadzwonić pod numer Cast<T> (jeśli korzystasz z .NET 3.5) lub napisać własną metodę do iteracji w tablicy. Aby uniknąć rzucania, musielibyście napisać własną metodę, która odnalazłaby dolne/górne granice każdego wymiaru i sprowadziła to w ten sposób. Niezbyt przyjemnie.

+0

Ponadto, specyfikacja języka C# (wersja 4.0) wspomina w paragrafie 6.1.6 ** tylko ** konwersję jednowymiarowej tablicy na 'IList <> i jej podstawowe interfejsy. Ale to wstyd, że 'T [,]' nie jest 'ICollection '. –

5

Tablice wielowymiarowe są nie tablice dla cel hierarchii dziedziczenia. Są zupełnie odrębnym rodzajem. Ponadto ten typ nie ma dobrego wsparcia z frameworka z dwóch możliwych powodów:

  • To naprawdę nie jest użyteczne. Jedynym rzeczywistym zastosowaniem tablic wielowymiarowych są macierze. W przypadku prawie wszystkich innych struktur danych lepiej nadają się inne struktury danych (na przykład poszarpane tablice).
  • Nie jest trywialne wymyślanie ogólnych, przydatnych metod dla tych struktur.

W jaki sposób powinno to zostać wdrożone, tj. W jakiej kolejności należy wymienić elementy? Nie ma porządku nieodłącznego w wielowymiarowych układach.

+1

Ale tablice wielowymiarowe dziedziczą klasę Array –

+12

Ale IEnumerable * było * zaimplementowane, więc albo I Counter powinien móc używać tej samej logiki, albo IEnumerable nie powinno być zaimplementowane. – recursive

+9

Jest to wyraźnie określone w specyfikacji języka C#. W wersji 4.0 w punkcie 8.8.4 jest napisane: ** Kolejność, w jakiej foreach przechodzi przez elementy tablicy, jest następująca: W przypadku tablic jednowymiarowych elementy przechodzą w rosnącym porządku indeksów, zaczynając od indeksu 0 i kończąc. z indeksem Długość - 1. W przypadku wielowymiarowych tablic, elementy są wykonywane w taki sposób, że indeksy wymiaru z prawej strony są najpierw zwiększane, następnie następny lewy wymiar, i tak dalej po lewej. ** Następnie następuje przykład, który sprawia, że jest jeszcze bardziej przejrzysty w przypadku tablic wielowymiarowych. –

0

Jagged tablice nie obsługują IEnumerable<int> albo, ponieważ struktury wielowymiarowe nie są naprawdę tablicą typu, są tablicą z tablicą typu:

int[] singleDimensionArray = new int[10]; 
int[][] multiJagged = new int[10][]; 

Debug.WriteLine(singleDimensionArray is IEnumerable<int>); 
Debug.WriteLine(multiJagged is IEnumerable<int[]>); 
Debug.WriteLine(singleDimensionArray is IEnumerable); 
Debug.WriteLine(multiJagged is IEnumerable); 

Drukuje prawda, prawda, prawda, , prawdziwe.

Uwaga: int[,] nie jest IEnumerable<int[]>, to z przyczyn określonych w drugiej odpowiedzi, mianowicie nie ma rodzajowe sposobem, aby wiedzieć, które wymiar iteracyjnego. W przypadku poszarpanych tablic nie ma miejsca na interpretację, ponieważ składnia jest dość jasna, ponieważ jest to tablica tablic.

+0

Rekursja nie jest przedmiotem dyskusji. – recursive

+0

@recursive: okrzyki, naprawione. Tylko przypadek mieszania mojej terminologii. –

+3

Nie mówi o poszarpanych tablicach, mówił o wielowymiarowym układzie. –

10

Istnieje obejście: można przekonwertować dowolny wielowymiarową tablicę do IEnumerable

public static class ArrayExtensions 
{ 
    public static IEnumerable<T> ToEnumerable<T>(this Array target) 
    { 
     foreach (var item in target) 
      yield return (T)item; 
    } 
} 
+0

Świetne rzeczy! Musiałem jednak jawnie określić typ ogólny, więc użycie staje się: myArray.ToEnumerable () – angularsen

+23

To jest to samo, co 'IEnumerable.Cast '. –

+0

@Markus Brilliant, to powinna być zaakceptowana odpowiedź. Na marginesie, jeśli tablica może zawierać puste wartości, staje się Matrix.Cast (Of T) .Where (Function (x) x IsNot Nothing) – smirkingman

0

Pomyśl odwrotnie. Tablica 2d już istnieje. Po prostu ją wylicz. Utwórz tablicę 2d z wynikiem i miejscem początkowej tablicy lub znaczników, w tym zduplikowanymi wartościami.

int[] secondmarks = {20, 15, 31, 34, 35, 50, 40, 90, 99, 100, 20}; 

IEnumerable<int> finallist = secondmarks.OrderByDescending(c => c); 

int[,] orderedMarks = new int[2, finallist.Count()]; 

Enumerable.Range(0, finallist.Count()).ToList().ForEach(k => {orderedMarks[0, k] = (int) finallist.Skip(k).Take(1).Average(); 
orderedMarks[1, k] = k + 1;}); 

Enumerable.Range(0, finallist.Count()).Select(m => new {Score = orderedMarks[0, m], Place = orderedMarks[1, m]}).Dump(); 

Wyniki:

Score Place 

100  1 
99  2 
90  3 
50  4 
40  5 
35  6  
34  7  
31  8  
20  9  
20  10 
15  11 
+0

Proszę podać wyjaśnienie swojego kodu. –

1

zero bound tablic jednowymiarowych realizuje zarówno IEnumerable i IEnumerable<T>, ale tablice wielowymiarowe, niestety, realizuje tylko IEnumerable. "Obejście" przez @Jader Dias rzeczywiście przekształca wielowymiarową tablicę na IEnumerable<T>, ale z ogromnym kosztem: każdy element tablicy zostanie zapakowany.

Oto wersja, która nie spowoduje boks dla każdego elementu:

public static class ArrayExtensions 
{ 
    public static IEnumerable<T> ToEnumerable<T>(this T[,] target) 
    { 
     foreach (var item in target) 
      yield return item; 
    } 
} 
Powiązane problemy