Dlaczego nowy indeks operatora kapelusza z funkcji wycinania tablicy w języku C # 8 nie zaczyna się od 0?

156

C # 8.0 wprowadza wygodny sposób dzielenia tablic - zobacz oficjalny wpis na blogu C # 8.0 .

Składnia dostępu do ostatniego elementu tablicy to

int value[] = { 10, 11, 12, 13 };

int a = value[^1]; // 13
int b = value[^2]; // 12

Zastanawiam się, dlaczego indeksowanie w celu uzyskania dostępu do elementów wstecz zaczyna się od 1 zamiast 0? Czy jest tego techniczny powód?

Michael Pittino
źródło
11
Zauważ, że zakresy C ++ są również [beginInclusive, endExclusive). To powszechna konwencja.
bommelding
3
@Sinatr: Opierając się na tym poście na blogu, składnia zwracająca wszystko byłaby taka value[0..^0], ponieważ indeks końcowy jest wyłączny (tak działa również większość innych języków). Ponadto wygodnie value[^i..^0]da ci ostatnie iprzedmioty.
BlueRaja - Danny Pflughoeft
1
@bommelding: C ++ rbegin()nieco nie zgadza się z tym pojęciem - pierwszy element z tego zakresu również nie jest tym, który znajduje się poza końcem. ;-)
DevSolar
1
O super, to wygląda na odpowiednik indeksowania ujemnego w Pythonie:value[-1] # 13
cs95
1
@coldspeed A w Rubim to samo co Python. Domyślam się, że obaj pożyczyli tę konwencję od Perla.
Wayne Conrad,

Odpowiedzi:

170

Oficjalna odpowiedź

Aby uzyskać lepszą widoczność, oto komentarz Madsa Torgersena wyjaśniający tę decyzję projektową z wpisu na blogu C # 8 :

Zdecydowaliśmy się pójść za Pythonem, jeśli chodzi o arytmetykę od początku i od końca. 0 wyznacza pierwszy element (jak zawsze) i  ^0 „długość” elementu, czyli ten znajdujący się tuż za końcem. W ten sposób otrzymujesz prostą relację, w której pozycja elementu od początku plus jego pozycja od końca równa się długości. x w  ^x to co byś odjąć od długości jeśli zrobił matematyki siebie.

Dlaczego nie użyć minus ( -) zamiast nowego ^operatora hat ( )? Dotyczy to przede wszystkim zakresów. Ponownie, zgodnie z Pythonem i większością branży, chcemy, aby nasze zakresy były na początku uwzględniające, a na końcu wyłączne. Jaki jest indeks, który przekazujesz, aby stwierdzić, że zakres powinien sięgać aż do końca? W C # odpowiedź jest prosta: x..^0idzie od  x końca. W Pythonie nie ma wyraźnego indeksu, który możesz podać: -0nie działa, ponieważ jest równy 0pierwszemu elementowi! Tak w Pythonie, musisz opuścić indeks końcowy całkowicie wyłączony w celu wyrażenia zakresu że idzie do końca: x... Jeśli obliczany jest koniec zakresu, musisz pamiętać o specjalnej logice na wypadek, gdyby to się okazało 0. Jak w x..-y, gdzieyzostał obliczony i wyszedł do 0. Jest to powszechna uciążliwość i źródło błędów.

Na koniec zwróć uwagę, że indeksy i zakresy są pierwszymi typami klas w .NET / C #. Ich zachowanie nie jest powiązane z tym, do czego są stosowane, ani nawet nie ma ich używać w indeksatorze. Możesz całkowicie zdefiniować swój własny indeksator, który pobiera indeks i inny, który bierze Range- i zamierzamy dodać takie indeksatory np  Span. Ale możesz też mieć metody, które na przykład przyjmują zakresy.

Moja odpowiedź

Myślę, że jest to zgodne z klasyczną składnią, do której jesteśmy przyzwyczajeni:

value[^1] == value[value.Length - 1]

Gdyby używał 0, byłoby mylące, gdyby dwie składnie były używane obok siebie. W ten sposób ma mniejsze obciążenie poznawcze.

Inne języki, takie jak Python, również używają tej samej konwencji.

Martin Zikmund
źródło
13
Drobna poprawka do komentarza Madsa: nie musisz całkowicie opuszczać indeksu końcowego w Pythonie. Można użyć Nonew miejsce numeru: [0,1,2,3,4][2:None] == [2,3,4]. Ale tak, nie możesz użyć liczby całkowitej jako indeksu końcowego (bez oczywiście obliczenia długości).
Giacomo Alzetta
4
Czekaj… co jest nie tak x..? Wydaje się w porządku i nigdy nie miałem problemu ze [3:]składnią Pythona .
mowwwalker
8
@mowwwalker - czy nie jest to już uwzględnione w wycenie? „Więc w Pythonie ... Jeśli obliczany jest koniec zakresu, musisz pamiętać o specjalnej logice na wypadek, gdyby okazało się, że wynosi 0”
Damien_The_Unbeliever,
3
Komentarz @mowwwalker Madsa dotyczył przypadków, w których nie wiesz, jaka będzie wartość indeksu, ponieważ jest w jakiś sposób obliczana. Mówią, że w przypadku, gdy chcesz obliczyć endIndexjako indeks ujemny (tj. Indeks od końca), będziesz miał nieciągłość między liczbami ujemnymi i dodatnimi, ponieważ 0w tym przypadku nie będzie działać poprawnie. Jak podkreślił, trzeba zastąpić 0przez Noneto. Oznacza to, że Twój kod powinien wyglądać seq[startIndex:endIndex or None]na przykład. or NonePowinny zostać pominięte, jeśli endIndexma być dodatni.
Giacomo Alzetta
5
Dobrze jest widzieć, że nie powtarzają błędu Pythona z opcją -0. Obsługa tego specjalnego przypadku jest ogromnym kłopotem i zbyt łatwo o niej zapomnieć.
user2357112 obsługuje Monikę