Literał surowego ciągu znaków
Notatka
Ten artykuł jest specyfikacją funkcji. Specyfikacja służy jako dokument projektowy dla funkcji. Zawiera proponowane zmiany specyfikacji wraz z informacjami wymaganymi podczas projektowania i opracowywania funkcji. Te artykuły są publikowane do momentu sfinalizowania proponowanych zmian specyfikacji i włączenia ich do obecnej specyfikacji ECMA.
Mogą wystąpić pewne rozbieżności między specyfikacją funkcji a ukończoną implementacją. Te różnice są odnotowane w odpowiednich notatkach z spotkań projektowych językowych (LDM) .
Więcej informacji na temat procesu wdrażania specyfikacji funkcji można znaleźć w standardzie języka C# w artykule dotyczącym specyfikacji .
Streszczenie
Zezwalaj na nową formę literału ciągu rozpoczynającego się od co najmniej trzech """
znaków (ale bez maksymalnej), opcjonalnie następuje new_line
, zawartość ciągu, a następnie kończy się tą samą liczbą cudzysłowów, z którymi zaczynał się literał. Na przykład:
var xml = """
<element attr="content"/>
""";
Ponieważ zawartość zagnieżdżona może sama chcieć zastosować """
, wówczas ograniczniki początkowe/końcowe mogą być dłuższe, na przykład:
var xml = """"
Ok to use """ here
"""";
Aby tekst był łatwy do odczytania i umożliwiał wcięcie, które deweloperzy lubią w kodzie, te literały ciągu będą naturalnie usuwać wcięcie określone w ostatnim wierszu podczas tworzenia końcowej wartości literału. Na przykład literał formularza:
var xml = """
<element attr="content">
<body>
</body>
</element>
""";
Zawartość będzie obejmować:
<element attr="content">
<body>
</body>
</element>
Dzięki temu kod może wyglądać naturalnie, jednocześnie tworząc żądane literały i unikając kosztów środowiska uruchomieniowego, jeśli jest to wymagane do użycia wyspecjalizowanych procedur manipulowania ciągami.
Jeśli nie jest potrzebne zachowanie wcięcia, łatwo można je wyłączyć w następujący sposób:
var xml = """
<element attr="content">
<body>
</body>
</element>
""";
Obsługiwany jest również pojedynczy formularz liniowy. Zaczyna się od co najmniej trzech znaków """
(ale bez maksymalnej), zawartości ciągu (który nie może zawierać żadnych znaków new_line
), a następnie kończy się tą samą liczbą cudzysłowów, z którymi zaczynał się literał. Na przykład:
var xml = """<summary><element attr="content"/></summary>""";
Obsługiwane są również nieprzetworzone ciągi interpolowane. W tym przypadku ciąg określa liczbę nawiasów klamrowych potrzebnych do rozpoczęcia interpolacji (określonej przez liczbę znaków dolara obecnych na początku literału). Każda sekwencja z mniejszą liczbą nawiasów klamrowych jest traktowana tylko za zawartość. Na przykład:
var json = $$"""
{
"summary": "text",
"length" : {{value.Length}},
};
""";
Motywacja
Język C# nie ma ogólnego sposobu tworzenia prostych literałów tekstowych, które mogą zawierać dowolny tekst. Wszystkie formularze literału ciągu języka C# wymagają obecnie jakiejś formy ucieczki, jeśli zawartość używa specjalnego znaku (zawsze, jeśli jest używany ogranicznik). Zapobiega to łatwemu tworzeniu literałów zawierających inne języki (na przykład literału XML, HTML lub JSON).
Wszystkie bieżące podejścia do tworzenia tych literałów w języku C# zawsze wymuszają na użytkowniku ręczne ucieczkę zawartości. Edytowanie w tym momencie może być bardzo irytujące, ponieważ ucieczka nie może być unikana i musi być traktowana za każdym razem, gdy pojawia się w treści. Jest to szczególnie bolesne dla wyrażeń regularnych, zwłaszcza gdy zawierają cudzysłowy lub backslashe. Nawet w przypadku ciągu dosłownego (@""
), cudzysłowy muszą zostać odpowiednio zapisane, co prowadzi do połączenia składni C# z wyrażeniami regularnymi.
{
i }
są podobnie frustrujące w interpolowanych ciągach ($""
).
Problemem jest to, że wszystkie nasze ciągi mają stały ogranicznik początkowy/końcowy. Tak długo, jak to jest w przypadku, zawsze będziemy musieli mieć mechanizm ucieczki, ponieważ zawartość ciągu może wymagać określenia tego ogranicznika końcowego w ich zawartości. Jest to szczególnie problematyczne, ponieważ ogranicznik "
jest niezwykle powszechny w wielu językach.
Aby rozwiązać ten problem, ta propozycja umożliwia elastyczne ograniczniki początkowe i końcowe, dzięki czemu mogą one być zawsze wykonywane w sposób, który nie będzie powodować konfliktu z zawartością ciągu.
Cele
- Podaj mechanizm, który umożliwi wszystkich wartości ciągów być udostępniany przez użytkownika bez konieczności żadnych sekwencji ucieczki. Ponieważ wszystkie ciągi muszą być reprezentowane bez sekwencji ucieczki, zawsze musi być możliwe, aby użytkownik mógł określić ograniczniki, które będą gwarantowane, aby nie zderzyły się z żadną zawartością tekstową.
- Umożliwiaj interpolacje w taki sam sposób. Jak powyżej, ponieważ wszystkie ciągi muszą być reprezentowane bez ucieczki, zawsze musi być możliwe, aby użytkownik określił ogranicznik
interpolation
, który będzie mieć gwarancję, że nie będzie zderzać się z żadną zawartością tekstową. Co ważne, języki korzystające z naszych interpolacji znaków ograniczników ({
i}
) powinny być uważane za najwyższej klasy i nie powinny sprawiać trudności w użyciu. - Literały tekstu wielowierszowego powinny być estetyczne w kodzie i nie powinny sprawiać, że wcięcia w jednostce kompilacji wydają się niepasujące. Co ważne, wartości literałów, które same nie mają wcięcia, nie powinny być umieszczane w pierwszej kolumnie w pliku, ponieważ może to zakłócić przepływ kodu i będzie wyglądać niezsynchronizowane z resztą otaczającego kodu.
- To zachowanie powinno być łatwe do zastąpienia przy zachowaniu czytelnych i łatwych do odczytania literałów.
- W przypadku wszystkich ciągów, które nie zawierają samego znaku
new_line
lub początku lub końca cudzysłowem ("
), powinno być możliwe reprezentowanie samego literału ciągu w jednym wierszu.- Opcjonalnie, z dodatkową złożonością, możemy udoskonalić tę wersję, aby stwierdzić, że: dla wszystkich ciągów, które same nie zawierają
new_line
(ale mogą się rozpoczynać lub kończyć od znaku cudzysłowu"
), powinno być możliwe, aby literał ciągu znaków był reprezentowany w pojedynczym wierszu. Aby uzyskać więcej informacji, zobacz rozszerzoną propozycję w sekcjiDrawbacks
.
- Opcjonalnie, z dodatkową złożonością, możemy udoskonalić tę wersję, aby stwierdzić, że: dla wszystkich ciągów, które same nie zawierają
Szczegółowy projekt (przypadek bez interpolacji)
Dodamy nową produkcję string_literal
w następującej formie:
string_literal
: regular_string_literal
| verbatim_string_literal
| raw_string_literal
;
raw_string_literal
: single_line_raw_string_literal
| multi_line_raw_string_literal
;
raw_string_literal_delimiter
: """
| """"
| """""
| etc.
;
raw_content
: not_new_line+
;
single_line_raw_string_literal
: raw_string_literal_delimiter raw_content raw_string_literal_delimiter
;
multi_line_raw_string_literal
: raw_string_literal_delimiter whitespace* new_line (raw_content | new_line)* new_line whitespace* raw_string_literal_delimiter
;
not_new_line
: <any unicode character that is not new_line>
;
Ogranicznik końcowy do raw_string_literal
musi być zgodny z ogranicznikiem początkowym. Więc jeśli ogranicznik początkowy jest """""
ogranicznik końcowy musi być taki, jak również.
Powyższa gramatyka dla raw_string_literal
powinna być interpretowana jako:
- Zaczyna się od co najmniej trzech cudzysłowów (ale nie ma górnej granicy cudzysłowów).
- Następnie kontynuuje treść w tej samej linii co cudzysłowy początkowe. Zawartość w tym samym wierszu może być pusta lub niepusta. "pusty" jest synonimem "całkowicie pustych znaków".
- Jeśli zawartość tego samego wiersza nie jest pusta, dalsza zawartość nie może się pojawić. Innymi słowy, literał musi kończyć się taką samą ilością cudzysłowów w tym samym wierszu.
- Jeśli zawartość w tym samym wierszu jest pusta, literał może kontynuować z
new_line
i pewną liczbą kolejnych wierszy zawartości inew_line
s.- Linia zawartości to dowolny tekst z wyjątkiem
new_line
. - Następnie kończy się
new_line
pewną liczbą (prawdopodobnie zerową)whitespace
i taką samą liczbą cudzysłowów, od których zaczynał się literał.
- Linia zawartości to dowolny tekst z wyjątkiem
Nieprzetworzona wartość literału ciągu
Części między raw_string_literal_delimiter
początkową a końcową służą do tworzenia wartości raw_string_literal
w następujący sposób:
- W przypadku
single_line_raw_string_literal
wartość literału będzie dokładnie treścią między początkowym a końcowymraw_string_literal_delimiter
. - W przypadku
multi_line_raw_string_literal
początkowywhitespace* new_line
i końcowynew_line whitespace*
nie wchodzi w skład wartości ciągu. Jednak ostatnia częśćwhitespace*
poprzedzająca terminalraw_string_literal_delimiter
jest traktowana jako "biały znak wcięcia" i będzie mieć wpływ na sposób interpretowania innych wierszy. - Aby uzyskać końcową wartość, należy przejść sekwencję
(raw_content | new_line)*
i wykonać następujące czynności:- Jeżeli jest
new_line
, zawartośćnew_line
zostanie dodana do końcowej wartości ciągu. - Jeśli nie jest to "pusty"
raw_content
(tj.not_new_line+
zawiera znak inny niżwhitespace
):- Znak "wcięcia biały znak" musi być prefiksem
raw_content
. W przeciwnym razie jest to błąd. - Odstęp wcięcia jest usuwany z początku
raw_content
, a pozostała część jest dodawana do końcowej wartości ciągu.
- Znak "wcięcia biały znak" musi być prefiksem
- Jeżeli
raw_content
jest 'pustym' (tj.not_new_line+
jest całkowiciewhitespace
):- Znak "biały znak wcięcia" musi być prefiksem
raw_content
alboraw_content
musi być prefiksem "białego znaku wcięcia". W przeciwnym razie jest to błąd. - ponieważ większość białych znaków "wcięcia" jest usuwana od początku
raw_content
, a pozostała część jest dodawana do końcowej wartości ciągu.
- Znak "biały znak wcięcia" musi być prefiksem
- Jeżeli jest
Wyjaśnienia:
single_line_raw_string_literal
nie może reprezentować ciągu z wartościąnew_line
.single_line_raw_string_literal
nie uczestniczy w przycinaniu białych znaków wcięcia. Jego wartość to zawsze dokładnie takie znaki między ogranicznikami początkowymi i końcowymi.Ponieważ
multi_line_raw_string_literal
ignoruje ostatniąnew_line
ostatniego wiersza zawartości, poniżej przedstawiono ciąg bez początkowegonew_line
i bez zakończenianew_line
var v1 = """
This is the entire content of the string.
""";
Aby zachować symetrię z ignorowaniem początkowego new_line
, zapewnia to również jednolity sposób na to, by zawsze można było dostosować odstępy wcięcia. Aby reprezentować ciąg z terminalem new_line
należy podać dodatkowy wiersz w następujący sposób:
var v1 = """
This string ends with a new line.
""";
single_line_raw_string_literal
nie może reprezentować wartości ciągu, która rozpoczyna się lub kończy cudzysłowem ("
), chociaż rozszerzenie tej propozycji jest podane w sekcjiDrawbacks
, w której pokazano, jak można to obsługiwać.multi_line_raw_string_literal
rozpoczyna się odwhitespace* new_line
po początkowymraw_string_literal_delimiter
. Ta zawartość po ograniczniku jest całkowicie ignorowana i nie jest używana w żaden sposób podczas określania wartości ciągu. Dzięki temu mechanizm może określaćraw_string_literal
, którego zawartość zaczyna się od samego znaku"
. Na przykład:
var v1 = """
"The content of this string starts with a quote
""";
-
raw_string_literal
może również reprezentować zawartość kończącą się cudzysłowem ("
). Jest to obsługiwane, ponieważ ogranicznik zakończenia musi znajdować się we własnym wierszu. Na przykład:
var v1 = """
"The content of this string starts and ends with a quote"
""";
var v1 = """
""The content of this string starts and ends with two quotes""
""";
- Wymaganie, aby 'puste'
raw_content
było prefiksem 'odstępu wcięcia' lub aby 'odstęp wcięcia' był jego prefiksem, pomaga zapewnić, że mylące scenariusze z mieszanym odstępem nie występują; zwłaszcza, że nie jest jasne, co powinno się zdarzyć z tym wierszem. Na przykład następujący przypadek jest niedozwolony:
var v1 = """
Start
<tab>
End
""";
Tutaj "wcięcie spacji" to dziewięć znaków spacji, ale "pusty znak"
raw_content
nie zaczyna się od takiego prefiksu. Nie ma wyraźnej odpowiedzi na to, jak ta linia<tab>
powinna być traktowana w ogóle. Czy powinno być ignorowane? Czy powinno być takie samo jak.........<tab>
? Aby uniknąć zamieszania, wydaje się najjaśniejsze, aby uznać to za niezgodne z prawem.Następujące przypadki są jednak legalne i reprezentują ten sam ciąg:
var v1 = """
Start
<four spaces>
End
""";
var v1 = """
Start
<nine spaces>
End
""";
W obu tych przypadkach odstęp "wcięcia" będzie mieć dziewięć spacji. W obu przypadkach usuniemy jak najwięcej tego prefiksu, pozostawiając "puste" raw_content
w każdym przypadku pustym (nie licząc każdego new_line
). Dzięki temu użytkownicy nie muszą widzieć i potencjalnie martwić się o białe znaki w tych wierszach podczas kopiowania/wklejania lub edytowania tych wierszy.
- Jednak w przypadku:
var v1 = """
Start
<ten spaces>
End
""";
Znaki białe dla wcięcia nadal zajmują dziewięć miejsc. W tym miejscu usuniemy jak najwięcej białych znaków wcięcia, a puste raw_content
przyczyni się do dodania jednej spacji do ostatecznej zawartości. Pozwala to na przypadki, w których zawartość wymaga białych znaków w tych wierszach, które powinny zostać zachowane.
- Następujące kwestie nie są technicznie legalne:
var v1 = """
""";
Wynika to z faktu, że początek nieprzetworzonego ciągu musi mieć new_line
(co robi), ale koniec musi również mieć new_line
(czego nie robi). Minimalna raw_string_literal
prawna to:
var v1 = """
""";
Jednak ten ciąg jest zdecydowanie nieinteresujący, ponieważ jest odpowiednikiem ""
.
Przykłady wcięcia
Algorytm "wcięcia białych znaków" można wizualizować na kilku danych wejściowych, takich jak w tym przypadku. W poniższych przykładach użyto znaku pionowego paska |
, aby zilustrować pierwszą kolumnę w wynikowym nieprzetworzonym ciągu:
Przykład 1 — przypadek standardowy
var xml = """
<element attr="content">
<body>
</body>
</element>
""";
jest interpretowany jako
var xml = """
|<element attr="content">
| <body>
| </body>
|</element>
""";
Przykład 2 — ogranicznik końcowy w tym samym wierszu co zawartość.
var xml = """
<element attr="content">
<body>
</body>
</element>""";
Jest to nielegalne. Ostatni wiersz zawartości musi kończyć się na new_line
.
Przykład 3 — ogranicznik końcowy przed ogranicznikiem rozpoczęcia
var xml = """
<element attr="content">
<body>
</body>
</element>
""";
jest interpretowany jako
var xml = """
| <element attr="content">
| <body>
| </body>
| </element>
""";
Przykład 4 — ogranicznik końcowy po ograniczniku początkowym
var xml = """
<element attr="content">
<body>
</body>
</element>
""";
Jest to nielegalne. Wiersze zawartości muszą zaczynać się od znaków odstępu używanych do wcięcia.
Przykład 5 — pusty wiersz
var xml = """
<element attr="content">
<body>
</body>
</element>
""";
jest interpretowany jako
var xml = """
|<element attr="content">
| <body>
| </body>
|
|</element>
""";
Przykład 6 — pusty wiersz o mniejszym odstępie niż prefiks (kropki reprezentują spacje)
var xml = """
<element attr="content">
<body>
</body>
....
</element>
""";
jest interpretowany jako
var xml = """
|<element attr="content">
| <body>
| </body>
|
|</element>
""";
Przykład 7 — pusty wiersz z większą ilością spacji niż prefiks (kropki oznaczają spacje)
var xml = """
<element attr="content">
<body>
</body>
..............
</element>
""";
jest interpretowany jako
var xml = """
|<element attr="content">
| <body>
| </body>
|....
|</element>
""";
Szczegółowy projekt (przypadek interpolacji)
Interpolacje w normalnych ciągach interpolowanych (np. $"..."
) są obecnie obsługiwane za pomocą znaku {
, aby uruchomić interpolation
i użyć {{
sekwencji ucieczki w celu wstawienia rzeczywistego otwartego znaku nawiasu klamrowego. Zastosowanie tego samego mechanizmu naruszałoby cele "1" i "2" niniejszego wniosku. Języki, które mają {
jako podstawowy znak (przykłady to JavaScript, JSON, Regex, a nawet osadzone C#) będą teraz wymagały ucieczki, cofania przeznaczenia nieprzetworzonych literałów ciągu.
Aby obsługiwać interpolacje, wprowadzamy je w inny sposób niż zwykłe $"
ciągi interpolowane. W szczególności interpolated_raw_string_literal
rozpocznie się od pewnej liczby znaków $
. Liczba tych znaków wskazuje, ile znaków {
(i }
) jest potrzebnych w zawartości literału, aby rozgraniczyć interpolation
. Co ważne, nadal nie ma mechanizmu ucieczki dla nawiasów klamrowych. Zamiast tego, podobnie jak w przypadku cudzysłowów ("
) sam literał zawsze może zapewnić, że określa ograniczniki interpolacji, które z pewnością nie zderzają się z żadną resztą zawartości ciągu. Na przykład literał JSON zawierający otwory interpolacji można zapisać w następujący sposób:
var v1 = $$"""
{
"orders":
[
{ "number": {{order_number}} }
]
}
"""
W tym miejscu {{...}}
odpowiada wymaganej liczbie dwóch nawiasów klamrowych określonych przez prefiks ogranicznika $$
. W przypadku pojedynczego $
oznacza to, że interpolacja jest określana dokładnie tak jak {...}
w normalnych literałach ciągów interpolowanych. Co ważne, oznacza to, że literał interpolowany ze znakami N
$
może mieć sekwencję klamer 2*N-1
(takiego samego typu, następujących po sobie). Ostatnie nawiasy klamrowe N
rozpoczynają (lub kończą) interpolację, a pozostałe nawiasy klamrowe N-1
będą po prostu zawartością. Na przykład:
var v1 = $$"""X{{{1+1}}}Z""";
W tym przypadku wewnętrzne dwa nawiasy klamrowe {{
i }}
należą do interpolacji, a zewnętrzne nawiasy klamrowe są jedynie częścią zawartości. Powyższy ciąg jest więc odpowiednikiem zawartości X{2}Z
. Posiadanie 2*N
(lub więcej) nawiasów klamrowych jest zawsze błędem. Aby mieć dłuższe sekwencje nawiasów klamrowych jako zawartość, należy odpowiednio zwiększyć liczbę znaków $
.
Literały nieprzetworzonych ciągów interpolowanych są definiowane jako:
interpolated_raw_string_literal
: single_line_interpolated_raw_string_literal
| multi_line_interpolated_raw_string_literal
;
interpolated_raw_string_start
: $
| $$
| $$$
| etc.
;
interpolated_raw_string_literal_delimiter
: interpolated_raw_string_start raw_string_literal_delimiter
;
single_line_interpolated_raw_string_literal
: interpolated_raw_string_literal_delimiter interpolated_raw_content raw_string_literal_delimiter
;
multi_line_interpolated_raw_string_literal
: interpolated_raw_string_literal_delimiter whitespace* new_line (interpolated_raw_content | new_line)* new_line whitespace* raw_string_literal_delimiter
;
interpolated_raw_content
: (not_new_line | raw_interpolation)+
;
raw_interpolation
: raw_interpolation_start interpolation raw_interpolation_end
;
raw_interpolation_start
: {
| {{
| {{{
| etc.
;
raw_interpolation_end
: }
| }}
| }}}
| etc.
;
Powyższe jest podobne do definicji raw_string_literal
, ale z pewnymi ważnymi różnicami.
interpolated_raw_string_literal
należy interpretować jako:
- Zaczyna się od co najmniej jednego znaku dolara (ale bez górnej granicy), a następnie trzech znaków cudzysłowów (również bez górnej granicy).
- Następnie kontynuuje zawartość w tym samym wierszu co cudzysłowy początkowe. Ta zawartość w tym samym wierszu może być pusta lub niepusta. "pusty" jest synonimem "całkowicie pustych znaków".
- Jeśli zawartość w tym samym wierszu nie jest pusta, nie może za nią pojawić się żadna dalsza zawartość. Innymi słowy, literał musi kończyć się taką samą ilością cudzysłowów w tym samym wierszu.
- Jeśli zawartość w tym samym wierszu jest pusta, literał może kontynuować z
new_line
i pewną liczbą kolejnych wierszy zawartości inew_line
s.- Linia zawartości to dowolny tekst z wyjątkiem
new_line
. - Wiersz zawartości może zawierać wiele wystąpień
raw_interpolation
w dowolnej pozycji.raw_interpolation
musi zaczynać się od równej liczby otwartych nawiasów klamrowych ({
) jak liczby znaków dolara na początku literału. - Jeśli spacje wcięcia nie są puste,
raw_interpolation
nie może bezpośrednio nastąpić ponew_line
. -
raw_interpolation
będzie przestrzegać normalnych zasad określonych w §12.8.3. Każdaraw_interpolation
musi kończyć się taką samą liczbą zamykających nawiasów klamrowych (}
), jak liczba znaków dolara oraz otwartych nawiasów klamrowych. - Każdy
interpolation
może zawierać nowe wiersze w taki sam sposób jakinterpolation
w normalnymverbatim_string_literal
(@""
). - Następnie kończy się
new_line
pewną liczbą (prawdopodobnie zerową)whitespace
i taką samą liczbą cudzysłowów, od których zaczynał się literał.
- Linia zawartości to dowolny tekst z wyjątkiem
Obliczenie wartości ciągu interpolowanego jest zgodne z tymi samymi regułami co normalny raw_string_literal
z wyjątkiem aktualizacji w celu obsługi wierszy zawierających raw_interpolation
s. Budowanie wartości ciągu odbywa się w taki sam sposób, tylko że otwory interpolacji są zastępowane wartościami, które generują te wyrażenia w czasie wykonywania. Jeśli interpolated_raw_string_literal
zostanie przekonwertowana na FormattableString
, to wartości interpolacji zostaną przekazane w odpowiedniej kolejności do tablicy arguments
w celu FormattableString.Create
. Pozostała część zawartości interpolated_raw_string_literal
po odstępie "wcięcia" została usunięta ze wszystkich wierszy do wygenerowania ciągu format
przekazanego do FormattableString.Create
, z wyjątkiem odpowiednio ponumerowanej zawartości {N}
w każdej lokalizacji, w której wystąpił raw_interpolation
(lub {N,constant}
w przypadku, gdy jego interpolation
ma postać expression ',' constant_expression
).
W powyższej specyfikacji występuje niejednoznaczność. W szczególności, gdy sekcja {
w tekście przylega do {
interpolacji. Na przykład:
var v1 = $$"""
{{{order_number}}}
"""
Można to interpretować jako: {{ {order_number } }}
lub { {{order_number}} }
. Jednak ponieważ poprzednie jest nielegalne (żadne wyrażenie języka C# nie może zaczynać się od {
), byłoby bezcelowe interpretowanie w ten sposób. Tak więc interpretujemy w ten ostatni sposób, gdzie najbardziej wewnętrzne nawiasy klamrowe {
i }
tworzą interpolację, a te najbardziej zewnętrzne tworzą tekst. W przyszłości może to być problem, jeśli język kiedykolwiek będzie obsługiwał jakiekolwiek wyrażenia, które są otoczone nawiasami klamrowymi. Jednak w takim przypadku zaleceniem byłoby napisanie takiego przypadku w następujący sposób: {{({some_new_expression_form})}}
. W tym miejscu nawiasy pomogą oddzielić fragmenty wyrażenia od reszty tekstu dosłownego/interpolacji. Ma to już pierwszeństwo przed tym, jak ternarne wyrażenia warunkowe muszą być zapisane, aby nie powodować konfliktu ze specyfikatorem formatowania/wyrównania interpolacji (np. {(x ? y : z)}
).
Niedociągnięcia
Nieprzetworzone literały ciągu dodają większą złożoność do języka. W wielu celach mamy już wiele formularzy literałów ciągu. Ciągi ""
, @""
i $""
mają już dużą elastyczność i potencjał. Ale wszyscy nie mają sposobu, aby zapewnić nieprzetworzone treści, które nigdy nie potrzebują ucieczki.
Powyższe reguły nie obsługują przypadku 4.a:
- ...
- Opcjonalnie, z dodatkową złożonością, możemy udoskonalić tę wersję, aby stwierdzić, że: dla wszystkich ciągów, które same nie zawierają
new_line
(ale mogą się rozpoczynać lub kończyć od znaku cudzysłowu"
), powinno być możliwe, aby literał ciągu znaków był reprezentowany w pojedynczym wierszu.
- Opcjonalnie, z dodatkową złożonością, możemy udoskonalić tę wersję, aby stwierdzić, że: dla wszystkich ciągów, które same nie zawierają
To dlatego, że nie mamy sposobu, aby wiedzieć, czy cudzysłów początkowy lub końcowy ("
) powinien należeć do zawartości, a nie do samego separatora. Jeśli jest to ważny scenariusz, który chcemy obsługiwać, możemy dodać równoległą konstrukcję '''
, aby towarzyszyć formularzowi """
. W przypadku tej konstrukcji równoległej pojedynczy ciąg liniowy rozpoczynający się i kończący się "
można łatwo zapisywać jako '''"This string starts and ends with quotes"'''
wraz z konstrukcją równoległą """'This string starts and ends with apostrophes'"""
. Może to być również pożądane, aby wesprzeć wizualne oddzielenie cytatów, co mogłoby być przydatne przy osadzaniu języków, które używają jednego znaku cudzysłowu znacznie częściej niż drugiego.
Alternatywy
https://github.com/dotnet/csharplang/discussions/89 obejmuje tu wiele opcji. Alternatywy są liczne, ale oddalają się zbytnio w złożoność i słabą ergonomię. To podejście wybiera prostotę, w której po prostu zwiększasz długość cudzysłowu początkowego/końcowego, dopóki nie ma obaw o konflikt z zawartością ciągu. Umożliwia również, aby kod, który piszesz, był dobrze wcięty, jednocześnie tworząc literał bez wcięcia, który jest tym, czego potrzebuje większość kodu.
Jedną z najbardziej interesujących potencjalnych odmian jest jednak użycie znaczników `
(lub ```
) dla tych surowych literałów łańcuchowych. Może to mieć kilka korzyści:
- Pozwoliłoby to uniknąć wszystkich problemów z ciągami rozpoczynającymi się lub kończącymi się cudzysłowami.
- Wyglądałoby to znajomo na markdown. Chociaż to samo w sobie nie jest dobrym rozwiązaniem, ponieważ użytkownicy mogą oczekiwać interpretacji markdown.
- Pierwotny literał ciągu musiałby zaczynać i kończyć się tylko pojedynczym znakiem w większości przypadków i potrzebowałby tylko wielu w znacznie rzadszym przypadku zawartości zawierającej same znaczniki wsteczne.
- W przyszłości naturalne byłoby rozszerzenie tego o
```xml
, ponownie w sposób podobny do notacji markdown. Chociaż, oczywiście, jest to również prawdą w formularzu"""
.
Ogólnie rzecz biorąc, korzyści netto wydaje się niewielkie. Zgodnie z historią języka C# myślę, że "
powinny nadal być ogranicznikiem string literal
, podobnie jak w przypadku @""
i $""
.
Spotkania projektowe
Otwartych problemów do omówienia Rozwiązane problemy:
- [x] czy powinniśmy mieć formularz z jedną linią? Technicznie moglibyśmy to zrobić bez niego. Oznaczałoby to jednak, że proste ciągi nie zawierające nowego wiersza zawsze miałyby co najmniej trzy wiersze. Myślę, że powinniśmy uznać, że wymuszanie konstrukcji pojedynczej linii, aby były trzyliniowe, tylko po to, by uniknąć ucieczki znaków, jest bardzo trudne.
Decyzja projektowa: Tak, będziemy mieli pojedynczy formularz liniowy.
- [x] czy powinniśmy wymagać, aby wielowierszowe musiały zaczynać się od nowego wiersza? Myślę, że powinniśmy. Daje nam również możliwość wspierania takich rzeczy, jak
"""xml
w przyszłości.
Decyzja projektowa: Tak, wymagamy, aby wielowierszowa linia zaczęła się od nowego wiersza
- [x] czy automatyczne usuwanie wcięcia powinno być wykonywane w ogóle? Myślę, że powinniśmy. To sprawia, że kod wygląda o wiele przyjemniej.
Decyzja projektowa: Tak, nastąpi automatyczne usunięcie wcięcia.
- [x] czy należy ograniczyć łączenie różnych typów białych znaków we wspólnej przestrzeni? Nie sądzę, abyśmy powinni. W rzeczywistości istnieje wspólna strategia wcięcia o nazwie "tabulator do wcięć, spacja do wyrównania". Byłoby to bardzo naturalne, aby użyć tego, aby wyrównać ogranicznik końcowy z ogranicznikiem początkowym w przypadku, gdy ogranicznik początkowy nie zaczyna się na zatrzymaniu karty.
Decyzja projektowa: nie będziemy mieć żadnych ograniczeń dotyczących mieszania białych znaków.
- [x] czy powinniśmy używać czegoś innego dla ogrodzeń?
`
pasuje do składni języka Markdown i oznaczałoby to, że nie zawsze musieliśmy uruchamiać te ciągi z trzema cudzysłowami. Wystarczyłoby tylko jedno z typowych przypadków.
Decyzja projektowa: użyjemy """
- [x] czy należy mieć wymóg, aby ogranicznik miał więcej cudzysłowów niż najdłuższa sekwencja cudzysłowów w wartości ciągu? Technicznie nie jest to wymagane. na przykład:
var v = """
contents"""""
"""
Jest to ciąg z """
jako ogranicznikiem. Kilku członków społeczności stwierdziło, że jest to mylące i powinniśmy wymagać w takim przypadku, aby ogranicznik zawsze miał więcej znaków. Byłoby to wtedy:
var v = """"""
contents"""""
""""""
Decyzja projektowa: Tak, ogranicznik musi być dłuższy niż jakakolwiek sekwencja cudzysłowów w samym ciągu.
C# feature specifications