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


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

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

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

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

  • У него должно быть имя Main.
  • Это должно быть static.
  • Оно не должно быть универсальным.
  • Он должен быть объявлен в не универсальном типе. Если тип, объявляющий метод, является вложенным типом, ни один из его вложенных типов не может быть универсальным.
  • У него может быть модификатор, предоставленный async типом System.Threading.Tasks.Task возвращаемого метода.System.Threading.Tasks.Task<int>
  • Тип возвращаемого значения должен быть 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.8) (§15.6). Операторы (§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с одинаковым полным именем пространства имен являются членами одного объединенного пространства объявлений.
  • Каждый compilation_unit и namespace_body имеет пространство объявления псевдонима. Каждый extern_alias_directive и using_alias_directive compilation_unit или namespace_body вносит свой вклад в пространство объявления псевдонима (§14.5.2).
  • Каждый не частичный класс, структура или объявление интерфейса создает новое пространство объявления. Каждый частичный класс, структура или объявление интерфейса вносит вклад в пространство объявлений, совместно используемое всеми соответствующими частями в одной программе (§16.2.4). Имена вводятся в это пространство объявления через class_member_declarations, struct_member_declaration s, interface_member_declarationили type_parameters. За исключением объявлений перегруженных конструкторов экземпляров и объявлений статических конструкторов, класс или структуру не может содержать объявление члена с тем же именем, что и класс или структура. Класс, структура или интерфейс разрешает объявление перегруженных методов и индексаторов. Кроме того, класс или структура разрешает объявление перегруженных конструкторов экземпляров и операторов. Например, класс, структура или интерфейс могут содержать несколько объявлений методов с одинаковым именем, если эти объявления методов отличаются в их сигнатуре (§7.6). Обратите внимание, что базовые классы не вносят вклад в пространство объявления класса, а базовые интерфейсы не вносят вклад в пространство объявления интерфейса. Таким образом, производный класс или интерфейс может объявлять член с тем же именем, что и унаследованный элемент. Такой член, как говорят, скрывает унаследованный член.
  • Каждое объявление делегата создает новое пространство объявления. Имена вводятся в это пространство объявлений с помощью параметров (fixed_parameters и parameter_array) и type_parameters.
  • Каждое объявление перечисления создает новое пространство объявления. Имена вводятся в это пространство объявлений через enum_member_declarations.
  • Каждое объявление метода, объявление свойства, объявление доступа к свойствам, объявление индексатора, объявление доступа индексатора, объявление оператора, объявление экземпляра конструктора, анонимная функция и локальная функция создает новое пространство объявления, называемое локальным пространством объявлений переменной. Имена вводятся в это пространство объявлений с помощью параметров (fixed_parameters и parameter_array) и type_parameters. Метод доступа набора для свойства или индексатора вводит имя value в качестве параметра. Текст элемента функции, анонимной функции или локальной функции, если он есть, считается вложенным в локальное пространство объявления переменной. Если пространство объявления локальной переменной и вложенное пространство объявления переменной содержат элементы с тем же именем в области вложенного локального имени, внешнее локальное имя скрыто (§7.7.1) вложенным локальным именем.
  • Дополнительные локальные пространства объявлений переменных могут возникать в объявлениях-членах, анонимных функциях и локальных функциях. Имена вводятся в эти пространства объявлений с помощью шаблонов, declaration_expression s, declaration_statements и exception_specifier. Локальные пространства объявлений переменных могут быть вложенными, но это ошибка для пространства объявления локальной переменной и вложенного пространства объявления локальной переменной для хранения элементов с тем же именем. Таким образом, в вложенном пространстве объявления невозможно объявить локальную переменную, локальную функцию или константу с таким же именем, как параметр, параметр типа, локальная переменная, локальная функция или константа в заключающее пространство объявления. Для двух пробелов объявления можно содержать элементы с одинаковым именем, если ни одно пространство объявления не содержит другое. Локальные пространства объявлений создаются следующими конструкциями:
    • Каждая variable_initializer в объявлении полей и свойств представляет собственное локальное пространство объявления переменной, которое не вложено в другое локальное пространство объявления переменной.
    • Текст элемента функции, анонимной функции или локальной функции, если таковой имеется, создает пространство объявления локальной переменной, которое считается вложенным в локальное пространство объявления переменной функции.
    • Каждый constructor_initializer создает пространство объявления локальной переменной, вложенное в объявление конструктора экземпляра. Пространство объявления локальной переменной для текста конструктора в свою очередь вложено в это локальное пространство объявления переменной.
    • Каждый блок, switch_block, specific_catch_clause, iteration_statement и using_statement создает вложенное пространство объявления локальной переменной.
    • Каждый embedded_statement , который не является непосредственно частью statement_list , создает вложенное пространство объявления локальной переменной.
    • Каждый switch_section создает вложенное пространство объявления локальной переменной. Однако переменные, объявленные непосредственно в statement_list switch_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 находится в области в разделе коммутатора по умолчанию, несмотря на объявление, отображающееся в разделе переключателя для варианта 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 объявляется во внешнем блоке и не может быть переопределено во внутреннем блоке. Однако методы допустимы, HI так как два i's объявлены в отдельных не вложенных блоках.

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 путем включения модификатора в объявление члена. Интуитивно понятное значение private — "доступ ограничен типом, содержащим".

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

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

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

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

    Примечание. Тип, объявленный как член структуры, может иметь publicили internalprivateобъявленную специальные возможности, в то время как тип, объявленный как член пространства имен, может иметь только 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 этот тип недоступен.
  • В противном случае Mpublicдоступ разрешен.
  • В противном случае, если это так, доступ разрешен, если M он происходит в программе, в которой объявлен, или если он возникает в классе, производном от класса, в котором protected internalM объявлен и проходит через производный тип класса (M).
  • В противном случае, если это так, доступ разрешен, если M он происходит в классе, в котором объявлен, или если он возникает в классе, производном от класса, в котором protectedM объявлен и проходит через производный тип класса (M).
  • В противном случае, если это 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.XB.YB.CB.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экземпляра, и пусть MD будет классом, производным от B. Доступ к class_bodyDM может принимать одну из следующих форм:

  • Неквалифицированный type_name или primary_expression формы M.
  • Primary_expression формы, при условии, что тип является E.M или класс, производный от Eкласса, где T является класс или тип классаT, построенный на основеT.DD
  • Primary_expression формыbase.M.
  • Primary_expression формы 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 через экземпляры обоих ABи , так как в любом случае доступ происходит через экземпляр 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>.xD является допустимой, даже если класс D является производным от C<string>. конечная заметка

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

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

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

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

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

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

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

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

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

Примечание. Это ограничение позволяет программам C# легко переводиться в инфраструктуру common Language (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.outref Кроме того, обратите внимание, что возвращаемый тип и 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 с полным именем которого является, является namespace_body каждого N, полное имя которого или начинается с , за которым следует период.NN

  • Область имени, определяемая 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 не является транзитивным, но скорее влияет только на compilation_unit или namespace_body , в которых она происходит.

  • Область параметра типа, объявленного type_parameter_list на class_declaration (§15.2), является class_base, type_parameter_constraints_clauses и 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.

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

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

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

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

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

  • Область локальной константы, объявленной в 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 по-прежнему ссылается на переменную Gэкземпляраi. Внутри локальной функции 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?
    | namespace_or_type_name '.' identifier type_argument_list?
    | qualified_alias_member
    ;

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

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

Type_name — это namespace_or_type_name, ссылающийся на тип. Следующее разрешение, как описано ниже, namespace_or_type_name type_name должен ссылаться на тип или в противном случае возникает ошибка во время компиляции.

Если namespace_or_type_name является qualified_alias_member его значение, как описано в §14.8.1. В противном случае namespace_or_type_name имеет одну из четырех форм:

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

где I является одним идентификатором, N является namespace_or_type_name и <A₁, ..., Aₓ> является необязательным type_argument_list. Если type_argument_list не задано, следует x учитывать нулевое значение.

Значение namespace_or_type_name определяется следующим образом:

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

      Примечание. Элементы без типов (константы, поля, методы, свойства, индексаторы, операторы, конструкторы экземпляров, методы завершения и статические конструкторы) и элементы типов с другим числом параметров типа игнорируются при определении значения 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 является неоднозначным и возникает ошибка во время компиляции.
        • В противном случае namespace_or_type_name ссылается на пространство имен, именованное I в N.
      • В противном случае, если N содержится доступный тип с параметрами имени I и x типа, то:
        • Если x значение равно нулю, а расположение, в котором происходит namespace_or_type_name, заключено объявлением пространства имен, N а объявление пространства имен содержит extern_alias_directive или using_alias_directive, которое связывает имя I с пространством имен или типом, то namespace_or_type_name является неоднозначным и возникает ошибка во время компиляции.
        • В противном случае namespace_or_type_name относится к типу, созданному с заданными аргументами типа.
      • В противном случае, если расположение, в котором происходит namespace_or_type_name , заключено в объявление пространства имен для N:
        • Если x значение равно нулю, а объявление пространства имен содержит extern_alias_directive или using_alias_directive , которая связывает имя I с импортированным пространством имен или типом, то namespace_or_type_name ссылается на это пространство имен или тип.
        • В противном случае, если пространства имен, импортированные using_namespace_directiveобъявления пространства имен, содержат ровно один тип с параметрами имени I и x типа, то namespace_or_type_name ссылается на этот тип, созданный с заданными аргументами типа.
        • В противном случае, если пространства имен, импортированные using_namespace_directiveобъявления пространства имен, содержат несколько типов с параметрами имени I и x типа, то namespace_or_type_name неоднозначно и возникает ошибка.
    • В противном случае namespace_or_type_name не определен и возникает ошибка во время компиляции.
  • В противном случае namespace_or_type_name имеет форму или форму N.IN.I<A₁, ..., Aₓ>. N сначала разрешается как namespace_or_type_name. Если разрешение N не выполнено успешно, возникает ошибка во время компиляции. N.I В противном случае или N.I<A₁, ..., Aₓ> разрешается следующим образом:
    • Если x значение равно нулю и относится к пространству имен и NN содержит вложенное пространство имен с именем I, то namespace_or_type_name ссылается на это вложенное пространство имен.
    • В противном случае, если N ссылается на пространство имен и N содержит доступный тип с параметрами имени I и x типа, то namespace_or_type_name ссылается на этот тип, созданный с помощью аргументов данного типа.
    • В противном случае, если N относится к классу (возможно созданному) классу или типу структуры и N любому из его базовых классов содержится вложенный тип с параметрами имени I и x типа, то namespace_or_type_name ссылается на этот тип, созданный с заданными аргументами типа. Если существует несколько таких типов, выбирается тип, объявленный в более производного типа.

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

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

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 Неквалифицированные имена

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

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

7.8.3 Полные имена

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

  • Если N он является членом глобального пространства имен, его полное имя равно N.
  • В противном случае полное имя — S.NS это полное имя пространства имен или объявления типа, в котором 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

or

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 средства завершения. Кроме того, обратите внимание, что запуск средства завершения может привести к тому, что объект станет пригодным для использования из программы mainline снова. В этом случае выполнение 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). Кроме того, среда выполнения не должна оценивать часть выражения, если она может выводить, что значение этого выражения не используется, и что не требуются побочные эффекты создаются (включая любые вызванные вызовом метода или доступом к переменному полю). Если выполнение программы прерывается асинхронным событием (например, исключением, вызванным другим потоком), не гарантируется, что наблюдаемые побочные эффекты отображаются в исходном порядке программы.