Os mecanismos para formatar strings em C#/.NET são muitos e variados: desde a simples concatenação de objetos, passando pelas chamadas ao método object.ToString() substituído, até à utilização de ICustomFormatter (não abordado neste exercício).
Os dois mecanismos mais comuns para formatar strings são a interpolação de strings e String.Format(). A classe StringBuilder (cross-ref-tba) também pode ser utilizada para construir uma string quando há complexidade envolvida, como várias linhas.
ToString()
System.Object(), do qual todas as classes e estruturas herdam, tem um método ToString(). Por exemplo, new DateTime(2019, 5, 23).ToString() produz "05/23/2019 00:00:00" (numa thread com a cultura dos EUA - vê abaixo). Há situações, como a concatenação de strings, em que este método ToString() predefinido pode ser invocado implicitamente; "" + new DateTime(2019, 5, 23) dá o mesmo resultado.
Além do método ToString() predefinido, os tipos em que a formatação é relevante têm sobrecargas que recebem uma format string ou até um fornecedor de formato. Na biblioteca de classes base (BCL), estes tipos são, em particular, os números, as datas, as enumerações e os GUIDs.
String.Format() recebe uma string (designada na documentação por formato composto) composta por texto fixo e marcadores de posição (conhecidos na documentação como itens de formatação) e um número variável de argumentos. O valor devolvido resolve cada item de formatação com o argumento correspondente e combina os valores resolvidos com o texto fixo.
string.Format("I had {0} bitcoins on {1}, the day I forgot my password.", 55.5, new DateTime(2010, 2, 25))
// => "I had 55.5 bitcoins on 2/25/2010 00:00:00, the day I forgot my password." - US settings
Format() pode ser uma escolha melhor do que a interpolação quando o formato é utilizado em várias expressões como uma espécie de modelo, ou quando incorporar o formato e as expressões a formatar se torna demasiado complicado, como acontece quando estão envolvidas [strings verbatim][verbatim-strings].
Este mecanismo é tecnicamente conhecido como formatação composta.
Neste artigo encontras uma lista mais completa de métodos que produzem strings e que tiram partido da formatação composta.
As strings interpoladas têm o prefixo $ e incluem expressões de tempo de execução encerradas entre chavetas. O item de formatação tem a seguinte sintaxe: $"{<interpolationExpression>[,<alignment>][:<formatString>]}". Dispensam a necessidade de uma lista separada de argumentos. O resultado é funcionalmente equivalente ao mecanismo String.Format().
A expressão pode conter qualquer coisa que esteja no âmbito. O alinhamento é o comprimento do "campo" onde o texto fica. Se o alinhamento for positivo, o texto é preenchido com espaços à esquerda; se for negativo, o preenchimento fica à direita do texto.
var loadsOf = 55.5;
var thatDay = new DateTime(2010, 2, 25);
$"I had {loadsOf} bitcoins on {thatDay}, the day I forgot my password."
// => "I had 55.5 bitcoins on 2/25/2010 00:00:00, the day I forgot my password." - US settings
O texto entre chavetas, marcadores de posição no caso do formato composto e expressões interpoladas no caso da interpolação de strings, é conhecido como item de formatação.
Um item de formatação pode ser composto por até 3 partes. A primeira é a expressão ou o marcador de posição do argumento, obrigatória, como se vê no exemplo de código acima. Além disso, há um alinhamento opcional (introduzido por uma vírgula, ",") e uma string de formatação opcional (introduzida por dois pontos, ":").
{<interpolationExpression>[,<alignment>][:<formatString>]}
O alinhamento especifica o comprimento do "campo" onde o texto é colocado, preenchido à esquerda com espaços se o alinhamento for positivo, ou à direita se for negativo.
A string de formatação especifica a forma do texto de saída, como por exemplo se devem ser incluídos separadores de milhares num número, ou se deve ser produzida apenas a parte da data de um objeto DateTime.
O código seguinte ilustra a apresentação da parte da data de um objeto DateTime e de um número de vírgula flutuante em forma exponencial.
var loadsOf = 55.5;
var thatDay = new DateTime(2010, 2, 25);
$"I had {loadsOf:E} bitcoins on {thatDay:d}, the day I forgot my password."
// => I had 5.550000E+001 bitcoins on 02/25/2010, the day I forgot my password. - US settings
string.Format(
"I had {0:E} bitcoins on {1:d}, the day I forgot my password.",
loadsOf, thatDay)
// => I had 5.550000E+001 bitcoins on 02/25/2010, the day I forgot my password. - US settings
Existe formatação padrão e personalizada tanto para números como para datas. Não há uma diferença essencial entre personalizada e padrão, exceto que tens a oportunidade de compor strings de formatação personalizadas a partir de caracteres de formatação. "custom", neste contexto, não tem nada a ver com a interface ICustomFormatter, que é utilizada quando desenvolves os teus próprios formatadores personalizados.
A biblioteca de classes base (BCL) fornece 2 formatadores, DateTimeFormatInfo e NumberFormatInfo, e 6 grupos de strings de formatação.
As várias listas de strings de formatação são as seguintes:
As strings de formatação de Enum e GUID podem ser classificadas como padrão. Embora enum e GUID tenham sobrecargas de ToString() que recebem um IFormatProvider, não é claro que façam alguma coisa.
Na biblioteca, é feito um esforço para incutir alguma consistência nas strings de formatação (para além do facto de serem representadas como strings). Essa procura de consistência encontra-se nas strings padrão. Na realidade, enquanto programador, raramente te preocupas com a diferença entre strings padrão e personalizadas. Embora seja uma boa ideia seguir as strings padrão existentes, ao implementar formatadores para as tuas próprias classes, quando as tuas classes o justifiquem, podes praticamente ignorar a diferença.
Cada thread de execução tem uma cultura predefinida, Thread.CurrentThread.CurrentCulture, encapsulada numa instância de CultureInfo. A cultura da thread determina como as datas e os números são formatados por predefinição, tendo em conta variações regionais como a diferença de formato de data convencional entre o Reino Unido DD/MM/YYYY e os EUA MM/DD/YYYY.
CultureInfo implementa a interface IFormatProvider, que pode ser passada a certas sobrecargas de String.Format(). Isto pode ser usado para substituir a cultura da thread.