2017-02-09 19 views
41

próbuję dowiedzieć się, dlaczego mój testu urządzenie nie (Trzeci assert poniżej):interpolacja String wystawia

var date = new DateTime(2017, 1, 1, 1, 0, 0); 

var formatted = "{countdown|" + date.ToString("o") + "}"; 

//Works 
Assert.AreEqual(date.ToString("o"), $"{date:o}"); 
//Works 
Assert.AreEqual(formatted, $"{{countdown|{date.ToString("o")}}}"); 
//This one fails 
Assert.AreEqual(formatted, $"{{countdown|{date:o}}}"); 

AFAIK, to powinno działać poprawnie, ale wydaje się, że to nie przejdzie formatowanie Parametr poprawnie, pojawia się tylko jako {countdown|o} do kodu. Masz pomysł, dlaczego to się nie udaje?

+0

Pojawia się (chociaż nie znoszę tego mówić), że jest to błąd kompilatora. – DavidG

+5

@DavidG: Może to być błąd kompilatora lub może być błędem w bazowej bibliotece formatowania, ale zgadzam się, że coś tu śmierdzi. To przynajmniej powinno być zbadane. –

+0

Wydaje się, że ma to związek ze sposobem sprawdzania nawiasu zamykającego interpolacji.Z powyższym kodem zewnętrzny nawias zamyka interpolację '{{countdown | ** {** date: o}} **} **', spacja między nawiasami powoduje jej ocenę do wewnętrznego nawiasu '{{countdown | ** {** date: o **} ** _}} '. – Equalsk

Odpowiedz

21

Problem z tej linii

Assert.AreEqual(formatted, $"{{countdown|{date:o}}}"); 

jest to, że masz 3 cudzysłowy po format string zmiennej być uciekł i rozpoczyna ucieczkę od lewej do prawej, dlatego traktuje pierwsze 2 cudzysłowy jako część ciągu formatu, a trzeci kędzierzawy cytat jako zamykający.

Przekształca się w o w o} i nie można go interpolować.

To powinno działać

Assert.AreEqual(formatted, $"{{countdown|{date:o}"+"}"); 

zauważyć, że prostsze $"{date}}}" (czyli 3 loki po zmiennej bezformat string) działa ponieważ uznaje, że pierwsza kręcone cytat jest zamknięcie jednego, podczas interpretacji specyfikatora formatu po : łamie prawidłową identyfikację nawias zamknięcia.

Aby udowodnić, że ciąg format uciekł jak struna, uważają, że następujący

$"{date:\x6f}" 

jest traktowany jako

$"{date:o}" 

Wreszcie, jest całkiem możliwe, że dwukrotnie uciekł kręcone Cytaty są częścią niestandardowego formatu daty, więc absolutnie rozsądne jest zachowanie kompilatora. Ponownie konkretny przykład:

$"{date:MMM}}dd}}yyy}" // it's a valid feb}09}2017 

Parsowanie jest formalnym procesem opartym na regułach gramatyki ekspresji, nie można tego zrobić, po prostu rzucając okiem na to.

+0

Liczba nawiasów klamrowych to nie problem jako '$" {{{date: o}}} "' nadal generuje nieprawidłowy wynik. – DavidG

+1

@DavidG * 3 kręcone cytaty * po tym, jak napis formatujący 'o' * do * tworzy nieprawidłowe wyniki, jak napisałem w mojej odpowiedzi - to jest teraz ** poprawne ** –

+2

Niezła analiza. Jestem zaskoczony tym, że lexer traktuje '}}}', który podąża za specyfikatorem formatu jako "unikalny"}, po którym następuje znaczący '}'. Naiwnie oczekiwałbym, że reguła będzie "po znalezieniu sensownego' {', parsowania sformatowanych wyrażeń dopóki nie znajdziesz pasującego'} ', a następnie wznowienia zwykłego lexowania ciągów znaków Następnym razem gdy zobaczę Neala, zapytam co doprowadziło do tego nieco zaskakującego wyniku: –

2

Problem polega na wstawianiu nawiasu przy użyciu interpolacji ciągów, które należy ująć, powielając je. Jeśli dodać nawias używany samej interpolacji, możemy skończyć z potrójnym nawiasie takie jak to masz w wierszu, który daje wyjątek:

Assert.AreEqual(formatted, $"{{countdown|{date:o}}}"); 

Teraz, gdy mamy do czynienia z "}} } ", możemy zauważyć, że pierwszy nawias zamyka interpolację ciągów, podczas gdy ostatnie dwie mają być traktowane jako nawias z ciągami znaków.

Jednak kompilator traktuje pierwsze dwa jako znak ciągniętych znaków, dlatego wstawia ciąg pomiędzy ogranicznikami interpolacji.Zasadniczo kompilator robi coś takiego:

string str = "a string"; 
$"{str'}'}"; //this would obviously generate a compile error which is bypassed by this bug 

Można rozwiązać ten problem poprzez ponowne sformatowanie linię jako takie:

Assert.AreEqual(formatted, $"{{countdown|{$"{date:o}"}}}"); 
6

To follow-up do mojej oryginalnej odpowiedzi w celu

, aby upewnić się, że jest to zamierzone zachowanie:

Jeśli chodzi o oficjalne źródło, powinniśmy odnosić się do Interpolated Strings z msdn.

Strukturę interpolowana ciąg jest

$ " <text> { <interpolation-expression> <optional-comma-field-width> <optional-colon-format> } <text> ... } " 

a każdy pojedynczy interpolacja jest formalnie zdefiniowana w składni

single-interpolation: 
    interpolation-start 
    interpolation-start : regular-string-literal 

interpolation-start: 
    expression 
    expression , expression 

Tu liczy, że

  1. optional-colon-format jest zdefiniowana jako składnia regular-string-literal => tj. może zawierać escape-sequence, zgodnie z paragraph 2.4.4.5 String literals z C# Language Specification 5.0
  2. można użyć interpolowana ciąg wszędzie można użyć string literal
  3. Aby dołączyć nawias klamrowy ({ lub }) w interpolowana ciąg użyć dwóch nawiasów klamrowych, {{ lub }} = > tj kompilator ucieka dwa nawiasów klamrowych w optional-colon-format
  4. kompilator skanuje zawarty interpolacji expressions jako symetryczne tekstu aż znajdzie przecinek, okrężnicy, albo w pobliżu nawiasem => tj okrężnicy przerwy wyważony tekst jak bliski nawias klamrowy

Żeby było jasne, to wyjaśnia różnicę między $"{{{date}}}" gdzie date jest expression a więc jest tokenized do pierwszego nawias klamrowy kontra $"{{{date:o}}}" gdzie date jest znowu expression i teraz jest to tokenized aż do pierwszego dwukropek, po którym regularny ciąg dosłowne zaczyna i kompilator powraca ucieczce dwa nawiasy klamrowe, itp ...

Istnieje również String Formatting FAQ z MSDN, gdzie ta sprawa był wyraźnie potraktowane.

int i = 42; 
string s = String.Format(“{{{0:N}}}”, i); //prints ‘{N}’ 

Pytanie jest, dlaczego ta ostatnia próba nie powiedzie?Są dwie rzeczy trzeba wiedzieć, aby zrozumieć ten wynik:

Świadcząc formacie specyfikator formatowanie ciąg traktuje te etapy:

Ustal, czy specifier jest dłuższy niż jeden znak: jeśli tak, następnie założyć, że specyfikator jest niestandardowym formatem. Niestandardowy format użyje odpowiednich zamienników dla twojego formatu, ale jeśli nie będzie wiedział, co zrobić z jakimś charakterem, po prostu wypisze go jako literał znaleziony w formacie Ustal, czy pojedynczy znak Specyfikator jest obsługiwany specyfikator (na przykład "N" w przypadku formatowania ). Jeśli tak, sformatuj odpowiednio. Jeśli nie, rzucać ArgumnetException

Podczas próby ustalenia, czy kręcone wspornik powinien być uciekł, nawiasy klamrowe są po prostu traktowane w kolejności, w jakiej są odebrany. W związku z tym {{{ będzie uciec od pierwszych dwóch znaków, a wydrukuje literał {, a trzeci nawias klamrowy rozpocznie sekcję formatowania . Na tej podstawie w }}} zostaną usunięte pierwsze dwa nawiasy klamrowe , dlatego literowy } zostanie zapisany w postaci ciąg formatu, a następnie ostatni nawias klamrowy zostanie przyjęty na , kończąc sekcję formatowania z tymi informacjami, teraz można dowiedzieć się, co dzieje się w naszej sytuacji {{{0:N}}}. Pierwsze dwa nawiasy klamrowe są unikane, a następnie mamy sekcję formatowania. Jednakże, również wtedy unikamy zamykającego nawiasu klamrowego, przed zamknięciem sekcji formatowania . Dlatego nasza sekcja formatowania jest w rzeczywistości interpretowana jako zawierająca 0:N}. Teraz formater analizuje specyfikator formatu i widzi N} dla specyfikatora. Dlatego też, interpretuje to jako format niestandardowy, a ponieważ ani N ani} oznacza niczego dla niestandardowego formatu liczbowego, te znaki są po prostu wypisywane, a nie wartością odnośnej zmiennej.

+0

Dzięki za szczegółową obserwację. – FrankerZ

1

Jest to najprostszy sposób, aby uzyskać dochodzić do pracy ...

Assert.AreEqual(formatted, "{" + $"countdown|{date:o}" + "}"); 

W tej formie ...

Assert.AreEqual(formatted, $"{{countdown|{date:o}}}"); 

pierwszych 2 zamykających szelki są interpretowane jako dosłowny zamykający nawias klamrowy i trzeci zamykający wyrażenie formatujące.

To nie jest błąd, a jedynie ograniczenie gramatyki interpolowanych ciągów znaków. Błąd, jeśli taki istnieje, jest taki, że wyjście sformatowanego tekstu powinno być raczej "o}" zamiast "o".

Powodem, dla którego mamy operatora "+ =" zamiast "= +" w C, C# i C++ jest to, że w formie = + nie można w niektórych przypadkach stwierdzić, czy "+" jest częścią operatora lub jednoargumentowy "+".