Przyczyny nieintuicyjnej implementacji C # String.Split ()

10

W C #, jeśli chcę podzielić jeden stringprzez inny, stringmuszę zrobić coś takiego:

testString.Split(new string[] { "anotherString" }, StringSplitOptions.None);

Z przeciążonej String.SplitDokumentacji MSDN możemy zobaczyć implementację i dlaczego należy wykonać takie połączenie.

Pochodząc z Pythona , trudno mi właściwie zrozumieć, dlaczego takie połączenie jest potrzebne. Mam na myśli, że mogłabym użyć Regex.Splitpodobnej składni niż implementacja Pythona, ale musiałbym to zrobić kosztem mniejszej wydajności (czasu instalacji) dla dowolnej prostej rzeczy .

Zasadniczo moje pytanie brzmi: dlaczego, u diabła, nie możemy po prostu zrobić:

testString.Split("anotherString");

Zauważ, że nie sugeruję żadnego prototypu ani implementacji. Rozumiem, dlaczego nie można wdrożyć powyższej wersji, biorąc pod uwagę bieżący interfejs API. Moim celem było zrozumienie, dlaczego taki interfejs API mógł zostać utworzony, biorąc pod uwagę korzyści wynikające z powyższej składni. Na razie elastyczność wydaje się być celem prądu, String.Splitktóry ma sens, ale szczerze mówiąc, naprawdę myślałem, że gdzieś nastąpił jakiś wzrost wydajności. Chyba się myliłem.

scharette
źródło
3
Też o tym myślałem. Spekuluję, że po prostu nie włożyli dużo wysiłku w zaprojektowanie tego jednego API. A jeśli uświadomili sobie swój błąd, było już za późno.
Euforia
@Caleth Czy możesz to rozwinąć? może się mylę, ale nie rozumiem, co w tym jest niejednoznacznego. Dlaczego nie mogę testString.Split(",.;");i testString.Split(new Char [] {',', '.', ';',);które nie są tym samym.
scharette
@Euphoric Też tak myślałem, ale to byłoby takie dziwne. Mam nadzieję, że ktoś przyjdzie z bardziej logiczną odpowiedzią.
scharette
Możesz iterować po łańcuchu tak jak w przypadku IEnumerable<char>, gdy sugerowany przez Ciebie dodatkowy prototyp może w niektórych przypadkach wydawać się niejednoznaczny (czy ograniczasz go przez cały ciąg, czy też przez każdy z jego znaków?).
John Wu
@JohnWu Być może jest to sprawa osobista, ale w przypadku 99,9% przypadków składni, na przykład testString.Split("anotherString");, jestem całkiem pewny, że mogę powiedzieć, że oczekiwanym zachowaniem było ograniczenie całego łańcucha ( anotherStringw tym przypadku).
scharette

Odpowiedzi:

15

Czasami użyteczne jest dzielenie na więcej niż jeden znak / ciąg znaków, więc interfejs API pozwala zapewnić tablicę, zapewniając maksymalną elastyczność. W przypadku chars otrzymujesz zarówno prostotę składni, jak i elastyczność, ponieważ parametr jest oznaczony jako, paramswięc możesz pisać Split('x')zamiast Split(new[]{'x'}).

Dlaczego więc nie ma podobnej opcji dla ciągów, umożliwiającej pisanie Split("x")?

Jest to być może niefortunna konsekwencja projektowania interfejsu API. Początkowo dozwolone było tylko dzielenie znaków. Dzielenie ciągów zostało dodane w 2.0, prawdopodobnie dlatego, że jest bardziej skomplikowane do wdrożenia. Ale dodawanie String.Split(string)lub String.Split(string[])przeciążanie nie było możliwe , ponieważ spowodowałoby to, że wyrażenie byłoby testString.Split(null)niejednoznaczne, a kod nie byłby już kompilowany.

testString.Split(null) jest właściwie dość powszechnym idiomem, ponieważ dzieli ciąg znaków na białe znaki, więc takie złamanie byłoby zbyt powszechne, aby było możliwe do zaakceptowania.

Używanie nullparametru jako przełącznika specjalnego zachowania jest obecnie ogólnie uważane za zły projekt, więc myślę, że to sprawiedliwe, że ten interfejs API jest po prostu wadliwy.

Nie ma Split(string[], Int32)też żadnego , prawdopodobnie z podobnego powodu - byłoby dwuznaczne, Split(char[], Int32)gdyby pierwszym parametrem był null. Tam podobne przeciążenia z StringSplitOptionsparametrami, ale te były dodawane w tym samym czasie w 2,0, więc nie ma dwuznaczności został wprowadzony w istniejącym kodzie.

Uwaga

Dla jasności, to tylko moja hipoteza, nie znam faktycznego myślenia projektantów frameworka .net.

JacquesB
źródło
1
Czy to w ogóle jest przydatne? Wątpie w to. I to tylko przerwa API, a nie ABI.
Deduplicator
2
@Deduplicator: Split (null) dzieli się na białe znaki, więc jest to prawdopodobnie jeden z najczęstszych przypadków użycia split, nawet jeśli użycie interfejsu API o takiej wartości jest złe.
JacquesB,
1
Myślę, że @Deduplicator chciał powiedzieć, że Split(null)jest to bezużyteczne, jeśli pozwolisz Split(""). Poza tym, że pozwoliłoby to na lepszą składnię, ta i tak jest bardziej gadatliwa ...
scharette
1
@scharette: Jasne, ale nie można teraz zmienić, nie naruszając wstecznej kompatybilności.
JacquesB,
1
uwaga: z aktualnego podglądu C # 8, wyłączając typy bazowe opcje dopuszczania wartości null String.Split(null)nie będzie już niejednoznaczne, aby mogli dodać przeciążenie
BgrWorker
2

Nie będąc autorem metod, nie wiem, dlaczego wybrano ten zestaw przeciążeń. Należy jednak zwrócić uwagę na dwie rzeczy:

  1. Jeśli dzielisz na jeden znak, public string[] Split(params char[] separatormożna użyć wersji) w ten sposób:

    var splitValues = testString.Split(',');

    jak char[]to paramsparametr.

  2. Możesz łatwo dodać tutaj własną metodę rozszerzenia, aby osiągnąć to, co chcesz:

    public static class StringExtensions
    {
        public static string[] Split(this string source, string separator)
            => source.Split(new string[] { separator }, StringSplitOptions.None);
    }
    

    i teraz testString.Split("anotherString");będzie działać dla ciebie.

David Arno
źródło
1
Dziękuję za opinię. Chociaż twoja odpowiedź jest pomocna i zwięzła, nie mogę się z tobą zgodzić. Zwłaszcza drugi punkt. Czy nie jest jeszcze jeden powód, aby mieć to wbudowane? Pozwala społeczności stworzyć inną wersję metody, którą wszyscy (lub prawie wszyscy) będą się zachowywać w ten sam sposób.
scharette
Nawiasem mówiąc, nie próbując debatować, twój punkt widzenia jest całkowicie uzasadniony. Próbuję zrozumieć przyczynę tego. Logicznie rzecz biorąc, musi istnieć powód historyczny lub związany z wydajnością ...
scharette
@scharette: Powodem jest, aby metoda była tak ogólna, jak to możliwe. Preferowane jest, gdy znajdziesz wybraną sygnaturę metody, ale nie będzie działać dla wielu separatorów. Wersja Microsoft będzie działać dla wielu ograniczników, a także dla pojedynczego ogranicznika.
Robert Harvey
@RobertHarvey Cóż, czy nie byłoby to możliwe? Powiedzmy, że metoda rozszerzenia w powyższej odpowiedzi była częścią Stringklasy, obie byłyby możliwe. Czy się mylę ?
scharette
Myślę, że brakuje ci sensu. Twoje przeciążenie pozwala tylko na jeden ogranicznik. Przeciążenie Microsoft pozwala na więcej niż jeden. Nie możesz wywołać przeciążenia wiele razy i osiągnąć ten sam wynik; nie tak to działa.
Robert Harvey
1

Różne języki mają nieco inne reguły dotyczące niejawnych konwersji i przeciążenia, a .NET Framework został zaprojektowany do współpracy z dowolnym z nich. W Option Strict Offdialekcie VB.NET wartość typu Stringmoże być przekazana do funkcji, która oczekuje Char[]zachowania o wartości równoważnej wywołaniu ToCharArray()ciągu.

Myślę, że rozsądną rzeczą byłoby mieć osobne nazwy dla Split(który akceptuje pojedynczy Charlub String) i SplitMulti(który akceptuje Char[]lub String[]), ale .NET wydaje się czasem preferować używanie samego przeciążenia do wybierania różnych rodzajów operacji. Niestety nie wiem, w jaki sposób można zastosować String.Splitscenariusze użycia, które wymagałyby rozróżnienia różnych rodzajów ograniczników innych niż oddzielne rozdzielenie każdego z nich.

Innym pominięciem jest opcja zachowania ograniczników, włączając je na końcu poprzedniego łańcucha lub na początku następnego łańcucha, lub mając elementy tablicy nieparzystej liczby, a elementy parzyste są między nimi.

supercat
źródło
1
.NET wydaje się czasem preferować używanie samego przeciążenia do wybierania różnego rodzaju operacji. Tak prawda ...
scharette