Tipos de referência internos (Referência de C#)

C# tem muitos tipos de referência internas Eles têm palavras-chave ou operadores que são sinônimos de um tipo na biblioteca do .NET.

O tipo de objeto

O tipo object é um alias de System.Object no .NET. No sistema de tipos unificado do C#, todos os tipos, predefinidos e definidos pelo usuário, tipos de referência e tipos de valor, herdam direta ou indiretamente de System.Object. Você pode atribuir valores de qualquer tipo a variáveis do tipo object. Qualquer variável object pode ser atribuída ao seu valor padrão usando o literal null. Quando uma variável de um tipo de valor é convertida para um objeto, ela é chamada de boxed. Quando uma variável do objeto do tipo object é convertida para um tipo de valor, ela é chamada de unboxed. Para obter mais informações, consulte Conversões boxing e unboxing.

O tipo de cadeia de caracteres

O tipo string representa uma sequência de zero ou mais caracteres Unicode. string é um alias de System.String no .NET.

Embora string seja um tipo de referência, os operadores de igualdade == e != são definidos para comparar os valores de objetos string, não referências. A igualdade baseada em valor torna o teste de igualdade de cadeia de caracteres mais intuitivo. Por exemplo:

string a = "hello";
string b = "h";
// Append to contents of 'b'
b += "ello";
Console.WriteLine(a == b);
Console.WriteLine(object.ReferenceEquals(a, b));

O exemplo anterior mostra "True" e, em seguida, "False" porque os conteúdos das cadeias de caracteres são equivalentes, mas a e b não fazem referência à mesma instância da cadeia de caracteres.

O operador + concatena as cadeias de caracteres:

string a = "good " + "morning";

O código anterior cria um objeto de cadeia de caracteres que contém “good morning”.

Cadeias de caracteres são imutável – o conteúdo de um objeto de cadeia de caracteres não pode ser alterado depois que o objeto é criado. Por exemplo, quando você escreve esse código, o compilador, na verdade, cria um objeto de cadeia de caracteres para manter a nova sequência de caracteres e esse novo objeto é atribuído a b. A memória que tiver sido alocada para b (quando ela continha a cadeia de caracteres "h") será elegível para a coleta de lixo.

string b = "h";
b += "ello";

O operador[] pode ser usado para acesso somente leitura a caracteres individuais de uma cadeia de caracteres. Os valores de índice válidos começam em 0 e devem ser menores do que o comprimento da cadeia de caracteres:

string str = "test";
char x = str[2];  // x = 's';

De maneira semelhante, o operador [] também pode ser usado para iterar em cada caractere em uma cadeia de caracteres:

string str = "test";

for (int i = 0; i < str.Length; i++)
{
  Console.Write(str[i] + " ");
}
// Output: t e s t

Literais de cadeia de caracteres

Literais de cadeia de caracteres são do tipo string e podem ser escritos de duas formas, entre aspas e literalmente.

Literais de cadeia de caracteres brutos estão disponíveis a partir do C# 11. literais de cadeias de caracteres brutos podem conter texto arbitrário sem precisar de sequências de escape. Literais de cadeia de caracteres bruta podem inclluir espaço em branco e novas linhas, aspas embutidas e outros caracteres especiais. Literais de cadeia de caracteres brutos são colocados em um mínimo de três aspas duplas ("""):

"""
This is a multi-line
    string literal with the second line indented.
"""

Você pode até incluir uma sequência de três (ou mais) caracteres de aspas duplas. Se o texto exigir uma sequência inserida de aspas, você iniciará e terminará o literal de cadeia de caracteres bruto com mais aspas, conforme necessário:

"""""
This raw string literal has four """", count them: """" four!
embedded quote characters in a sequence. That's why it starts and ends
with five double quotes.

You could extend this example with as many embedded quotes as needed for your text.
"""""

Literais de cadeia de caracteres brutos normalmente têm as sequências de aspas iniciais e finais em linhas separadas do texto inserido. Literais de cadeia de caracteres brutos de várias linhas dão suporte a cadeias de caracteres que são as próprias cadeias de caracteres entre aspas:

var message = """
"This is a very important message."
""";
Console.WriteLine(message);
// output: "This is a very important message."

Quando as aspas inicial e final estão em linhas separadas, as linhas novas que seguem a cotação de abertura e a citação final não são incluídas no conteúdo final. A sequência de aspas de fechamento dita a coluna mais à esquerda para o literal da cadeia de caracteres. Você pode recuar um literal de cadeia de caracteres bruto para corresponder ao formato de código geral:

var message = """
    "This is a very important message."
    """;
Console.WriteLine(message);
// output: "This is a very important message."
// The leftmost whitespace is not part of the raw string literal

As colunas à direita da sequência de aspas final são preservadas. Esse comportamento permite cadeias de caracteres brutas para formatos de dados como JSON, YAML ou XML, conforme mostrado no exemplo a seguir:

var json= """
    {
        "prop": 0
    }
    """;

O compilador emitirá um erro se qualquer uma das linhas de texto se estender à esquerda da sequência de aspas de fechamento. As sequências de aspas de abertura e fechamento podem estar na mesma linha, desde que o literal da cadeia de caracteres não comece nem termine com um caractere de aspas:

var shortText = """He said "hello!" this morning.""";

Você pode combinar literais de cadeia de caracteres brutos com interpolação de cadeia de caracteres para incluir caracteres de citação e chaves na cadeia de caracteres de saída.

Os literais de cadeia de caracteres entre aspas são colocados entre aspas duplas ("):

"good morning"  // a string literal

Os literais de cadeia de caracteres podem conter qualquer literal de caractere. Sequências de escape são incluídas. O exemplo a seguir usa a sequência de escape \\ de barra invertida, \u0066 para a letra f e \n para a nova linha.

string a = "\\\u0066\n F";
Console.WriteLine(a);
// Output:
// \f
//  F

Observação

O código de escape \udddd (em que dddd é um número de quatro dígitos) representa o caractere Unicode U+dddd. Os códigos de escape Unicode de oito dígitos também são reconhecidos: \Udddddddd.

Os literais de cadeia de caracteres textuais começam com @ e também são colocados entre aspas duplas. Por exemplo:

@"good morning"  // a string literal

A vantagem das cadeias de caracteres textuais é que as sequências de escape não são processadas, o que facilita a escrita: Por exemplo, o texto a seguir corresponde a um nome de arquivo do Windows totalmente qualificado:

@"c:\Docs\Source\a.txt"  // rather than "c:\\Docs\\Source\\a.txt"

Para incluir aspas duplas em uma cadeia de caracteres com@, dobre-a:

@"""Ahoy!"" cried the captain." // "Ahoy!" cried the captain.

Cadeia de caracteres UTF-8 literais

As cadeias de caracteres no .NET são armazenadas usando a codificação UTF-16. UTF-8 é o padrão para protocolos Web e outras bibliotecas importantes. A partir do C# 11, você pode adicionar o sufixo u8 a um literal de string para especificar a codificação UTF-8. Os literais UTF-8 são armazenados como objetos ReadOnlySpan<byte>. O tipo natural de um literal de cadeia de caracteres UTF-8 é ReadOnlySpan<byte>. Usar um literal de cadeia de caracteres UTF-8 cria uma declaração mais clara do que declarar o System.ReadOnlySpan<T> equivalente, conforme mostrado no código a seguir:

ReadOnlySpan<byte> AuthWithTrailingSpace = new byte[] { 0x41, 0x55, 0x54, 0x48, 0x20 };
ReadOnlySpan<byte> AuthStringLiteral = "AUTH "u8;

Para armazenar um literal de cadeia de caracteres UTF-8 como uma matriz, é necessário o uso de ReadOnlySpan<T>.ToArray() para copiar os bytes que contêm o literal para a matriz mutável:

byte[] AuthStringLiteral = "AUTH "u8.ToArray();

Literais de cadeia de caracteres UTF-8 não são constantes de tempo de compilação; são constantes de runtime. Portanto, eles não podem ser usados como o valor padrão para um parâmetro opcional. Literais de cadeia de caracteres UTF-8 não podem ser combinados com interpolação de cadeia de caracteres. Você não pode usar o token $ e o sufixo u8 na mesma expressão de cadeia de caracteres.

O tipo de delegado

A declaração de um tipo de delegado é semelhante a uma assinatura de método. Ela tem um valor retornado e parâmetros de qualquer tipo:

public delegate void MessageDelegate(string message);
public delegate int AnotherDelegate(MyType m, long num);

No .NET, os tipos System.Action e System.Func fornecem definições genéricas para muitos delegados comuns. Você provavelmente não precisa definir novos tipos de delegado personalizado. Em vez disso, é possível criar instâncias dos tipos genéricos fornecidos.

Um delegate é um tipo de referência que pode ser usado para encapsular um método nomeado ou anônimo. Representantes são semelhantes a ponteiros de função em C++. No entanto, os representantes são fortemente tipados e seguros. Para aplicativos de representantes, consulte Representantes e Representantes genéricos. Os representantes são a base dos Eventos. Um delegado pode ser instanciado associando-o a um método nomeado ou anônimo.

O delegado deve ser instanciado com um método ou expressão lambda que tenha um tipo de retorno compatível e parâmetros de entrada. Para obter mais informações sobre o grau de variação permitido na assinatura do método, consulte Variação em representantes. Para uso com métodos anônimos, o delegado e o código a ser associado a ele são declarados juntos.

A combinação de representantes ou a remoção falham com uma exceção de runtime quando os tipos de delegado envolvidos em tempo de execução são diferentes devido à conversão de variante. O seguinte exemplo demonstra uma situação de falha:

Action<string> stringAction = str => {};
Action<object> objectAction = obj => {};
  
// Valid due to implicit reference conversion of
// objectAction to Action<string>, but may fail
// at run time.
Action<string> combination = stringAction + objectAction;

Você pode criar um delegado com o tipo de runtime correto criando um novo objeto delegado. O exemplo a seguir demonstra como essa solução alternativa pode ser aplicada ao exemplo anterior.

Action<string> stringAction = str => {};
Action<object> objectAction = obj => {};
  
// Creates a new delegate instance with a runtime type of Action<string>.
Action<string> wrappedObjectAction = new Action<string>(objectAction);

// The two Action<string> delegate instances can now be combined.
Action<string> combination = stringAction + wrappedObjectAction;

A partir do C# 9, você pode declarar ponteiros de função, que usam sintaxe semelhante. Um ponteiro de função usa a instrução calli em vez de instanciar um tipo de delegado e chamar o método virtual Invoke.

O tipo dinâmico

O tipo dynamic indica que o uso de variável e as referências aos seus membros ignoram a verificação de tipo de tempo de compilação. Em vez disso, essas operações são resolvidas em tempo de execução. O tipo dynamic simplifica o acesso a APIs COM, como as APIs de Automação do Office e também às APIs dinâmicas, como bibliotecas do IronPython e ao DOM (Modelo de Objeto do Documento) HTML.

O tipo dynamic se comporta como o tipo object na maioria das circunstâncias. Em particular, qualquer expressão não nula pode ser convertida no tipo dynamic. O tipo dynamic é diferente de object nas operações que contêm expressões do tipo dynamic não resolvidas ou com tipo verificado pelo compilador. O compilador junta as informações sobre a operação em pacotes e, posteriormente, essas informações são usadas para avaliar a operação em tempo de execução. Como parte do processo, as variáveis do tipo dynamic são compiladas em variáveis do tipo object. Portanto, o tipo dynamic existe somente em tempo de compilação e não em tempo de execução.

O exemplo a seguir compara uma variável do tipo dynamic a uma variável do tipo object. Para verificar o tipo de cada variável no tempo de compilação, coloque o ponteiro do mouse sobre dyn ou obj nas instruções WriteLine. Copie o seguinte código para um editor em que o IntelliSense está disponível. O IntelliSense mostra dinâmico para dyn e objeto para obj.

class Program
{
    static void Main(string[] args)
    {
        dynamic dyn = 1;
        object obj = 1;

        // Rest the mouse pointer over dyn and obj to see their
        // types at compile time.
        System.Console.WriteLine(dyn.GetType());
        System.Console.WriteLine(obj.GetType());
    }
}

As instruções WriteLine exibem os tipos de tempo de execução de dyn e obj. Nesse ponto, ambos têm o mesmo tipo, inteiro. A seguinte saída é produzida:

System.Int32
System.Int32

Para ver a diferença entre dyn e obj em tempo de compilação, adicione as duas linhas a seguir entre as declarações e as instruções WriteLine no exemplo anterior.

dyn = dyn + 3;
obj = obj + 3;

Um erro de compilador será relatado em virtude da tentativa de adição de um inteiro e um objeto à expressão obj + 3. No entanto, nenhum erro será relatado para dyn + 3. A expressão contém dyn não é verificada em tempo de compilação, pois o tipo de dyn é dynamic.

O exemplo a seguir usa dynamic em várias declarações. O método Main também compara a verificação de tipo em tempo de compilação com a verificação de tipo em tempo de execução.

using System;

namespace DynamicExamples
{
    class Program
    {
        static void Main(string[] args)
        {
            ExampleClass ec = new ExampleClass();
            Console.WriteLine(ec.exampleMethod(10));
            Console.WriteLine(ec.exampleMethod("value"));

            // The following line causes a compiler error because exampleMethod
            // takes only one argument.
            //Console.WriteLine(ec.exampleMethod(10, 4));

            dynamic dynamic_ec = new ExampleClass();
            Console.WriteLine(dynamic_ec.exampleMethod(10));

            // Because dynamic_ec is dynamic, the following call to exampleMethod
            // with two arguments does not produce an error at compile time.
            // However, it does cause a run-time error.
            //Console.WriteLine(dynamic_ec.exampleMethod(10, 4));
        }
    }

    class ExampleClass
    {
        static dynamic field;
        dynamic prop { get; set; }

        public dynamic exampleMethod(dynamic d)
        {
            dynamic local = "Local variable";
            int two = 2;

            if (d is int)
            {
                return local;
            }
            else
            {
                return two;
            }
        }
    }
}
// Results:
// Local variable
// 2
// Local variable

Confira também