Поделиться через


7 Основные понятия

Запуск приложения 7.1

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

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

  • У него должно быть имя Main.
  • Это должно быть static.
  • Оно не должно быть универсальным.
  • Он должен быть объявлен в не универсальном типе. Если тип, объявляющий метод, является вложенным типом, ни один из его вложенных типов не может быть универсальным.
  • Модификатор может присутствовать, если тип возвращаемого значения метода — это async или System.Threading.Tasks.Task.
  • Тип возвращаемого значения должен быть void, intSystem.Threading.Tasks.Taskили System.Threading.Tasks.Task<int>.
  • Он не должен быть частичным методом (§15.6.9) без реализации.
  • Список параметров должен быть пустым или иметь один параметр значения типа string[].

Примечание. Для определения точки входа методы с async модификатором должны иметь именно один из двух типов возвращаемых значений, указанных выше. Метод async void или метод async, который возвращает другой ожидаемый тип, например ValueTask или ValueTask<int>, не соответствует точке входа. конечная заметка

Если в программе объявлено несколько методов, определяющих точку входа, внешний механизм может использоваться для указания того, какой метод считается фактической точкой входа для приложения. Если подходящий метод с типом возвращаемого значения int или void найден, любой квалифицирующий метод с типом возвращаемого значения System.Threading.Tasks.Task или System.Threading.Tasks.Task<int> не считается методом точки входа. Это ошибка компиляции для программы, скомпилированной как приложение, без точно одной точки входа. Программа, скомпилированная как библиотека классов, может содержать методы, которые будут считаться точками входа приложения, но результирующая библиотека не имеет точки входа.

Обычно объявленные специальные возможности (§7.5.2) метода определяются модификаторами доступа (§15.3.6), указанными в объявлении, и аналогичным образом объявленные специальные возможности типа определяются модификаторами доступа, указанными в объявлении. Чтобы метод заданного типа мог быть вызываемым, и сам тип, и его член должны быть доступны. Однако точка входа приложения — это особый случай. В частности, среда выполнения может получить доступ к точке входа приложения независимо от объявленной доступности и независимо от объявленной доступности объявлений типов, которые её содержат.

Если метод точки входа имеет возвращаемый тип System.Threading.Tasks.Task или System.Threading.Tasks.Task<int>, компилятор должен синтезировать синхронный метод точки входа, вызывающий соответствующий метод Main. Синтезированный метод имеет параметры и типы возвращаемых значений, основанные на методе Main.

  • Список параметров синтезированного метода совпадает со списком Main параметров метода.
  • Если тип возвращаемого значения метода Main — это System.Threading.Tasks.Task, то тип возвращаемого значения синтезированного метода — void.
  • Если тип возвращаемого значения метода Main — это System.Threading.Tasks.Task<int>, то тип возвращаемого значения синтезированного метода — int.

Выполнение синтезированного метода продолжается следующим образом:

  • Синтезированный метод вызывает метод Main, передавая значение его параметра string[] в качестве аргумента, если у метода Main есть такой параметр.
  • Если метод Main создает исключение, это исключение распространяется синтезированным методом.
  • В противном случае синтезированная точка входа ожидает завершения возвращаемой задачи, вызывая GetAwaiter().GetResult(), используя метод без параметра экземпляра или метод расширения, описанный в §C.3. Если задача завершается ошибкой, GetResult() вызовет исключение, и это исключение распространяется синтезированным методом.
  • Main Для метода с возвращаемым типом System.Threading.Tasks.Task<int>, если задача завершилась успешно, возвращаемое значение int возвращается из синтезированного метода GetResult().

Эффективная точка входа приложения — это точка входа, объявленная в программе, или синтезированный метод, если он необходим, как описано выше. Таким образом, возвращаемый тип эффективной точки входа всегда void или int.

При запуске приложения создается новый домен приложения. Несколько различных экземпляров приложения могут существовать на одном компьютере одновременно, и каждый из них имеет собственный домен приложения. Домен приложения позволяет изоляции приложений, выступая в качестве контейнера для состояния приложения. Домен приложения выступает в качестве контейнера и границы для типов, определенных в приложении, и библиотеки классов, которые он использует. Типы, загруженные в один домен приложения, отличаются от тех же типов, загруженных в другой домен приложения, и экземпляры объектов не используются напрямую между доменами приложений. Например, каждый домен приложения имеет собственную копию статических переменных для этих типов, а статический конструктор для типа выполняется не более одного раза для каждого домена приложения. Реализации могут предоставлять определяемую реализацией политику или механизмы создания и уничтожения доменов приложений.

Запуск приложения происходит, когда среда выполнения вызывает эффективную точку входа приложения. Если эффективная точка входа объявляет параметр, то во время запуска приложения реализация должна гарантировать, что начальное значение этого параметра является ненулевой ссылкой на массив строк. Этот массив должен состоять из ненулевых ссылок на строки, называемые параметрами приложения, которые задаются значениями, определенными реализацией в среде узла перед запуском приложения. Цель заключается в предоставлении информации о приложении, определенной до запуска приложения из другого места в размещенной среде.

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

Если тип возвращаемого значения действующей точки входа равен int, возвращаемое значение из вызова метода средой выполнения используется для завершения приложения (§7.2).

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

7.2 Завершение приложения

Завершение приложения возвращает управление средой выполнения.

Если возвращаемый тип эффективного метода точки входа приложения – int и выполнение завершилось без исключения, значение int служит кодом завершения приложения. Цель этого кода — разрешить обмен данными об успешном выполнении или сбое в среде выполнения. Если возвращаемый тип метода эффективной точки входа — void и выполнение завершается без исключения, код состояния завершения будет 0.

Если метод эффективной точки входа завершается из-за исключения (§21.4), код выхода определяется реализацией. Кроме того, реализация может предоставлять альтернативные API для указания кода выхода.

Является ли выполнение методов завершения (§15.13) частью процесса завершения приложения определяется конкретной реализацией.

Примечание. Реализация .NET Framework делает все разумные усилия по вызову финализаторов (§15.13) для всех объектов, которые еще не были удалены сборщиком мусора, если только такая очистка не была подавлена (вызовом метода библиотеки GC.SuppressFinalize, например). конечная заметка

7.3 Объявления

Объявления в программе C# определяют составляющие элементы программы. Программы C# упорядочены с помощью пространств имен. Они представлены с помощью объявлений пространства имен (§14), которые могут содержать объявления типов и вложенные объявления пространства имен. Объявления типов (§14.7) используются для определения классов (§15), структур (§16), интерфейсов (§18), перечислений (§19) и делегатов (§20). Типы элементов, разрешенных в объявлении типа, зависят от формы объявления типа. Например, объявления классов могут содержать объявления констант (§15.4), полей (§15.5), методов (§15.6), свойств (§15.7), событий (§15.8), индексаторов (§15.9), операторов (§15.10), конструкторов экземпляров (§15.11), статических конструкторов (§15.12), финализаторов (§15.13) и вложенных типов (§15.3.9).

Объявление определяет имя в пространстве объявления, к которому принадлежит объявление. Если в пространстве объявлений присутствуют два или более объявлений, вводящих членов с одинаковым именем, это считается ошибкой на этапе компиляции, за исключением следующих случаев:

  • В одной области объявления разрешены два или более объявления пространства имен с одинаковым именем. Такие объявления пространства имен объединяются для формирования одного логического пространства имен и совместного использования одного пространства объявлений.
  • Объявления в отдельных программах, но в том же пространстве имен, могут иметь то же самое имя.

    Примечание. Однако эти объявления могут привести к неоднозначности, если они включены в то же приложение. конечная заметка

  • Два или более методов с одинаковым именем, но разными сигнатурами допускаются в одном пространстве объявления (§7.6).
  • Два или более объявлений типов с одинаковым именем, но отдельные числа параметров типа разрешены в одном пространстве объявлений (§7.8.2).
  • Два или более объявлений типов с частичным модификатором в одном пространстве объявлений могут использовать одно и то же имя, одно и то же количество параметров типа и одну и ту же классификацию (класс, структура или интерфейс). В этом случае объявления типов способствуют одному типу и сами объединяются для формирования единого пространства объявлений (§15.2.7).
  • Объявление пространства имен и объявление типа в том же пространстве объявлений могут использовать то же имя, если объявление типа имеет по крайней мере один параметр типа (§7.8.2).

Существует несколько различных типов пространств объявлений, как описано в следующем разделе.

  • Во всех единицах компиляции программы namespace_member_declaration без заключения в namespace_declaration являются членами единого объединенного пространства объявлений, называемого глобальным пространством объявлений.
  • В пределах всех единиц компиляции программы namespace_member_declarations в namespace_declaration, имеющих одинаковое полное имя namespace, являются членами одного общего пространства деклараций.
  • Каждая compilation_unit и namespace_body имеет пространство объявлений псевдонимов. Каждый extern_alias_directive и using_alias_directivecompilation_unit или namespace_body добавляет элемент в пространство объявления псевдонима (§14.5.2).
  • Каждый не частичный класс, структура или объявление интерфейса создает новое пространство объявления. Каждый частичный класс, структура или объявление интерфейса вносит вклад в пространство объявлений, совместно используемое всеми соответствующими частями в одной программе (§16.2.4). Имена вводятся в это пространство объявления через class_member_declaration, struct_member_declaration, interface_member_declaration или type_parameter. За исключением объявлений перегруженных конструкторов экземпляров и объявлений статических конструкторов, класс или структуру не может содержать объявление члена с тем же именем, что и класс или структура. Класс, структура или интерфейс разрешает объявление перегруженных методов и индексаторов. Кроме того, класс или структура разрешает объявление перегруженных конструкторов экземпляров и операторов. Например, класс, структура или интерфейс могут содержать несколько объявлений методов с одинаковым именем, если эти объявления методов отличаются в их сигнатуре (§7.6). Обратите внимание, что базовые классы не вносят вклад в пространство объявления класса, а базовые интерфейсы не вносят вклад в пространство объявления интерфейса. Таким образом, производный класс или интерфейс может объявлять член с тем же именем, что и унаследованный элемент. Такой член, как говорят, скрывает унаследованный член.
  • Каждое объявление делегата создает новое пространство объявления. Имена вводятся в это пространство объявлений через параметры (fixed_parameters и parameter_arrays) и type_parameters.
  • Каждое объявление перечисления создает новое пространство объявления. Имена вводятся в это пространство объявлений через enum_member_declarations.
  • Каждое объявление метода, объявление свойства, объявление доступа к свойствам, объявление индексатора, объявление доступа индексатора, объявление оператора, объявление экземпляра конструктора, анонимная функция и локальная функция создает новое пространство объявления, называемое локальным пространством объявлений переменной. Имена вводятся в это пространство объявлений через параметры (fixed_parameters и parameter_arrays) и type_parameters. Установщик для свойства или индексатора вводит имя value в качестве параметра. Текст элемента функции, анонимной функции или локальной функции, если он есть, считается вложенным в локальное пространство объявления переменной. Если пространство объявления локальной переменной и вложенное пространство объявления переменной содержат элементы с тем же именем в области вложенного локального имени, внешнее локальное имя скрыто (§7.7.1) вложенным локальным именем.
  • Дополнительные пространства для объявления локальных переменных могут возникать в объявлениях членов, анонимных функциях и локальных функциях. Имена вводятся в эти пространства объявлений с помощью шаблонов, выражений объявления, инструкций объявления и спецификаторов исключений. Области объявления локальных переменных могут быть вложенными, но является ошибкой, если область объявления локальной переменной и вложенная область включают элементы с одинаковым именем. Таким образом, в вложенном пространстве объявления невозможно объявить локальную переменную, локальную функцию или константу с тем же именем, что и параметр, параметр типа, локальная переменная, локальная функция или константа во включающем пространстве объявления. Для двух пространств декларации могут содержать элементы с одинаковым именем, если ни одно из этих пространств декларации не содержит другое. Локальные пространства объявлений создаются следующими конструкциями:
    • Каждый инициализатор переменной в объявлении полей и свойств создает собственное локальное пространство объявления переменной, не вложенное в другое локальное пространство.
    • Тело элемента функции, анонимной функции или локальной функции, если оно имеется, создает пространство объявления локальной переменной, которое считается вложенным в локальное пространство объявления переменной функции.
    • Каждый constructor_initializer создает пространство объявления локальной переменной, вложенное в объявление конструктора экземпляра. Пространство объявления локальной переменной для текста конструктора в свою очередь вложено в это локальное пространство объявления переменной.
    • Каждый блок, switch_block, specific_catch_clause, iteration_statement и using_statement создает вложенное пространство объявления локальной переменной.
    • Каждый embedded_statement, который не является непосредственно частью statement_list, создает вложенное пространство объявления локальных переменных.
    • Каждый switch_section создает вложенное пространство для объявления локальных переменных. Однако переменные, объявленные непосредственно в statement_listswitch_section (но не в пространстве объявления вложенной локальной переменной внутри statement_list), добавляются непосредственно в пространство объявления локальных переменных включённого switch_block вместо switch_section.
    • Синтактический перевод query_expression (§12.20.3) может вводить одно или несколько лямбда-выражений. В качестве анонимных функций каждая из них создает локальное пространство объявления переменной, как описано выше.
  • Каждый блок или switch_block создает отдельное пространство объявлений для меток. Имена вводятся в это пространство объявления через labeled_statements, а имена ссылаются через goto_statements. Пространство объявления метки блока включает в себя все вложенные блоки. Таким образом, в вложенном блоке невозможно объявить метку с тем же именем, что и метка в заключаемом блоке.

Примечание: Тот факт, что переменные, объявленные непосредственно в switch_section, добавляются в пространство объявления локальных переменных switch_block, а не в switch_section, может привести к неожиданному поведению кода. В приведенном ниже примере локальная переменная y находится в области видимости в секции switch по умолчанию, несмотря на то что объявление появляется в секции switch для case 0. Локальная переменная z не находится в области видимости в разделе переключателя для случая по умолчанию, так как она вводится в пространство объявления локальной переменной для раздела, в котором происходит объявление.

int x = 1;
switch (x)
{
    case 0:
        int y;
        break;
    case var z when z < 10:
        break;
    default:
        y = 10;
        // Valid: y is in scope
        Console.WriteLine(x + y);
        // Invalid: z is not scope
        Console.WriteLine(x + z);
        break;
}

конечная заметка

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

  • Порядок объявления полей определяет порядок выполнения инициализаторов (если таковых) (§15.5.6.2, §15.5.5.6.3).
  • Локальные переменные должны быть определены перед их использованием (§7.7).
  • Порядок объявления элементов перечисления (§19.4) имеет важное значение, если значения constant_expression не указаны.

Пример: Пространство объявлений пространства имен — «открытое», и два объявления пространства имен с одинаковым полным именем входят в одно и то же пространство объявлений. Например.

namespace Megacorp.Data
{
    class Customer
    {
        ...
    }
}

namespace Megacorp.Data
{
    class Order
    {
        ...
    }
}

Два объявления пространства имен, приведенные выше, относятся к одному и тому же пространству имен, объявляя в этом случае два класса с полными именами Megacorp.Data.Customer и Megacorp.Data.Order. Так как два объявления способствуют одному и тому же пространству объявлений, это вызвало бы ошибку во время компиляции, если каждая из них содержала объявление класса с одинаковым именем.

конец примера

Примечание. Как указано выше, пространство объявления блока включает в себя все вложенные блоки. Таким образом, в следующем примере F методы G приводят к ошибке во время компиляции, так как имя i объявляется во внешнем блоке и не может быть переопределено во внутреннем блоке. Однако методы H и I допустимы, так как два i объявлены в отдельных невложенных блоках.

class A
{
    void F()
    {
        int i = 0;
        if (true)
        {
            int i = 1;
        }
    }

    void G()
    {
        if (true)
        {
            int i = 0;
        }
        int i = 1;
    }

    void H()
    {
        if (true)
        {
            int i = 0;
        }
        if (true)
        {
            int i = 1;
        }
    }

    void I()
    {
        for (int i = 0; i < 10; i++)
        {
            H();
        }
        for (int i = 0; i < 10; i++)
        {
            H();
        }
    }
}

конечная заметка

7.4 Члены

7.4.1 Общие

Пространства имен и типы имеют члены.

Примечание. Члены сущности обычно доступны через использование квалифицированного имени, которое начинается со ссылки на сущность, за которой следует маркер "." и имя члена. конечная заметка

Члены типа объявляются в объявлении типа или наследуются от базового класса типа. Если тип наследуется от базового класса, то все члены базового класса, кроме конструкторов экземпляров, финализаторов и статических конструкторов, становятся членами производного типа. Объявленная область видимости элемента базового класса не контролирует, наследуется ли элемент — наследование распространяется на любой элемент, который не является конструктором экземпляра, статическим конструктором или финализатором.

Примечание: Унаследованный член, однако, может быть недоступен в производном типе, например, из-за его объявленной доступности (§7.5.2). конечная заметка

Члены пространства имен 7.4.2

Пространства имен и типы, не имеющие заключающего пространства имен, являются членами глобального пространства имен. Это соответствует именам, объявленным в глобальном пространстве объявлений.

Пространства имен и типы, объявленные в них, являются членами соответствующих пространств имен. Это соответствует именам, объявленным в пространстве объявлений пространства имен.

Пространства имен не имеют ограничений доступа. Нельзя объявлять частные, защищенные или внутренние пространства имен, а имена пространств имен всегда общедоступны.

Элементы структуры 7.4.3

Члены структуры — это члены, объявленные в структуре, и члены, унаследованные от прямого базового класса System.ValueType структуры и косвенного базового класса object.

Элементы простого типа соответствуют непосредственно элементам типа структуры, которому присвоен простой тип (§8.3.5).

Элементы перечисления 7.4.4

Члены перечисления — это константы, объявленные в перечислении, и члены, унаследованные от прямого базового класса System.Enum перечисления, а также косвенные базовые классы System.ValueType и object.

Члены класса 7.4.5

Члены класса — это элементы, объявленные в классе, и элементы, унаследованные от базового класса (за исключением класса object, который не имеет базового класса). Члены, унаследованные от базового класса, включают константы, поля, методы, свойства, события, индексаторы, операторы и типы базового класса, но не конструкторы экземпляров, методы завершения и статические конструкторы базового класса. Члены базового класса наследуются без учета их специальных возможностей.

Объявление класса может содержать объявления констант, полей, методов, свойств, событий, индексаторов, операторов, конструкторов экземпляров, завершения, статических конструкторов и типов.

Члены object (§8.2.3) и string (§8.2.5) соответствуют непосредственно членам типов классов, которые они псевдонимируют.

Элементы интерфейса 7.4.6

Члены интерфейса — это члены, объявленные в самом интерфейсе и во всех его базовых интерфейсах.

Примечание. Члены класса object не являются, строго говоря, членами любого интерфейса (§18.4). Однако члены класса object доступны через поиск членов в любом типе интерфейса (§12.5). конечная заметка

Элементы массива 7.4.7

Члены массива являются элементами, унаследованными от класса System.Array.

7.4.8 Делегаты

Делегат наследует члены из класса System.Delegate. Кроме того, он содержит метод Invoke с тем же типом возвращаемого значения и списком параметров, указанным в объявлении (§20.2). Вызов этого метода должен вести себя идентично вызову делегата (§20.6) на том же экземпляре делегата.

Реализация может предоставлять дополнительные члены либо через наследование, либо непосредственно в самом делегате.

7.5 Доступ членов

7.5.1 Общие

Декларации членов позволяют управлять доступом к членам. Доступность элемента определяется его объявленной доступностью (§7.5.2) в сочетании с доступностью непосредственно содержащего типа, если таковой имеется.

Если доступ к конкретному члену разрешен, он будет доступен. И наоборот, если доступ к конкретному члену запрещен, член, как утверждается, недоступен. Доступ к члену разрешен, если текстовое расположение, в котором осуществляется доступ, входит в домен специальных возможностей (§7.5.3) члена.

7.5.2 Объявленная доступность

Объявленная доступность члена может быть одной из следующих:

  • Public, который выбирается путем включения public модификатора в объявление члена. Интуитивно понятное значение public — "доступ не ограничен".
  • Защищенный элемент, выбранный путем включения модификатора protected в объявление члена. Интуитивно понятное значение protected — "доступ ограничен содержащим классом или типами, производными от содержащего класса".
  • Внутренний, который выбирается путем включения модификатора internal в объявление члена. Интуитивно понятное значение internal — "доступ ограничен этой сборкой".
  • Защищённый внутренний, который выбирается путем включения как модификатора protected, так и модификатора internal в объявление члена. Интуитивно понятное значение protected internal — «доступно в этой сборке, а также в типах, производных от содержащего класса».
  • Private Protected, который выбирается путем включения как модификатора private, так и модификатора protected в объявлении члена. Интуитивно понятное значение private protected — "доступно в этой сборке с помощью содержащего класса и типов, производных от содержащего класса".
  • Приватный, выбираемый путем включения модификатора private в объявление члена. Интуитивно понятное значение private — "доступ ограничен содержащим типом".

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

  • Пространства имен неявно имеют объявленную доступность. Модификаторы доступа не разрешены в объявлениях пространства имен.
  • Типы, объявленные непосредственно в единицах компиляции или пространствах имен (в отличие от других типов), могут иметь объявленную доступность public или internal, и по умолчанию объявленная доступность будет internal.
  • Члены класса могут иметь любой из разрешенных видов объявленной доступности, а по умолчанию использовать private объявленную доступность.

    Примечание. Тип, объявленный как член класса, может иметь любой из разрешенных типов объявленных специальных возможностей, в то время как тип, объявленный как член пространства имен, может иметь только public или internal объявленные специальные возможности. конечная заметка

  • Члены структуры могут иметь public, internal или private объявленную доступность и по умолчанию получать private доступ, так как структуры неявно запечатаны. Члены структуры, представленные в struct (т. е. не унаследованные этой структурой), не могут иметь protected, protected internal или private protected, объявленный уровень доступности.

    Примечание. Тип, объявленный как член структуры, может иметь public, internal или private объявленную доступность, в то время как тип, объявленный как член пространства имен, может иметь только public или internal объявленную доступность. конечная заметка

  • Члены интерфейса неявно обладают объявленным уровнем доступа public. В объявлениях членов интерфейса не допускаются модификаторы доступа.
  • Члены перечисления неявно имеют public объявленный уровень доступа. Модификаторы доступа не допускаются в объявлениях членов перечислений.

7.5.3 Домены доступности

Домен доступности члена состоит из (возможно, несмежных) разделов текста программы, в которых разрешен доступ к члену. Для целей определения домена доступности члена, член считается верхнего уровня, если он не объявлен внутри типа, и член считается вложенным, если он объявлен внутри другого типа. Кроме того, текст программы программы определяется как весь текст, содержащийся во всех единицах компиляции программы, и текст программы типа определяется как весь текст, содержащийся в type_declarationэтого типа (включая, возможно, типы, вложенные в тип).

Домен специальных возможностей предопределенного типа (например object, intили double) является неограниченным.

Домен специальных возможностей верхнего уровня несвязанного типа T (§8.4.4), объявленного в программе P , определяется следующим образом:

  • Если объявленная доступность T является общедоступной, домен доступности T — это текст программы P и любая программа, ссылающаяся на P.
  • Если объявленная доступность T является внутренней, домен доступности T — это текст программы P.

Примечание. Из этих определений следует, что область доступности независимого типа верхнего уровня всегда как минимум охватывает текст программы, в которой объявлен этот тип. конечная заметка

Домен специальных возможностей для созданного типа T<A₁, ..., Aₑ> — это пересечение домена специальных возможностей несвязанного универсального типа T и доменов специальных возможностей аргументов A₁, ..., Aₑтипа.

Область доступа вложенного члена M, объявленного в типе T в программе P, определяется следующим образом (учитывая, что M сам по себе может быть типом):

  • Если объявленный уровень доступности Mpublic, то домен доступности M совпадает с доменом доступности T.
  • Если объявленная доступность M равна protected internal, пусть D будет объединением текста программы P и текста программы любого производного типа от T, объявленного вне P. Домен доступности M является пересечением домена доступности T с D.
  • Если объявленная доступность M имеет значение private protected, пусть D будет пересечением текста программы P и текста программы T и любого типа, производного от T. Домен доступности M является пересечением домена доступности T с D.
  • Если объявленная доступность M равна protected, тогда D будет объединением текста программы T и текста программы любого типа, производного от T. Домен доступности M является пересечением домена доступности T с D.
  • Если объявленный уровень доступности Minternal, то домен доступности M представляет собой пересечение домена доступности T с текстом программы P.
  • Если объявленный уровень доступности Mprivate, то домен доступности M совпадает с текстом программы T.

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

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

  • Во-первых, если M объявлен в пределах типа (в отличие от единицы компиляции или пространства имен), возникает ошибка времени компиляции, если этот тип недоступен.
  • Если M это public, доступ разрешен.
  • В противном случае, если M равно protected internal, доступ разрешен, если он осуществляется в программе, в которой объявлен M, или если возникает в классе, производном от класса, в котором объявлен M, и происходит через производный тип класса (§7.5.4).
  • В противном случае, если M — это protected, доступ разрешен, если он происходит в классе, в котором объявлен M, или если он происходит в классе, производном от класса, в котором объявлен M, и осуществляется через производный тип класса (§7.5.4).
  • В противном случае, если M является internal, доступ разрешен, если это происходит в программе, где объявлен M.
  • В противном случае, если M является private, доступ разрешен, если он осуществляется в пределах типа, в котором объявлен M.
  • В противном случае тип или член недоступны, и возникает ошибка во время компиляции. конечная заметка

Пример. В следующем коде

public class A
{
    public static int X;
    internal static int Y;
    private static int Z;
}

internal class B
{
    public static int X;
    internal static int Y;
    private static int Z;

    public class C
    {
        public static int X;
        internal static int Y;
        private static int Z;
    }

    private class D
    {
        public static int X;
        internal static int Y;
        private static int Z;
    }
}

Классы и члены имеют следующие области доступности:

  • Домен специальных возможностей A и A.X является неограниченным.
  • Домен доступности A.Y, B, B.X, B.Y, B.C, B.C.X и B.C.Y является текстом содержащей программы программы.
  • Домен специальных возможностей A.Z — это программный текст A.
  • Область доступности B.Z и B.D охватывает текст программы B, включая текст программы B.C и B.D.
  • Домен специальных возможностей B.C.Z — это программный текст B.C.
  • Область доступности B.D.X и B.D.Y охватывает текст программы B, включая текст программы B.C и B.D.
  • Домен специальных возможностей B.D.Z — это программный текст B.D. Как показано в примере, домен специальных возможностей элемента никогда не превышает область, содержащую тип. Например, несмотря на то, что все X члены имеют публично объявленную доступность, все, кроме A.X, имеют домены доступности, ограниченные типом, который их содержит.

конец примера

Как описано в разделе 7.4, все члены базового класса, за исключением конструкторов экземпляров, финализаторов и статических конструкторов, наследуются производными типами. Это включает даже частные члены базового класса. Однако область доступности частного члена включает только текст программы того типа, в котором объявлен член.

Пример. В следующем коде

class A
{
    int x;

    static void F(B b)
    {
        b.x = 1;         // Ok
    }
}

class B : A
{
    static void F(B b)
    {
        b.x = 1;         // Error, x not accessible
    }
}

Класс B наследует закрытый член x из класса A. Поскольку член является приватным, он доступен только внутри class_bodyA. Таким образом, доступ к b.x успешен в методе A.F, но заканчивается сбоем в методе B.F.

конец примера

Защищенный доступ 7.5.4

Когда член экземпляра protected или private protected обращается за пределами текста программы класса, в котором он объявлен, и когда член экземпляра protected internal обращается за пределами текста программы, в которой он объявлен, доступ должен осуществляться внутри объявления класса, производного от того класса, в котором он объявлен. Кроме того, доступ должен проходить через экземпляр этого производного типа класса или типа класса, созданного из него. Это ограничение запрещает одному производному классу получать доступ к защищенным членам других производных классов, даже если члены наследуются от одного базового класса.

Пусть B будет базовым классом, который объявляет защищенный член экземпляра M, и пусть D будет классом, производным от B. Внутри class_bodyD, доступ к M может принимать одну из следующих форм:

  • Неквалифицированный type_name или primary_expression в виде M.
  • Первичное выражение формы , если тип E.M является E или классом, производным от T, где T является классом T или типом класса, построенным из D.
  • Основное_выражение формы base.M.
  • Некоторое первичное выражение формы base[argument_list].

Помимо этих форм доступа производный класс может получить доступ к защищенному конструктору экземпляра базового класса в constructor_initializer (§15.11.2).

Пример. В следующем коде

public class A
{
    protected int x;

    static void F(A a, B b)
    {
        a.x = 1; // Ok
        b.x = 1; // Ok
    }
}

public class B : A
{
    static void F(A a, B b)
    {
        a.x = 1; // Error, must access through instance of B
        b.x = 1; // Ok
    }
}

в пределах A можно получить доступ x через экземпляры как A, так и B, так как в любом случае доступ происходит через экземпляр A или класс, производный от A. Однако в пределах Bне удается получить доступ x через экземпляр A, так как A не является производным от B.

конец примера

Пример:

class C<T>
{
    protected T x;
}

class D<T> : C<T>
{
    static void F()
    {
        D<T> dt = new D<T>();
        D<int> di = new D<int>();
        D<string> ds = new D<string>();
        dt.x = default(T);
        di.x = 123;
        ds.x = "test";
    }
}

Здесь разрешены три присваивания переменной x, так как все они выполняются через экземпляры классов, созданных из универсального типа.

конец примера

Примечание. Область видимости (§7.5.3) защищенного члена, объявленного в обобщенном классе, включает текст программы всех объявлений классов, наследуемых от любых типов, созданных из этого обобщенного класса. В примере:

class C<T>
{
    protected static T x;
}

class D : C<string>
{
    static void Main()
    {
        C<int>.x = 5;
    }
}

Ссылка на protected члена C<int>.x в D является допустимой, даже если класс D является производным от C<string>. конечная заметка

Ограничения специальных возможностей 7.5.5

Для некоторых конструкций в языке C# требуется, чтобы тип имел по крайней мере такую же доступность, как и член или другой тип. Тип T считается по крайней мере таким же доступным, как член или тип M, если домен доступности T является супермножеством домена доступности M. Другими словами, T по крайней мере так же доступен, как M, если T доступен во всех контекстах, в которых доступен M.

Существуют следующие ограничения специальных возможностей:

  • Прямой базовый класс типа класса должен быть по крайней мере так же доступен, как сам тип класса.
  • Явные базовые интерфейсы типа интерфейса должны быть по крайней мере такими же доступными, как и сам тип интерфейса.
  • Возвращаемый тип и типы параметров типа делегата должны быть по крайней мере доступны как сам тип делегата.
  • Тип константы должен быть по крайней мере таким же доступным, как сама константа.
  • Тип поля должен быть по крайней мере таким же доступным, как и само поле.
  • Возвращаемый тип и типы параметров метода должны быть по крайней мере такими же доступными, как и сам метод.
  • Тип свойства должен быть по крайней мере таким же доступным, как и само свойство.
  • Тип события должен быть по крайней мере таким же доступным, как само событие.
  • Тип и типы параметров индексатора должны быть по крайней мере как доступны, как сам индексатор.
  • Возвращаемый тип и типы параметров оператора должны быть, по крайней мере, такими же доступными, как и сам оператор.
  • Типы параметров конструктора экземпляра должны быть по крайней мере как доступны, как и сам конструктор экземпляра.
  • Ограничение типа интерфейса или класса для параметра типа должно быть как минимум так же доступно, как член, который объявляет это ограничение.

Пример. В следующем коде

class A {...}
public class B: A {...}

B класс вызывает ошибку на этапе компиляции, так как A не является таким же доступным, как B.

конец примера

Пример. Аналогичным образом в следующем коде

class A {...}

public class B
{
    A F() {...}
    internal A G() {...}
    public A H() {...}
}

Метод H в B приводит к ошибке времени компиляции, потому что возвращаемый тип A не является по крайней мере настолько же доступным, как и сам метод.

конец примера

7.6 Сигнатуры и перегрузки

Методы, конструкторы экземпляров, индексаторы и операторы характеризуются их сигнатурами:

  • Сигнатура метода состоит из имени метода, количества параметров типа, а также типа и режима передачи каждого из его параметров, рассмотренных в порядке слева направо. Для этих целей любой параметр типа метода, который встречается в типе параметра, определяется не по имени, а по порядковому номеру в списке параметров типа метода. Сигнатура метода, в частности, не включает возвращаемый тип, имена параметров, имена параметров типа, ограничения параметров типа, модификаторы параметров params или this, независимо от того, являются ли параметры обязательными или необязательными.
  • Сигнатура конструктора экземпляра состоит из типа и режима передачи параметров каждого из его параметров, рассмотренного в порядке слева направо. Сигнатура конструктора экземпляра, в частности, не включает модификатор params, который может быть указан для крайнего правого параметра, а также не учитывает, являются ли параметры обязательными или необязательными.
  • Сигнатура индексатора состоит из типа каждого из параметров, рассмотренного в порядке слева направо. Сигнатура индексатора, в частности, не включает тип элемента, не включает модификатор params, который может быть указан для самого правого параметра, и не указывает, являются ли параметры обязательными или необязательными.
  • Сигнатура оператора состоит из имени оператора и типа каждого из его параметров, рассмотренного в порядке слева направо. Подпись оператора, в частности, не включает тип результата.
  • Подпись оператора преобразования состоит из исходного типа и целевого типа. Неявная или явная классификация оператора преобразования не является частью сигнатуры.
  • Две сигнатуры одного вида члена (метод, конструктор экземпляра, индексатор или оператор) считаются одинаковыми, если они имеют одинаковое имя, число параметров типа, количество параметров и режимы передачи параметров, а тождественное преобразование существует между типами соответствующих параметров (§10.2.2).

Подписи — это механизм перегрузки членов в классах, структурах и интерфейсах:

  • Перегрузка методов позволяет классу, структуре или интерфейсу объявлять несколько методов с одинаковым именем, если их сигнатуры уникальны в этом классе, структуре или интерфейсе.
  • Перегрузка конструкторов экземпляров позволяет классу или структуре объявлять несколько конструкторов экземпляров, если их сигнатуры уникальны в этом классе или структуре.
  • Перегрузка индексаторов позволяет классу, структуре или интерфейсу объявлять несколько индексаторов, если их сигнатуры уникальны в этом классе, структуре или интерфейсе.
  • Перегрузка операторов позволяет классу или структуре объявлять несколько операторов с одинаковым именем, если их сигнатуры уникальны в этом классе или структуре.

Несмотря на то, что модификаторы параметров in, out и ref считаются частью подписи, члены, объявленные в одном типе, не могут отличаться в подписи только по in, out и ref. Ошибка компиляции возникает, если два члена объявлены в одном типе с сигнатурами, которые становятся одинаковыми, если все параметры обоих методов с модификаторами out или in изменены на модификаторы ref. Для других целей сопоставления подписей (например, скрытия или переопределения), inoutи ref считаются частью подписи и не соответствуют друг другу.

Примечание. Это ограничение позволяет программам на C# легко переноситься на Общую языковую инфраструктуру (CLI), которая не позволяет определять методы, различающиеся только по in, out и ref. конечная заметка

Типы object и dynamic не отличаются при сравнении подписей. Поэтому члены, объявленные в одном типе, подписи которых отличаются только заменой objectdynamic на не допускаются.

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

interface ITest
{
    void F();                   // F()
    void F(int x);              // F(int)
    void F(ref int x);          // F(ref int)
    void F(out int x);          // F(out int) error
    void F(object o);           // F(object)
    void F(dynamic d);          // error.
    void F(int x, int y);       // F(int, int)
    int F(string s);            // F(string)
    int F(int x);               // F(int) error
    void F(string[] a);         // F(string[])
    void F(params string[] a);  // F(string[]) error
    void F<S>(S s);             // F<0>(0)
    void F<T>(T t);             // F<0>(0) error
    void F<S,T>(S s);           // F<0,1>(0)
    void F<T,S>(S s);           // F<0,1>(1) ok
}

Обратите внимание, что все inoutref модификаторы параметров (§15.6.2) являются частью сигнатуры. Таким образом, F(int), F(in int)и F(out int)F(ref int) все уникальные подписи. Однако F(in int), F(out int) и F(ref int) не могут быть объявлены в одном интерфейсе, так как их подписи отличаются исключительно по in, out и ref. Кроме того, обратите внимание, что возвращаемый тип и params модификатор не являются частью сигнатуры, поэтому невозможно перегружать исключительно на основе типа возврата или включения или исключения params модификатора. Таким образом, объявления методов F(int) и F(params string[]) указанных выше методов приводят к ошибке во время компиляции. конец примера

7.7 Области

7.7.1 Общие

Область имени — это область текста программы, в которой можно ссылаться на сущность, объявленную именем без квалификации имени. Области могут быть вложены, а внутренняя область может переопределять смысл имени из внешней области. (Однако это не снимает ограничения, наложенного §7.3, заключающегося в том, что во вложенном блоке невозможно объявить локальную переменную или локальную константу с таким же именем, как локальная переменная или локальная константа во внешнем блоке.) Имя из внешней области считается скрытым в области текста программы, охватываемой внутренней областью, и доступ к внешнему имени возможен только путем уточнения имени.

  • Область элемента пространства имен, объявленного namespace_member_declaration (§14.6) без окружающего namespace_declaration, охватывает весь текст программы.

  • Область действия элемента пространства имен, объявленного в namespace_member_declaration внутри namespace_declaration с полным именем N, - это namespace_body каждого namespace_declaration, полное имя которого равно или начинается с N, за которым следует точка N.

  • Область видимости имени, определяемая extern_alias_directive (§14.4), распространяется на using_directive, global_attributes и namespace_member_declaration в его непосредственно содержащем compilation_unit или namespace_body. Extern_alias_directive не вносит новых членов в основное пространство объявлений. Другими словами, extern_alias_directive не имеет транзитивного эффекта, а влияет только на compilation_unit или namespace_body, в которых он используется.

  • Область действия имени, определяемого или импортируемого директивой using_directive (§14.5), распространяется на global_attributes и namespace_member_declaration в compilation_unit или namespace_body, где происходит using_directive. Using_directive может сделать ноль или более пространств имен или имен типов доступными в определенном compilation_unit или namespace_body, но не вносит новых членов в базовое пространство объявлений. Другими словами, using_directive не является транзитивным и влияет только на единицу компиляции или пространство имен, в которых она используется.

  • Область параметра типа, объявленного type_parameter_list в class_declaration (§15.2), — это class_base, type_parameter_constraints_clauseи class_body этого class_declaration.

    Примечание. В отличие от членов класса, эта область не распространяется на производные классы. конечная заметка

  • Область действия параметра типа, объявленного в type_parameter_list в struct_declaration (§16.2), охватывает struct_interfaces, type_parameter_constraints_clause и struct_body данного struct_declaration.

  • Область параметра типа, объявленного в type_parameter_list в interface_declaration (§18.2), — это interface_base, type_parameter_constraints_clause и interface_body этого interface_declaration.

  • Область действия параметра типа, объявленного с помощью type_parameter_list в delegate_declaration (§20.2), распространяется на return_type, parameter_list и type_parameter_constraints_clause этого delegate_declaration.

  • Область параметра типа, объявленного type_parameter_list на method_declaration (§15.6.1), является method_declaration.

  • Область действия элемента, объявленного class_member_declaration (§15.3.1), является class_body, в которой происходит объявление. Кроме того, область члена класса распространяется на class_body этих производных классов, включенных в домен специальных возможностей (§7.5.3) члена.

  • Область действия члена, объявленного в struct_member_declaration (§16.3), — это struct_body, в котором происходит объявление.

  • Область действия члена, объявленного enum_member_declaration (§19.4), это enum_body, в которой объявление происходит.

  • Область действия параметра, объявленного в method_declaration (§15.6), — это method_body или ref_method_body этого method_declaration.

  • Область действия параметра, объявленного в indexer_declaration (§15.9), охватывает indexer_body этого indexer_declaration.

  • Область действия параметра, объявленного в operator_declaration (§15.10), это operator_body этого operator_declaration.

  • Область параметра, объявленного в constructor_declaration (§15.11), охватывает constructor_initializer и блок этого constructor_declaration.

  • Область параметра, объявленного в lambda_expression (§12.19), является lambda_expression_body этого lambda_expression.

  • Область параметра, объявленного в анонимном_методе_выражении (§12.19), это блок этого анонимного_метода_выражения.

  • Область метки, объявленной в labeled_statement (§13.5), — это блок, в котором происходит объявление.

  • Область локальной переменной, объявленной в local_variable_declaration (§13.6.2), является блоком, в котором происходит объявление.

  • Область действия локальной переменной, объявленной в switch_blockswitch инструкции (§13.8.3) является switch_block.

  • Область видимости локальной переменной, объявленной в for_initializer инструкции (for), включает в себя for_initializer, for_condition, for_iterator и embedded_statement инструкции .

  • Область локальной константы, объявленной в local_constant_declaration (§13.6.3), является блоком, в котором происходит объявление. Это ошибка времени компиляции, если ссылка на локальную константу происходит в тексте до constant_declarator.

  • Область переменной, объявленной как часть foreach_statement, using_statement, lock_statement или query_expression, определяется расширением заданной конструкции.

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

Пример:

class A
{
    void F()
    {
        i = 1;
    }

    int i = 0;
}

Здесь допустимо F ссылаться на i до его объявления.

конец примера

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

Пример:

class A
{
    int i = 0;

    void F()
    {
        i = 1;                // Error, use precedes declaration
        int i;
        i = 2;
    }

    void G()
    {
        int j = (j = 1);     // Valid
    }

    void H()
    {
        int a = 1, b = ++a; // Valid
    }
}

В методе выше первое присваивание F переменной i специально не относится к полю, объявленному во внешней области видимости. Скорее, он ссылается на локальную переменную и приводит к ошибке во время компиляции, так как она текстуально предшествует объявлению переменной. В методе G использование j в инициализаторе для объявления j является допустимым, так как использование не предшествует декларатору. В методе H последующий декларатор правильно ссылается на локальную переменную, объявленную в предыдущем деклараторе в том же local_variable_declaration.

конец примера

Примечание. Правила области для локальных переменных и локальных констант предназначены для гарантии того, что значение имени, используемого в контексте выражения, всегда совпадает в блоке. Если бы область видимости локальной переменной простиралась только от ее объявления до конца блока, то в приведенном выше примере первое присваивание относилось бы к переменной экземпляра, а второе присваивание — к локальной переменной, что могло бы привести к ошибкам во время компиляции, если операторы блока впоследствии были бы переупорядочены.

Значение имени в блоке может отличаться в зависимости от контекста, в котором используется имя. В примере

class A {}

class Test
{
    static void Main()
    {
        string A = "hello, world";
        string s = A;                      // expression context
        Type t = typeof(A);                // type context
        Console.WriteLine(s);              // writes "hello, world"
        Console.WriteLine(t);              // writes "A"
    }
}

имя A используется в контексте выражения для ссылки на локальную переменную A и в контексте типа для ссылки на класс A.

конечная заметка

Скрытие имени 7.7.2

7.7.2.1 Общие

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

Скрытие имен происходит, когда области пересекаются по причине вложенности или наследования. Характеристики двух типов скрытия описаны в следующих подклаузах.

7.7.2.2 Сокрытие через вложение

Скрытие имен через вложение может происходить в результате вложения пространств имен или типов в пространства имен, в результате вложения типов в классах или структурах, в результате вызова локальной функции или лямбда-функции, а также как результат деклараций параметров, локальных переменных и локальных констант.

Пример. В следующем коде

class A
{
    int i = 0;
    void F()
    {
        int i = 1;

        void M1()
        {
            float i = 1.0f;
            Func<double, double> doubler = (double i) => i * 2.0;
        }
    }

    void G()
    {
        i = 1;
    }
}

В методе F переменная экземпляра i скрыта локальной переменной i, но в методе Gi по-прежнему ссылается на переменную экземпляра. Внутри локальной функции M1float i скрывает непосредственно внешнее i. Лямбда-параметр i скрывает float i внутри лямбда-тела.

конец примера

Если имя во внутренней области скрывает имя во внешней области, оно скрывает все перегруженные варианты этого имени.

Пример. В следующем коде

class Outer
{
    static void F(int i) {}
    static void F(string s) {}

    class Inner
    {
        static void F(long l) {}

        void G()
        {
            F(1); // Invokes Outer.Inner.F
            F("Hello"); // Error
        }
    }
}

Вызов F(1) инициирует F, объявленный в Inner, поскольку все внешние вхождения F скрыты внутренним объявлением. По той же причине вызов F("Hello") приводит к ошибке во время компиляции.

конец примера

7.7.2.3 Скрытие через наследование

Скрытие имен через наследование происходит, когда классы или структуры переопределяют имена, унаследованные от базовых классов. Этот тип скрытия имен принимает одну из следующих форм:

  • Константа, поле, свойство, событие или тип, представленные в классе или структуре, скрывает все члены базового класса с одинаковым именем.
  • Метод, представленный в классе или структуре, скрывает всех членов базового класса, которые не являются методами и имеют такое же имя, а также все методы базового класса с той же сигнатурой (§7.6).
  • Индексатор, представленный в классе или структуре, скрывает все индексаторы базового класса с одной сигнатурой (§7.6).

Правила, управляющие объявлениями операторов (§15.10), делают невозможным для производного класса объявить оператор с той же подписью, что и оператор в базовом классе. Таким образом, операторы никогда не скрывают друг друга.

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

Пример. В следующем коде

class Base
{
    public void F() {}
}

class Derived : Base
{
    public void F() {} // Warning, hiding an inherited name
}

Объявление F в Derived вызывает сообщение о предупреждении. Скрытие унаследованного имени в частности не является ошибкой, так как это препятствует отдельной эволюции базовых классов. Например, описанная выше ситуация могла возникнуть потому, что более поздняя версия Base ввела метод F, который не был предусмотрен в более ранней версии класса.

конец примера

Предупреждение, вызванное скрытием унаследованного имени, можно устранить с помощью new модификатора:

Пример:

class Base
{
    public void F() {}
}

class Derived : Base
{
    public new void F() {}
}

Модификатор new указывает, что F в Derived является "новым" и действительно предназначен для скрытия унаследованного элемента.

конец примера

Объявление нового элемента скрывает унаследованный элемент только в пределах области нового элемента.

Пример:

class Base
{
    public static void F() {}
}

class Derived : Base
{
    private new static void F() {} // Hides Base.F in Derived only
}

class MoreDerived : Derived
{
    static void G()
    {
        F();                       // Invokes Base.F
    }
}

В приведенном выше примере объявление F в Derived скрывает F, унаследованный от Base, но поскольку новое F в Derived имеет закрытый доступ, его область действия не распространяется на MoreDerived. Таким образом, вызов F() в MoreDerived.G является допустимым и вызовет Base.F.

конец примера

7.8 Имена пространств имён и типов

7.8.1 Общие

Для указания нескольких контекстов в программе C# требуется namespace_name или type_name .

namespace_name
    : namespace_or_type_name
    ;

type_name
    : namespace_or_type_name
    ;

namespace_or_type_name
    : identifier type_argument_list? ('.' identifier type_argument_list?)*
    | qualified_alias_member ('.' identifier type_argument_list?)*
    ;

namespace_name — это namespace_or_type_name, который ссылается на пространство имен.

При разрешении, как описано ниже, namespace_or_type_name в namespace_name должно ссылаться на пространство имен, в противном случае возникает ошибка времени компиляции. Аргументы типа (§8.4.2) не могут присутствовать в namespace_name (только типы могут иметь аргументы типа).

Type_name — это namespace_or_type_name, который ссылается на тип.

После разрешения, как описано ниже, namespace_or_type_name для type_name должен ссылаться на тип, иначе возникнет ошибка на этапе компиляции.

namespace_or_type_name относится к типу или пространству имен. Определение конкретного пространства имен или типа включает два этапа, которые основаны на разделении грамматики на ведущую часть, являющуюся одним из фрагментов грамматики.

  • identifier type_argument_list?
  • qualified_alias_member

и заключительная часть, которая является грамматическим фрагментом:

  • ('.' identifier type_argument_list?)*

Сначала для определения R₀ начального пространства имен или типа разрешается первая часть.

Если ведущая часть namespace_or_type_name является qualified_alias_member, то это пространство имен или тип, определяемый путем разрешения, как описано в R₀.

В противном случае ведущая часть, являющаяся фрагментом грамматики identifier type_argument_list?, будет иметь одну из следующих форм:

  • I
  • I<A₁, ..., Aₓ>

где:

  • I является одним идентификатором; и
  • <A₁, ..., Aₓ> — это type_argument_list, если type_argument_list не указано, считается x нулевым.

R₀ определяется следующим образом:

  • Если x равно нулю, а namespace_or_type_name отображается в объявлении универсального метода (§15.6), но за пределами атрибутов заголовка метода, и если это объявление включает параметр типа (§15.2.3) с именем I, то R₀ ссылается на этот параметр типа.
  • В противном случае, если namespace_or_type_name отображается в объявлении типа, то для каждого типа T экземпляра (§15.3.2), начиная с типа экземпляра объявления этого типа и продолжая тип экземпляра каждого заключенного класса или объявления структуры (если таковой имеется):
    • Если x значение равно нулю, а объявление T включает параметр типа с именем I, то R₀ ссылается на этот параметр типа.
    • В противном случае, если namespace_or_type_name отображается в тексте объявления типа, и этот тип или любой из его базовых типов содержит вложенный доступный тип с именем T и параметрами типа I, тогда x ссылается на этот тип, созданный с заданными аргументами типа. Если существует более одного такого типа, выбирается тип, объявленный в более производном типе.

      Примечание. Элементы без типов (константы, поля, методы, свойства, индексаторы, операторы, конструкторы экземпляров, методы завершения и статические конструкторы) и элементы типов с другим числом параметров типа игнорируются при определении значения namespace_or_type_name. конечная заметка

    • В противном случае для каждого пространства имен N, начиная с пространства имен, в котором встречается namespace_or_type_name, продолжая с каждым заключающим пространством имен (если таковые имеются) и заканчивая глобальным пространством имен, следующие шаги рассматриваются, пока не найдется сущность:
      • Если x значение равно нулю и I является именем пространства имен в N, то:
        • Если место, где используется namespace_or_type_name, находится внутри объявления пространства имен для N, и это объявление содержит extern_alias_directive или using_alias_directive, которые связывают имя I с пространством имен или типом, то namespace_or_type_name будет неразрешимым, и возникнет ошибка во время компиляции.
        • В противном случае, R₀ относится к пространству имен, именованному I в N.
      • В противном случае, если N содержит доступный тип с именем I и параметрами типа x, то:
        • Если x равно нулю и место, где происходит namespace_or_type_name, заключено в объявление пространства имен N, а это объявление содержит extern_alias_directive или using_alias_directive, связывающее имя I с пространством имен или типом, то namespace_or_type_name является неоднозначным, и возникает ошибка компиляции.
        • В противном случае относится к типу R₀, который создается с помощью заданных аргументов типа.
      • В противном случае, если место, где упоминается namespace_or_type_name заключено в объявление пространства имен для N:
        • Если x равно нулю, и объявление пространства имен содержит extern_alias_directive или using_alias_directive, который связывает имя I с импортированным пространством имен или типом, то R₀ ссылается на это пространство имен или тип.
        • В противном случае, если пространства имен, импортированные директивой using_namespace_directive в объявлении пространства имен, содержат ровно один тип, имеющий имя I и x в качестве параметров типа, то R₀ ссылается на этот тип, построенный с помощью заданных аргументов типа.
        • В противном случае, если пространства имен, импортированные директивами using_namespace_directive объявления пространства имен, содержат больше одного типа, имеющего имя I и параметры типа x, то имя namespace_or_type_name становится неоднозначным, и возникает ошибка во время компиляции.
    • В противном случае namespace_or_type_name не определен и возникает ошибка во время компиляции.

Если R₀ успешно разрешено, то конечная часть namespace_or_type_name разрешается. Конечный фрагмент грамматики состоит из k ≥ 0 повторений, при этом каждое повторение последовательно уточняет указанное пространство имен или тип.

Если k равно нулю, т. е. нет конечной части, то namespace_or_type_name разрешается в R₀.

В противном случае каждое повторение будет иметь одну из форм:

  • .I
  • .I<A₁, ..., Aₓ>

где Iи Ax определяются как описано выше.

Для каждого повторения n, где 1 ≤ n ≤ k, его разрешение, Rₙ, включает в себя Rₚ, причем p = n - 1 — это разрешение предыдущего повторения, определяется следующим образом:

  • Если x значение равно нулю и Rₚ относится к пространству имен и Rₚ содержит вложенное пространство имен с именем I, то Rₙ ссылается на это вложенное пространство имен.
  • В противном случае, если Rₚ ссылается на пространство имен и Rₚ содержит доступный тип с параметрами имени I и x типа, то Rₙ ссылается на этот тип, созданный с помощью аргументов данного типа.
  • В противном случае, если Rₚ относится к классу или типу структуры (возможно, построенному), и Rₚ или любой из его базовых классов содержит вложенный доступный тип с именем I и параметрами типа x, то Rₙ ссылается на этот тип, созданный с указанными аргументами типа. Если существует более одного такого типа, выбирается тип, объявленный в более производном типе.

    Примечание. Если значение T.Iдля определенного типа Tопределяется как часть разрешения спецификации T базового класса, то прямой базовый класс T считается object (§15.2.4.2). конечная заметка

  • В противном случае namespace_or_type_name недопустим и возникает ошибка во время компиляции.

Разрешение namespace_or_type_name — это разрешение последнего повторения. Rₖ

namespace_or_type_name может ссылаться на статический класс (§15.2.2.4), только если

  • namespace_or_type_name — это T в namespace_or_type_name формы T.I, или
  • namespace_or_type_name — это T в typeof_expression (§12.8.18) формы typeof(T)

7.8.2 Неквалифицированные имена

Каждое объявление пространства имен и объявление типа имеет неквалифицированное имя , определенное следующим образом:

  • Для объявления пространства имен неквалифицированное имя — это квалифицированный идентификатор, указанный в объявлении.
  • Для объявления типа без type_parameter_list не квалифицированное имя - это идентификатор, указанный в объявлении.
  • Для объявления типа с K параметрами типа неквалифицированное имя — это идентификатор , указанный в объявлении, за которым следует классификатор размерности (§12.8.18) для K параметров типа.

7.8.3 Полные квалифицированные имена

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

  • Если N является членом глобального пространства имен, его полное квалифицированное имя — N.
  • В противном случае его полное имя — это S.N, где S — полное имя пространства имен или объявления типа, в котором объявляется N.

Иными словами, полное имя N — это полный иерархический путь идентификаторов и generic_dimension_specifier, которые приводят к N, начиная с глобального пространства имен. Так как каждый член пространства имен или типа должен иметь уникальное имя, следует, что полное имя пространства имен или объявления типа всегда уникально. Ошибка времени компиляции возникает, если одно и то же полное имя ссылается на две разные сущности. В частности:

  • Ошибка возникает, когда как объявление пространства имен, так и объявление типа имеют одно и то же полное имя.
  • Это ошибка, если у двух различных объявлений типов одинаковое полное имя (например, если и структура, и класс имеют одинаковое полное имя).
  • Объявление типа будет ошибкой, если у него нет частичного модификатора и такое же полное имя, как у другого объявления типа (§15.2.7).

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

class A {}                 // A
namespace X                // X
{
    class B                // X.B
    {
        class C {}         // X.B.C
    }
    namespace Y            // X.Y
    {
        class D {}         // X.Y.D
    }
}
namespace X.Y              // X.Y
{
    class E {}             // X.Y.E
    class G<T>             // X.Y.G<>
    {
        class H {}         // X.Y.G<>.H
    }
    class G<S,T>           // X.Y.G<,>
    {
        class H<U> {}      // X.Y.G<,>.H<>
    }
}

конец примера

7.9 Автоматическое управление памятью

C# использует автоматическое управление памятью, которое освобождает разработчиков от ручного выделения и освобождения памяти, занятой объектами. Политики автоматического управления памятью реализуются сборщиком мусора. Жизненный цикл управления памятью объекта выглядит следующим образом:

  1. При создании объекта для него выделяется память, выполняется конструктор и объект считается динамическим.
  2. Если ни объект, ни какие-либо из его полей экземпляра не могут быть доступны для любого возможного продолжения выполнения, отличного от выполнения методов завершения, объект считается больше не используемым и становится допустимым для завершения.

    Примечание. Компилятор C# и сборщик мусора могут выбрать анализ кода, чтобы определить, какие ссылки на объект могут использоваться в будущем. Например, если локальная переменная, которая находится в области видимости, является единственной существующей ссылкой на объект, но к этой локальной переменной никогда не обращаются в любом возможном продолжении выполнения из текущей точки выполнения в процедуре, сборщик мусора может (но не обязан) обрабатывать объект как больше не используемый. конечная заметка

  3. После того как объект становится доступным для финализации, в неопределенный более поздний момент выполняется его финализатор (§15.13), если таковой имеется. В обычных обстоятельствах метод завершения для объекта выполняется только один раз, хотя определяемые реализацией API могут позволить переопределить это поведение.
  4. После запуска средства завершения для объекта, если ни объект, ни ни какие-либо из его полей экземпляра не могут быть доступны любым возможным продолжением выполнения, включая выполнение методов завершения, объект считается недоступным, и объект становится доступным для коллекции.

    Примечание: Объект, который ранее был недоступен, может снова стать доступным благодаря своему финализатору. Ниже приведен пример этого. конечная заметка

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

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

Как и другие языки, предполагающие существование сборщика мусора, C# разработан таким образом, чтобы сборщик мусора мог реализовать широкий спектр политик управления памятью. C# не указывает ни временных ограничений для этого диапазона, ни порядка выполнения финализаторов. Независимо от того, запускаются ли финализаторы в рамках завершения приложения, определяется реализацией (§7.2).

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

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

class A
{
    ~A()
    {
        Console.WriteLine("Finalize instance of A");
    }
}

class B
{
    object Ref;
    public B(object o)
    {
        Ref = o;
    }

    ~B()
    {
        Console.WriteLine("Finalize instance of B");
    }
}

class Test
{
    static void Main()
    {
        B b = new B(new A());
        b = null;
        GC.Collect();
        GC.WaitForPendingFinalizers();
    }
}

создает экземпляр класса A и экземпляр класса B. Эти объекты становятся доступными для сборки мусора, когда переменной b присваивается значение null, так как после этого ни один пользовательский код не сможет к ним обратиться. Выходные данные могут быть либо

Finalize instance of A
Finalize instance of B

или

Finalize instance of B
Finalize instance of A

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

В тонких случаях различие между "право на завершение" и "право на сбор" может быть важным. Например,

class A
{
    ~A()
    {
        Console.WriteLine("Finalize instance of A");
    }

    public void F()
    {
        Console.WriteLine("A.F");
        Test.RefA = this;
    }
}

class B
{
    public A Ref;

    ~B()
    {
        Console.WriteLine("Finalize instance of B");
        Ref.F();
    }
}

class Test
{
    public static A RefA;
    public static B RefB;

    static void Main()
    {
        RefB = new B();
        RefA = new A();
        RefB.Ref = RefA;
        RefB = null;
        RefA = null;
        // A and B now eligible for finalization
        GC.Collect();
        GC.WaitForPendingFinalizers();
        // B now eligible for collection, but A is not
        if (RefA != null)
        {
            Console.WriteLine("RefA is not null");
        }
    }
}

В приведенной выше программе, если сборщик мусора решит запустить финализатор A перед финализатором B, то выходные данные этой программы могут быть:

Finalize instance of A
Finalize instance of B
A.F
RefA is not null

Обратите внимание, что хотя экземпляр A не использовался и финализатор A был выполнен, методы A (в данном случае, F) все еще могут вызываться из другого финализатора. Кроме того, обратите внимание, что запуск средства завершения может привести к тому, что объект снова станет пригодным для использования из основной программы. В этом случае выполнение финализатора B привело к тому, что экземпляр A, который ранее не использовался, стал доступным из активной ссылки Test.RefA. После вызова WaitForPendingFinalizers экземпляр B может быть собран, но экземпляр A не может быть собран из-за ссылки Test.RefA.

конец примера

7.10 Порядок выполнения

Выполнение программы C# продолжается таким образом, что побочные эффекты каждого выполняемого потока сохраняются в критически важных точках выполнения. Побочный эффект определяется как чтение или запись изменяющегося поля, запись в ненезависимую переменную, запись во внешний ресурс и исключение. Критические точки выполнения, в которых порядок этих побочных эффектов должен быть сохранен, являются ссылками на переменные поля (§15.5.4), операторы (§13.13), а также создание и завершение потока. lock Среда выполнения может изменить порядок выполнения программы C# с учетом следующих ограничений:

  • Зависимость данных сохраняется в потоке выполнения. То есть значение каждой переменной вычисляется так, как если бы все инструкции в потоке выполнялись в исходном порядке программы.
  • Правила упорядочения инициализации сохраняются (§15.5.5, §15.5.6).
  • Порядок побочных эффектов сохраняется в отношении переменных операций чтения и записи (§15.5.4). Кроме того, среда выполнения не должна оценивать часть выражения, если она может сделать вывод, что значение этого выражения не используется и не создаются необходимые побочные эффекты (включая любые, вызванные вызовом метода или доступом к volatile полю). Если выполнение программы прерывается асинхронным событием (например, исключением, вызванным другим потоком), не гарантируется, что наблюдаемые побочные эффекты отображаются в исходном порядке программы.