Встроенные ссылочные типы (справочник по C#)

В C# есть множество встроенных ссылочных типов. У них есть ключевые слова или операторы, которые являются синонимами типа в библиотеке .NET.

Тип object

Тип object является псевдонимом System.Object в .NET. В унифицированной системе типов C# все типы, стандартные и определяемые пользователем, ссылочные типы и типы значений напрямую или косвенно наследуются из System.Object. Переменным типа object можно назначать значения любого типа. Любой переменной object можно назначить значение по умолчанию с помощью литерала null. Если переменная типа значения преобразуется в объект, она считается упакованой. Если переменная типа object преобразуется в тип значения, она считается распаковкой. Дополнительные сведения см. в разделе Упаковка-преобразование и распаковка-преобразование.

Тип string

Тип string представляет последовательность, состоящую из нуля или более символов в кодировке Юникод. string является псевдонимом для System.String в .NET.

Несмотря на то что string представляет собой ссылочный тип, операторы равенства == и != по определению сравнивают не ссылки, а значения объектов string. Равенство на основе значений делает тестирование на равенство строк более интуитивно понятным. Пример:

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

В предыдущем примере отображается значение True, а затем "False", так как содержимое строк эквивалентно, но ab не ссылается на тот же экземпляр строки.

Оператор + объединяет строки:

string a = "good " + "morning";

Приведенный выше код создает строковый объект, содержащий "доброе утро".

Строки являются неизменяемыми— содержимое строкового объекта невозможно изменить после создания объекта. Например, при написании кода компилятор фактически создает новый строковый объект для хранения новой последовательности символов, а затем этот новый объект назначается b. Память, выделенная для b (если он содержит строку h), затем доступна для сборки мусора.

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

Оператор[] можно использовать для доступа только для чтения к отдельным символам строки. Допустимые значения индекса начинаются с 0 и должны быть меньше, чем длина строки:

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

Также оператор [] можно использовать для итерации каждого символа в строке:

string str = "test";

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

Строковые литералы

Строковые литералы имеют тип string и могут быть написаны в трех формах, необработанных, кавычках и verbatim.

Необработанные строковые литералы доступны начиная с C# 11. Необработанные строковые литералы могут содержать произвольный текст, не требуя escape-последовательностей. Необработанные строковые литералы могут включать пробелы и новые строки, внедренные кавычки и другие специальные символы. Необработанные строковые литералы заключены как минимум в три двойные кавычки (""").

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

Можно даже включить последовательность из трех (или более) символов двойной кавычки. Если тексту требуется внедренная последовательность кавычек, вы начинаете и заканчиваете необработанный строковый литерал с дополнительными кавычками по мере необходимости:

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

Необработанные строковые литералы обычно имеют начальные и конечные последовательности кавычек в отдельных строках от внедренного текста. Многострочный необработанные строковые литералы поддерживают строки, которые сами являются строками с кавычками:

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

Когда начальные и конечные кавычки находятся в отдельных строках, новые строки после открывающей кавычки и предыдущие конечные кавычки не включаются в окончательное содержимое. Последовательность закрывающих кавычек определяет крайний левый столбец для строкового литерала. Вы можете отступить необработанный строковый литерал в соответствии с общим форматом кода:

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

Столбцы справа от конечной последовательности кавычки сохраняются. Такое поведение включает необработанные строки для таких форматов данных, как JSON, YAML или XML, как показано в следующем примере:

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

Компилятор выдает ошибку, если любая из текстовых строк простирается слева от закрывающей последовательности кавычек. Последовательности открывающих и закрывающих кавычки могут находиться в одной строке, предоставляя строковый литерал не начинается и не заканчивается символом кавычки:

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

Необработанные строковые литералы можно объединить с интерполяцией строк , чтобы включить символы кавычки и фигурные скобки в выходную строку.

Строковые литералы в кавычках заключаются в двойные кавычки ("):

"good morning"  // a string literal

Строковые литералы могут содержать любые символьные литералы. Escape-последовательности включены. В следующем примере escape-последовательность \\ используется для получения обратной косой черты, \u0066 — для получения буквы f, и \n — для получения новой строки.

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

Примечание

Escape-код \udddd (где dddd состоит из четырех цифр) представляет символ Юникода U+dddd. Также распознаются восьмизначные escape-коды Юникода: \Udddddddd.

Буквальные строковые литералы начинаются с @ и также заключаются в двойные кавычки. Пример:

@"good morning"  // a string literal

Преимуществом подробных строк является то, что escape-последовательности не обрабатываются, что упрощает запись. Например, следующий текст соответствует полному имени файла Windows:

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

Чтобы включить двойную кавычку в строку с кавычками, дважды выполните следующие действия:

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

Строковые литералы UTF-8

Строки в .NET хранятся с помощью кодировки UTF-16. UTF-8 — это стандарт для веб-протоколов и других важных библиотек. Начиная с C# 11, можно добавить u8 суффикс в строковый литерал, чтобы указать кодировку UTF-8. Литералы UTF-8 хранятся в виде ReadOnlySpan<byte> объектов. Естественным типом строкового литерала UTF-8 является ReadOnlySpan<byte>. При использовании строкового литерала UTF-8 создается более четкое объявление, чем объявление эквивалента System.ReadOnlySpan<T>, как показано в следующем коде:

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

Чтобы сохранить строковый литерал UTF-8 в виде массива, необходимо скопировать ReadOnlySpan<T>.ToArray() байты, содержащие литерал, в изменяемый массив:

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

Строковые литералы UTF-8 не являются константами времени компиляции; это константы среды выполнения. Поэтому их нельзя использовать в качестве значения по умолчанию для необязательного параметра. Строковые литералы UTF-8 нельзя объединить с интерполяцией строк. Маркер и u8 суффикс $ нельзя использовать в том же строковом выражении.

Тип delegate

Объявление типа делегата аналогично сигнатуре метода. Оно имеет возвращаемое значение и любое число параметров любого типа:

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

В .NET типы System.Action и System.Func предоставляют общие определения для многих распространенных делегатов. Скорее всего, не нужно определять новые пользовательские типы делегата. Вместо этого можно создать экземпляры предоставленных универсальных типов.

Ключевое слово delegate имеет ссылочный тип, который можно использовать для инкапсуляции именованного или анонимного метода. Делегаты аналогичны используемым в языке C++ указателям функций, но являются типобезопасными и безопасными. Сведения о применении делегатов см. в разделах Делегаты и Универсальные делегаты. Делегаты являются основой событий. Экземпляры делегата могут создаваться путем его связывания с именованным или анонимным методом.

Делегат должен быть создан при помощи метода или лямбда-выражения, имеющего совместимые возвращаемый тип и входные параметры. Дополнительные сведения о допустимой степени вариации сигнатур методов см. в разделе Вариативность в делегатах. Для использования с анонимными методами делегат и код, который должен быть связан с ним, должны быть объявлены вместе.

Сочетание делегатов или удаление завершается сбоем при исключении среды выполнения, если типы делегатов, участвующие во время выполнения, отличаются из-за преобразования вариантов. В следующем примере показана ситуация, которая завершается сбоем:

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;

Вы можете создать делегат с правильным типом среды выполнения, создав новый объект делегата. В следующем примере показано, как это решение можно применить к предыдущему примеру.

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;

Начиная с C# 9, можно объявить указатели функций, которые используют аналогичный синтаксис. Указатель функции использует инструкцию calli вместо создания экземпляра типа делегата и вызова виртуального метода Invoke.

Тип dynamic

Тип dynamic указывает, что использование переменной и ссылок на ее члены обходит проверку типа во время компиляции. Такие операции разрешаются во время выполнения. Тип dynamic упрощает доступ к API COM, таким как API автоматизации Office, к динамическим API, таким как библиотеки IronPython, и к HTML-модели DOM.

Тип dynamic в большинстве случаев ведет себя как тип object. В частности, можно преобразовать любое выражение, отличное от NULL, в тип dynamic. Тип dynamic отличается от object операций, содержащих выражения типа dynamic , не разрешаются или проверяются компилятором. Компилятор объединяет сведения об операции, которые впоследствии будут использоваться для оценки этой операции во время выполнения. В рамках этого процесса переменные типа dynamic компилируются в переменные типа object. Таким образом, тип dynamic существует только во время компиляции, но не во время выполнения.

В следующем примере переменной типа dynamic противопоставляется переменная типа object. Чтобы проверить тип каждой переменной во время компиляции, наведите указатель мыши на dyn или obj в операторах WriteLine. Скопируйте следующий код в редактор, где доступен IntelliSense. IntelliSense отображает dynamic для dyn и object для 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());
    }
}

Операторы WriteLine отображают типы времени выполнения dyn и obj. На этом этапе оба имеют один и тот же тип — целое число. Выводятся следующие результаты.

System.Int32
System.Int32

Чтобы увидеть разницу между dyn и obj во время компиляции, добавьте между объявлениями и операторами WriteLine в предыдущем примере следующие две строки:

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

При попытке добавления целого числа и объекта в выражение obj + 3 выдается ошибка компилятора. При этом для dyn + 3 ошибка не возникает. Выражение, которое содержится dyn , не проверяется во время компиляции, так как тип имеет значение dyndynamic.

В следующем примере dynamic используется в нескольких объявлениях. Метод Main также противопоставляет проверку типов во время компиляции.

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

Спецификация языка C#

Дополнительные сведения см. в следующих разделах статьи Спецификация языка C#:

См. также раздел