Compartilhar via


Literal de cadeia de caracteres bruto

Nota

Este artigo é uma especificação de recurso. A especificação serve como o documento de design para o recurso. Ele inclui alterações de especificação propostas, juntamente com as informações necessárias durante o design e o desenvolvimento do recurso. Esses artigos são publicados até que as alterações de especificação propostas sejam finalizadas e incorporadas na especificação ECMA atual.

Pode haver algumas discrepâncias entre a especificação do recurso e a implementação concluída. Essas diferenças são capturadas nas notas pertinentes da reunião de design de idioma (LDM).

Você pode saber mais sobre o processo de adoção de especificações de feature no padrão de linguagem C# no artigo sobre as especificações e.

Resumo

Permita uma nova forma de literal de cadeia de caracteres que começa com um mínimo de três caracteres """ (mas sem máximo), opcionalmente seguido por um new_line, o conteúdo da cadeia de caracteres e, em seguida, termina com o mesmo número de aspas com que o literal começou. Por exemplo:

var xml = """
          <element attr="content"/>
          """;

Como o conteúdo aninhado pode querer usar """, os delimitadores iniciais/finais podem ser mais longos assim:

var xml = """"
          Ok to use """ here
          """";

Para facilitar a leitura do texto e permitir a indentação que os desenvolvedores preferem no código, esses literais de string removerão naturalmente a indentação especificada na última linha ao gerar o valor literal final. Por exemplo, um literal do formulário:

var xml = """
          <element attr="content">
            <body>
            </body>
          </element>
          """;

Terá o conteúdo:

<element attr="content">
  <body>
  </body>
</element>

Isso permite que o código pareça natural, enquanto ainda produz literais desejados e evita custos de runtime se isso exigir o uso de rotinas especializadas de manipulação de cadeia de caracteres.

Se o comportamento de indentação não for desejado, também pode ser facilmente desabilitado assim:

var xml = """
          <element attr="content">
            <body>
            </body>
          </element>
""";

Também há suporte para um formulário de linha única. Começa com um mínimo de três caracteres """ (mas sem máximo), o conteúdo da cadeia de caracteres (que não pode conter nenhum caractere new_line) e termina com o mesmo número de aspas com as quais o literal começou. Por exemplo:

var xml = """<summary><element attr="content"/></summary>""";

Também há suporte para cadeias de caracteres brutas interpoladas. Nesse caso, a cadeia de caracteres especifica o número de chaves necessárias para iniciar uma interpolação (determinada pelo número de cifrões presentes no início do literal). Qualquer sequência de chaves com menos chaves do que isso é tratada apenas como conteúdo. Por exemplo:

var json = $$"""
             {
                "summary": "text",
                "length" : {{value.Length}},
             };
             """;

Motivação

O C# não tem uma maneira geral de criar literais de cadeia de caracteres simples que possam conter efetivamente qualquer texto arbitrário. Todos os formulários literais de cadeia de caracteres C# hoje precisam de alguma forma de escape caso o conteúdo use algum caractere especial (sempre se um delimitador for usado). Isso evita facilmente a existência de literais contendo outros idiomas (por exemplo, um literal XML, HTML ou JSON).

Todas as abordagens atuais para formar esses literais em C# hoje sempre forçam o usuário a escapar manualmente do conteúdo. A edição nesse ponto pode ser muito irritante, pois o escape não pode ser evitado e deve ser tratado sempre que surge no conteúdo. Isso é particularmente doloroso para regexes, especialmente quando contêm aspas ou barras invertidas. Mesmo com uma cadeia de caracteres de texto (@""), as próprias aspas devem ser escapadas, levando a uma mistura intercalada de C# e regex. { e } são igualmente frustrantes em cadeias de caracteres interpoladas ($"").

O ponto crucial do problema é que todas as nossas cadeias de caracteres têm um delimitador de início/término fixo. Desde que esse seja o caso, sempre precisaremos de um mecanismo de escape, pois o conteúdo da cadeia de caracteres pode precisar especificar esse delimitador final. Isso é particularmente problemático, pois esse delimitador " é extremamente comum em muitos idiomas.

Para resolver isso, essa proposta permite delimitadores de início e término flexíveis para que eles sempre possam ser feitos de forma que não entrem em conflito com o conteúdo da cadeia de caracteres.

Objetivos

  1. Forneça um mecanismo que permitirá que todos os valores de cadeia de caracteres sejam fornecidos pelo usuário sem a necessidade de nenhuma sequência de escape. Como todas as cadeias de caracteres devem ser representáveis sem sequências de escape, sempre será possível que o usuário especifique delimitadores que terão a garantia de não colidir com nenhum conteúdo de texto.
  2. Suporte a interpolações da mesma forma. Como acima, como todas as cadeias de caracteres devem ser representáveis sem escapes, sempre será possível que o usuário especifique um delimitador interpolation que terá não vai colidir com nenhum conteúdo de texto. É importante ressaltar que as linguagens que usam caracteres delimitadores de interpolação ({ e }) devem ser considerados de primeira classe e não devem ser difíceis de usar.
  3. Literais de cadeia de caracteres de várias linhas devem parecer agradáveis no código e não devem fazer o recuo na unidade de compilação parecer estranho. É importante ressaltar que os valores literais que não têm recuo não devem ser forçados a ocupar a primeira coluna do arquivo, pois isso pode quebrar o fluxo de código e parecerão desalinhados com o restante do código que o cerca.
    • Esse comportamento deve ser fácil de substituir, mantendo os literais claros e fáceis de ler.
  4. Para todas as cadeias de caracteres que não contêm new_line nem começam ou terminam com um caractere de aspa ("), deve ser possível representar o próprio literal de cadeia de caracteres em uma única linha.
    • Se desejar, com complexidade extra, poderíamos refinar isso para afirmar que: para todas as cadeias de caracteres que não contêm em si mesmas um caractere new_line (mas podem iniciar ou terminar com um caractere de aspa "), deve ser possível representar o literal de cadeia de caracteres em uma única linha. Para obter mais detalhes, consulte a proposta expandida na seção Drawbacks.

Design detalhado (caso de não interpolação)

Adicionaremos uma nova produção string_literal com a seguinte forma:

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>
    ;

O delimitador final para um raw_string_literal deve corresponder ao delimitador inicial. Portanto, se o delimitador inicial é """"" o delimitador final deve ser esse também.

A gramática acima para um raw_string_literal deve ser interpretada como:

  1. Ele começa com pelo menos três aspas (mas sem limite superior entre aspas).
  2. Em seguida, continua com conteúdo na mesma linha que as aspas de abertura. Esses conteúdos na mesma linha podem estar em branco ou não em branco. 'blank' é sinônimo de "espaço em branco inteiro".
  3. Se o conteúdo nessa mesma linha não estiver em branco, nenhum conteúdo adicional poderá seguir. Em outras palavras, o literal deve terminar com o mesmo número de aspas nessa mesma linha.
  4. Se o conteúdo na mesma linha estiver em branco, o literal poderá continuar com um new_line e algumas linhas de conteúdo subsequentes e new_lines.
    • Uma linha de conteúdo é qualquer texto, exceto um new_line.
    • Em seguida, termina com um new_line seguido por algum número (possivelmente zero) de whitespace e o mesmo número de aspas do que o literal começou.

Valor literal da cadeia de caracteres bruta

As partes entre a raw_string_literal_delimiter inicial e final são usadas para formar o valor da raw_string_literal da seguinte maneira:

  • No caso de single_line_raw_string_literal, o valor do literal será exatamente o conteúdo entre o raw_string_literal_delimiter inicial e final.
  • No caso de multi_line_raw_string_literal o whitespace* new_line inicial e o new_line whitespace* final não faz parte do valor da cadeia de caracteres. No entanto, a porção final whitespace* que precede o terminal raw_string_literal_delimiter é considerada o "espaço em branco de indentação" e afetará a forma como as outras linhas são interpretadas.
  • Para obter o valor final, a sequência de (raw_content | new_line)* é percorrida e o seguinte é executado:
    • Se for um new_line o conteúdo do new_line será adicionado ao valor final da cadeia de caracteres.
    • Se não for um raw_content 'em branco' (ou seja, not_new_line+ contém um caractere nãowhitespace):
      • o "espaço em branco de recuo" deve ser um prefixo do raw_content. Caso contrário, é um erro.
      • o "espaço em branco de recuo" é removido do início do raw_content e o restante é adicionado ao valor final da cadeia de caracteres.
    • Se for um raw_content "em branco" (ou seja, not_new_line+ é totalmente whitespace):
      • o "espaço em branco de recuo" deve ser um prefixo do raw_content ou o raw_content deve ser um prefixo do "espaço em branco de recuo". Caso contrário, é um erro.
      • a maior parte do "espaço em branco de recuo" é removida do início do raw_content e qualquer restante é adicionado ao valor final da cadeia de caracteres.

Esclarecimentos:

  1. Uma single_line_raw_string_literal não é capaz de representar uma cadeia de caracteres com um valor new_line nela. Um single_line_raw_string_literal não participa da remoção de "espaços em branco de recuo". O valor consiste sempre nos caracteres exatos entre os delimitadores inicial e final.

  2. Como um multi_line_raw_string_literal ignora o new_line final da última linha de conteúdo, o seguinte representa uma cadeia de caracteres sem new_line inicial e sem terminação new_line

var v1 = """
         This is the entire content of the string.
         """;

Isso mantém a simetria com como o new_line inicial é ignorado e também fornece uma maneira uniforme de garantir que o "espaço em branco de recuo" sempre possa ser ajustado. Para representar uma cadeia de caracteres com um terminal new_line uma linha extra deve ser fornecida da seguinte maneira:

var v1 = """
         This string ends with a new line.

         """;
  1. Um single_line_raw_string_literal não pode representar um valor de cadeia de caracteres que começa ou termina com uma aspa ("), embora um aumento para essa proposta seja fornecido na seção Drawbacks que mostra como isso poderia ser suportado.

  2. Um multi_line_raw_string_literal começa com whitespace* new_line após o raw_string_literal_delimiter inicial. Esse conteúdo após o delimitador é totalmente ignorado e não é usado de forma alguma ao determinar o valor da cadeia de caracteres. Isso permite que um mecanismo especifique um raw_string_literal cujo conteúdo começa com um caractere " em si. Por exemplo:

var v1 = """
         "The content of this string starts with a quote
         """;
  1. Um raw_string_literal também pode representar conteúdos que terminem com uma aspa ("). Isso é permitido, pois o delimitador de encerramento deve estar em sua própria linha. Por exemplo:
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""
         """;
  1. O requisito de que um raw_content "em branco" seja um prefixo do "espaço em branco de recuo" ou que o "espaço em branco de recuo" seja um prefixo dele ajuda a garantir que cenários confusos com espaço em branco misto não ocorram, especialmente porque não estaria claro o que deveria acontecer com essa linha. Por exemplo, o caso a seguir é ilegal:
var v1 = """
         Start
<tab>
         End
         """;
  1. Aqui, o "espaço em branco de recuo" tem nove caracteres de espaço, mas o raw_content "em branco" não começa com um prefixo. Não há uma resposta clara sobre como a linha de <tab> deve ser tratada. Deve ser ignorado? Deve ser igual a .........<tab>? Como tal, torná-lo ilegal parece o mais claro para evitar confusão.

  2. No entanto, os seguintes casos são legais e representam a mesma cadeia de caracteres:

var v1 = """
         Start
<four spaces>
         End
         """;
var v1 = """
         Start
<nine spaces>
         End
         """;

Nesses dois casos, o "espaço em branco de recuo" será de nove espaços. E em ambos os casos, removeremos o máximo possível desse prefixo, fazendo com que o raw_content 'em branco' em cada caso fique vazio (sem contar cada new_line). Isso permite que os usuários não precisem ver e potencialmente se preocupar com o espaço em branco nessas linhas quando copiarem/colarem ou editarem essas linhas.

  1. No caso, porém, de:
var v1 = """
         Start
<ten spaces>
         End
         """;

O "espaço em branco de recuo" ainda será de nove espaços. Aqui, no entanto, removeremos o máximo possível do "espaço em branco de recuo", e o raw_content "em branco" vai adicionar um único espaço ao conteúdo final. Isso permite casos em que o conteúdo precisa de espaço em branco nessas linhas que devem ser preservadas.

  1. Tecnicamente, o seguinte não é legal:
var v1 = """
         """;

Isso ocorre porque o início da cadeia de caracteres bruta deve ter um new_line (o que ele faz), mas o final deve ter um new_line também (o que não tem). O raw_string_literal legal mínimo é:

var v1 = """

         """;

No entanto, essa cadeia de caracteres é decididamente desinteressante, pois é equivalente a "".

Exemplos de recuo

O algoritmo de "espaço em branco de recuo" pode ser visualizado em várias entradas desta maneira. Os exemplos a seguir usam o caractere de barra vertical | para ilustrar a primeira coluna na cadeia de caracteres bruta resultante:

Exemplo 1 – Caso padrão

var xml = """
          <element attr="content">
            <body>
            </body>
          </element>
          """;

é interpretado como

var xml = """
          |<element attr="content">
          |  <body>
          |  </body>
          |</element>
           """;

Exemplo 2 – Delimitador final na mesma linha que o conteúdo.

var xml = """
          <element attr="content">
            <body>
            </body>
          </element>""";

Isso é ilegal. A última linha de conteúdo deve terminar com um new_line.

Exemplo 3 – Delimitador final antes do delimitador inicial

var xml = """
          <element attr="content">
            <body>
            </body>
          </element>
""";

é interpretado como

var xml = """
|          <element attr="content">
|            <body>
|            </body>
|          </element>
""";

Exemplo 4 – Delimitador final depois do delimitador inicial

var xml = """
          <element attr="content">
            <body>
            </body>
          </element>
              """;

Isso é ilegal. As linhas de conteúdo devem começar com o "espaço em branco de recuo"

Exemplo 5 – Linha vazia em branco

var xml = """
          <element attr="content">
            <body>
            </body>

          </element>
          """;

é interpretado como

var xml = """
          |<element attr="content">
          |  <body>
          |  </body>
          |
          |</element>
           """;

Exemplo 6 – linha em branco com menos espaço em branco do que o prefixo (os ponto representam espaços)

var xml = """
          <element attr="content">
            <body>
            </body>
....
          </element>
          """;

é interpretado como

var xml = """
          |<element attr="content">
          |  <body>
          |  </body>
          |
          |</element>
           """;

Exemplo 7 – linha em branco com mais espaço em branco do que prefixo (os ponto representam espaços)

var xml = """
          <element attr="content">
            <body>
            </body>
..............
          </element>
          """;

é interpretado como

var xml = """
          |<element attr="content">
          |  <body>
          |  </body>
          |....
          |</element>
           """;

Design detalhado (caso de interpolação)

Interpolações em cadeias de caracteres interpoladas normais (por exemplo, $"...") têm suporte hoje por meio do uso do caractere { para iniciar um interpolation e o uso de uma sequência de escape {{ para inserir um caractere de chave de abertura real. O uso desse mesmo mecanismo violaria as metas '1' e '2' desta proposta. Linguagens que têm { como um caractere principal (exemplos como JavaScript, JSON, Regex e até mesmo C# integrado) agora precisariam de escape, desfazendo a finalidade de literais de cadeia de caracteres brutos.

Para dar suporte a interpolações, introduzimos as cadeias de caracteres interpoladas de uma forma diferente da normal $". Especificamente, um interpolated_raw_string_literal começará com um certo número de caracteres $. A contagem desses indica quantos caracteres { (e }) são necessários no conteúdo do literal para delimitar o interpolation. É importante ressaltar que continua não havendo um mecanismo de escape para chaves. Em vez disso, assim como com aspas ("), o literal sempre pode especificar delimitadores para as interpolações que certamente não colidem com nenhum dos demais conteúdos da cadeia de caracteres. Por exemplo, um literal JSON que contém os orifícios de interpolação pode ser escrito da seguinte maneira:

var v1 = $$"""
         {
            "orders": 
            [
                { "number": {{order_number}} }
            ]
         }
         """

Aqui, o {{...}} corresponde à contagem obrigatória de dois colchetes especificada pelo prefixo do delimitador $$. No caso de um único $, significa que a interpolação é especificada da mesma forma que {...} em literais de cadeia de caracteres interpolados normais. Importante: isso significa que um literal interpolado com os caracteres N$ pode ter uma sequência de chaves 2*N-1 (do mesmo tipo em uma linha). As últimas N chaves iniciarão (ou encerrarão) uma interpolação, e as N-1 chaves restantes serão apenas conteúdo. Por exemplo:

var v1 = $$"""X{{{1+1}}}Z""";

Nesse caso, os dois parênteses internos {{ e }} pertencem à interpolação, e os parênteses singulares externos são apenas conteúdo. Portanto, a cadeia de caracteres acima é equivalente ao conteúdo X{2}Z. Ter 2*N chaves (ou mais) é sempre um erro. Para ter sequências mais longas de chaves como conteúdo, o número de caracteres $ deve ser aumentado conforme necessário.

Literais de cadeia de caracteres brutos interpolados são definidos como:

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.
    ;

O acima é semelhante à definição de raw_string_literal mas com algumas diferenças importantes. Um interpolated_raw_string_literal deve ser interpretado como:

  1. Começa com pelo menos um sinal de dólar (mas sem limite superior) e, em seguida, três aspas (também sem limite superior).
  2. Em seguida, continua com conteúdo na mesma linha que as aspas de abertura. Esse conteúdo na mesma linha pode estar em branco ou não em branco. 'blank' é sinônimo de "espaço em branco inteiro".
  3. Se o conteúdo dessa mesma linha não estiver em branco, nenhum conteúdo adicional poderá seguir. Em outras palavras, o literal deve terminar com o mesmo número de aspas nessa mesma linha.
  4. Se o conteúdo na mesma linha estiver em branco, o literal poderá continuar com um new_line e algumas linhas de conteúdo subsequentes e new_lines.
    • Uma linha de conteúdo é qualquer texto, exceto um new_line.
    • Uma linha de conteúdo pode conter várias ocorrências de raw_interpolation em qualquer posição. O raw_interpolation deve começar com um número de chaves de abertura ({) igual ao número de cifrões no início do literal.
    • Se o "espaço em branco de recuo" não estiver vazio, um raw_interpolation não poderá vir imediatamente após um new_line.
    • O raw_interpolation seguirá as regras normais especificadas em §12.8.3. Qualquer raw_interpolation deve terminar com o mesmo número de chaves de fechamento (}) que o número de cifrões e chaves de abertura.
    • Qualquer interpolation pode conter novas linhas dentro da mesma maneira que uma interpolation em um verbatim_string_literal normal (@"").
    • Em seguida, termina com um new_line seguido por algum número (possivelmente zero) de whitespace e o mesmo número de aspas do que o literal começou.

A computação do valor da string interpolada segue as mesmas regras de uma raw_string_literal normal, mas foi atualizada para lidar com linhas contendo raw_interpolations. A criação do valor da string ocorre da mesma forma, apenas com os orifícios de interpolação substituídos pelos valores que essas expressões produzem em tempo de execução. Se o interpolated_raw_string_literal for convertido em um FormattableString, os valores das interpolações serão passados em sua respectiva ordem para a matriz arguments para FormattableString.Create. O restante do conteúdo do interpolated_raw_string_literalapós o "espaço em branco de recuo" removido de todas as linhas será usado para gerar a cadeia de caracteres format passada para FormattableString.Create, exceto com conteúdo de {N} numerado adequadamente em cada local onde ocorreu um raw_interpolation (ou {N,constant} no caso, se o interpolation for da forma expression ',' constant_expression).

Há uma ambiguidade na especificação acima. Especificamente quando uma seção de { no texto e { em uma interpolação estão adjacentes. Por exemplo:

var v1 = $$"""
         {{{order_number}}}
         """

Isso pode ser interpretado como: {{ {order_number } }} ou { {{order_number}} }. No entanto, como o primeiro é ilegal (nenhuma expressão C# poderia começar com {) seria inútil interpretar dessa forma. Portanto, interpretamos da última forma, em que as chaves { e } mais internas formam a interpolação e as mais externas formam o texto. No futuro, isso poderá ser um problema se a linguagem der suporte a qualquer expressão que esteja cercada por chaves. No entanto, nesse caso, a recomendação seria escrever um caso como esse: {{({some_new_expression_form})}}. Aqui, parênteses ajudariam a designar a parte da expressão do restante do literal/da interpolação. Isso já tem precedência com a forma como expressões condicionais ternárias precisam ser encapsuladas para não entrar em conflito com o especificador de formatação/alinhamento de uma interpolação (por exemplo, {(x ? y : z)}).

Inconvenientes

Literais de cadeia de caracteres brutos adicionam mais complexidade à linguagem. Temos muitas formas de literais de cadeia de caracteres disponíveis para inúmeras finalidades. Cadeias de caracteres "", @"" e $"" já têm muita capacidade e flexibilidade. Mas nenhuma pode fornecer conteúdo bruto que nunca precisa de escape.

As regras acima não dão suporte ao caso de 4.a:

  1. ...
    • Se desejar, com complexidade extra, poderíamos refinar isso para afirmar que: para todas as cadeias de caracteres que não contêm em si mesmas um caractere new_line (mas podem iniciar ou terminar com um caractere de aspa "), deve ser possível representar o literal de cadeia de caracteres em uma única linha.

Isso porque não temos meios de saber que uma aspa inicial ou final (") deve pertencer ao conteúdo e não ao delimitador em si. Se esse for um cenário importante que desejamos dar suporte, porém, podemos adicionar uma construção ''' paralela para acompanhar o formulário """. Com esse constructo paralelo, uma única cadeia de caracteres de linha que inicia e termina com " pode ser gravada facilmente como '''"This string starts and ends with quotes"''' juntamente com o constructo paralelo """'This string starts and ends with apostrophes'""". Isso também pode ser desejável para dar suporte à separação visual dos caracteres de aspas, o que pode ser útil ao integrar idiomas que usam principalmente um caractere de aspas muito mais que o outro.

Alternativas

https://github.com/dotnet/csharplang/discussions/89 abrange várias opções aqui. As alternativas são numerosas, mas tendem a ser complexas demais e pouco ergonômicas. Essa abordagem opta pela simplicidade em que você continua aumentando o comprimento das aspas de início/término até que não haja preocupação com um conflito com o conteúdo da cadeia de caracteres. Isso também permite que o código que você escreve pareça bem recuado, mesmo ainda produzindo um literal recuado que é o que a maioria dos códigos deseja.

Uma das variações em potencial mais interessantes, porém, é o uso de limites ` (ou ```) para esses literais de cadeia de caracteres brutos. Isso teria vários benefícios:

  1. Isso evitaria todos os problemas com cadeias de caracteres começando ou terminando com aspas.
  2. Pareceria familiar para markdown. Embora isso em si não seja uma coisa boa, pois os usuários podem esperar uma interpretação do markdown.
  3. Um literal de cadeia de caracteres bruto só teria que começar e terminar com um único caractere na maioria dos casos, e só precisaria de vários no caso muito mais raro de conteúdo que contiver acento grave.
  4. Seria natural estender isso no futuro com ```xml, novamente semelhante ao markdown. No entanto, é claro, isso também é verdade na forma """.

No geral, porém, o benefício líquido aqui parece pequeno. De acordo com a história do C#, acho que " deve continuar a ser o delimitador string literal, assim como é para @"" e $"".

Reuniões de design

Problemas abertos para discussão Problemas resolvidos:

  • [x] devemos ter um formulário de linha única? Tecnicamente, poderíamos fazer sem ele. Mas isso significaria que cadeias de caracteres simples que não contêm uma nova linha sempre levariam pelo menos três linhas. Acho que exigir transformações de construções em linha única para linhas de três é muito pesado, apenas para evitar a necessidade de escape.

Decisão de design: Sim, teremos um formulário de linha única.

  • [x] Devemos exigir que várias linhas comecem com uma nova linha? Acho que deveríamos. Isso também nos dá a capacidade de apoiar coisas como """xml no futuro.

Decisão de design: sim, exigiremos que várias linhas comecem com uma nova linha

  • [x] o recuo automático deve ser feito? Acho que deveríamos. Faz com que o código pareça muito mais agradável.

Decisão de design: sim, o recuo automático será feito.

  • [x] devemos restringir o espaço em branco comum da combinação de tipos de espaço em branco? Acho que não deveríamos. De fato, há uma estratégia comum de recuo chamada "tabulação para recuo, espaço para alinhamento". Seria muito natural usar isso para alinhar o delimitador final com o delimitador de início quando o delimitador de início não começa em uma parada de tabulação.

Decisão de design: não teremos nenhuma restrição na combinação de espaço em branco.

  • [x] devemos usar outra coisa para as cercas? ` corresponderia à sintaxe markdown e significaria que nem sempre precisamos iniciar essas cadeias de caracteres com três aspas. Apenas um seria suficiente para o caso comum.

Decisão de design: usaremos """

  • [x] devemos ter um requisito de que o delimitador tenha mais aspas do que a sequência mais longa de aspas no valor da cadeia de caracteres? Tecnicamente, não é necessário. por exemplo:
var v = """
        contents"""""
        """

Essa é uma cadeia de caracteres com """ como delimitador. Vários membros da comunidade afirmaram que isso é confuso e devemos exigir, em um caso como este, que o delimitador sempre tenha mais caracteres. Isso seria então:

var v = """"""
        contents"""""
        """"""

Decisão de design: Sim, o delimitador deve ser maior do que qualquer sequência de aspas na própria cadeia de caracteres.