partial class c что это
Programming stuff
Страницы
понедельник, 14 марта 2011 г.
Частичные классы
Частичные классы (partial classes) (*) – это весьма простая конструкция языка C#, которая позволяет определить один класс в нескольких файлах (**). Это далеко не rocket science, но существует пара интересных сценариев их применения, которые являются не столь очевидными с первого взгляда. Но об этом позже, а начнем мы лучше с простого объяснения, что это такое и с чем его едят.
Общие сведения
Давайте вспомним старые добрые дни C# 1.0, когда спецификация языка была вдвое тоньше, возможностей языка было на порядок меньше, да и не было никаких LINQ-ов и всех остальных dynamic-ов (и вопросов на собеседовании, соответственно, тоже). Но даже тогда компилятор языка старался скрасить наши с вами серые будни и усердно выполнял всякую унылую работу, но делал он это не втихаря, путем генерации всяких там анонимных классов или методов, а путем генерации C# кода с помощь дизайнеров. И все бы ничего, но с этим подходом зачастую приходили и некоторые проблемы, которые, в основном сводились к ярому желанию разработчика расширить этот код и к ярому сопротивлению этого кода от подобного ручного вмешательства. Другими словами, внесение любых изменений в автосгенерированный код всегда вели к беде, ибо тут же прибивались компилятором при повторной генерации.
В те далекие дни существовало два простых способа борьбы с этой проблемой. Первый способ активно применялся, например, дизайнером Windows Forms, когда весь сгенерированный код формы помещался в отдельный регион, изменять который было чревато, а весь остальной пользовательский код располагался в оставшейся части файла (и, соответственно, класса). Другой подход наиболее часто применялся при работе с датасетами (да, это ORM для бедных, точнее и не ORM вовсе, но это уже и не важно), когда для расширения функционала нужно было создать класс-наследник и добавить в него весь необходимый дополнительный функционал.
Не сложно догадаться, что ни один из этих вариантов нельзя назвать очень уж удачным, так что не удивительно, что разработчики языка решили решить (во как!) эту проблему и предоставили возможность реализовывать один класс в нескольких файлах. В этом случае одним файлом ведает компилятор и творит с ним всяческие непотребства, ну а со вторым чем-то схожим занимается девелопер.
Итак, вот простой пример:
Обратите внимание, что только в одном файле указан наследник текущего класса (в данном случае, класс Form), и область видимости этого класса (ключевое слово public); это упрощает контроль этих аспектов пользователем, оставляя код, сгенерированный компилятором в нетронутом виде. Кроме того, как не сложно заметить, класс CustomForm все еще остается одним классом, что позволяет вызывать функции, определенные в одном файле (в данном случае, это функция InitializeComponent) из другого файла.
Частичные классы и выделение абстракций
Не пугайтесь страшного названия, rocket science в этой заметке так и не появится, просто более удачного названия подобрать не смог. Дело все в том, что частичные классы умеют делать нечто не совсем интуитивно понятное с первого взгляда. А именно, объявления частичных классов совпадать не должны, это значит, что в одном файле вы можете «объявить» реализацию классом некоторого интерфейса, а во втором файле – реализовать этот интерфейс, путем определения всех его методов (хотя можно часть методов реализовать в одном файле, а часть в другом; я думаю, что идея понятна):
И что в этом такого, скажете вы? Да, в общем-то, ничего особенного, но иногда это может очень здорово помочь при работе с автосгенерированным кодом. Давайте представим, что у нас есть некоторые сгенерированные компилятором классы (будь-то, классы, генерируемые по XSD, классы-сущности базы данных или что-то еще), и так уж вышло, что каждый из этих, совершенно не связанных между собой классов, содержит некоторую общую функциональность в виде набора свойств или методов, которые, в общем-то, делают одно и то же. Но поскольку все они генерируются компилятором, у вас (и у нас, кстати, тоже) нет возможности обрабатывать их обобщенным образом с помощью некоторого базового класса или интерфейса, в результате ваш код (и наш иногда тоже) начинает обрастать неприглядными конструкциями следующего вида:
Но, на самом деле, вам никто не мешает создать собственный интерфейс, который будет содержать всю общую функциональность, в виде свойств или методов, а затем просто добавить этот интерфейс к вашему определению частичного класса, не трогая при этом сгенерированный код. Тогда, если в каждом сгенерированном классе содержится свойство Name, вы можете добавить интерфейс IName со свойством Name и обрабатывать эти классы одним методом:
Частичные классы в юнит-тестировании
Чисто теоретически применение частичных классов не ограничивается автоматически сгенерированным кодом. Это самая обычная возможность языка программирования, и никто не мешает вам разбивать ваши собственные классы на несколько файлов. Почему «чисто теоретически»? Потому что, в большинстве случаев, если вы видите преимущества от использования частичных классов для реализации ваших собственных бизнес-сущностей, возникает некоторое подозрение, что эти самые сущности делают слишком много и, скорее всего, будет не лишним разбить их на несколько составляющих. (Хотя, нужно признать, из этого правила бывают и исключения.)
И хотя создание крупных классов в подавляющем большинстве случаев ни к чему хорошему не приводит, наличие достаточно увесистых классов юнит-тестов, в несколько раз превосходящих по объему тестируемые классы, дело обычное. Даже для небольшого класса может быть достаточно много сценариев тестирования, которые вполне можно сгруппировать по некоторому признаку (например, по некоторому аспекту поведения или чему-то в этом роде). В этом случае, мы можем разбить юнит-тест на несколько классов, и тогда придется запускать каждый из них независимо, а можем разбить один юнит-тест на несколько файлов с «частичными» классами. В этом случае, мы сможем запускать все тесты за раз, поскольку у нас будет всего один класс, но зато мы избавимся от громадных файлов. (Да, я знаю о наличии регионов, но мне подход с частичными классами нравится больше).
«Объединение» частичных классов в Solution Explorer-е
Внимательный разработчик наверняка давно уже заметил, что наша с вами любимая среда разработки, когда речь заходит об автоматически сгенерированном коде, умеет хитрым образом «объединять под одним крылом» частичные классы. Это выражается в том, что в Solution Explorer-е файлы, сгенерированные компилятором, находится «под» основным файлом, с которым работает разработчик. Но раз так, то наверняка мы сможем применять эту же технику для группировки наших собственных частичных классов.
И действительно, для этого достаточно немного поменять файл проекта и для зависимого файла добавить элемент с именем DependentUpon:
Помимо ручного редактирования файла проекта, можно порыться в Visual Studio Gallery и поискать расширение с подобной функциональностью. Как минимум одно расширение, с незамысловатым названием VSCommands 2010 поддерживает группировку нескольких файлов именно таким образом. После установки этого расширения в контекстном меню в Solution Explorer-е появятся дополнительные пункты меню “Group Items” и “Ungroup Items”, соответственно. Но не зависимо от того, каким именно способом вы будете «объединять» файлы, результат будет следующий (рисунок 1):
Рисунок 1 – Группировка файлов в Solution Explorer
Заключение
Ну вот, пожалуй, и все. Частичные классы – это действительно весьма простая возможность языка C#, которая в подавляющем большинстве случаев используется совместно с дизайнерами и автоматически генерируемым кодом. Но, тем не менее, существуют и другие интересные контексты использования этой возможности, которые могут быть не столь очевидными с первого взгляда.
(*) Да, как правильно заметили в комментариях, частичными бывают не только классы, но еще и структуры и интерфейсы. Так что все, что здесь говорится о классах, применимо еще и для структур с интерфейсами.
(**) Вообще-то, частичные классы могут объявляться и в одном файле:
Но вот польза в таком объявлении весьма сомнительна.
Частичные классы в C #
Частичный класс — это особенность C #. Он обеспечивает особую возможность реализовать функциональность одного класса в нескольких файлах, и все эти файлы объединяются в один файл класса при компиляции приложения. Частичный класс создается с использованием частичного ключевого слова. Это ключевое слово также полезно для разделения функциональности методов, интерфейсов или структуры на несколько файлов.
Синтаксис:
Важные моменты:
Пример: здесь мы берем класс с именем Geeks и разделяем определение класса Geeks на два разных файла с именем Geeks1.cs и Geeks2.cs как показано ниже:
Geeks1.cs
public partial class Geeks <
private string Author_name;
private int Total_articles;
public Geeks( string a, int t)
Geeks2.cs
public partial class Geeks <
public void Display()
Console.WriteLine( «Author’s name is : » + Author_name);
Console.WriteLine( «Total number articles is : » + Total_articles);
Когда мы выполняем приведенный выше код, компилятор объединяет Geeks1.cs и Geeks2.cs в один файл, то есть Geeks как показано ниже.
Гики Этот класс может содержать метод Main. Для простоты здесь метод Main () не включен.
public class Geeks <
private string Author_name;
private int Total_articles;
public Geeks( string a, int t)
public void Display()
Console.WriteLine( «Author’s name is : » + Author_name);
Console.WriteLine( «Total number articles is : » + Total_articles);
Преимущества:
Partial методы в C#
Какое практическое применение есть для partial методов?
3 ответа 3
Когда есть partial class, сигнатуру методов можно определить в одном файле, а реализацию в другом. Например
В одном файле может быть:
Это позволяет вызывать Bar в первом файле не беспокоясь реализован он где-нибудь или нет. Если Bar так и не был нигде реализован, то все его вызовы будут удалены во время компиляции (отсюда):
Partial методы позволяют реализующему одну часть класса определять методы, похожие на события. Реализующий вторую часть класса решает, будет ли он реализовывать метод или нет. Если метод не был реализован, то компилятор удалит сигнатуру метода и все его вызовы. Вызовы метода, включая любые результаты, которые могут получиться при вычисление аргументов при вызове будут проигнорированы в run time. Поэтому любой код в partial class может свободно использовать partial method, даже если он не будет реализован. В результате, если метод был вызван без реализации, не будет никаких compile-time или run-time ошибок.
Partial method особенно полезны при настройке сгенерированного кода. Они позволяют зарезервировать имя и сигнатуру метода, чтобы сгенерированный код мог вызвать метод, но разработчик может сам решить реализовывать ли этот метод. Подобно partial classes, partial method позволяют совмещать сгенерированный код и код написанный разработчиком без затрат в run-time.
Так что может быть сгенерированный код, вызывающий partial method (определенный без реализации в сгенерированном коде) и разработчик сам решает хочет ли он / нужно ли ему расширять partial class и реализовывать partial method.
Partial class definition on C++?
For me it seems quite useful for definining multi-platform classes that have common functions between them that are platform-independent because inheritance is a cost to pay that is non-useful for multi-platform classes.
I mean you will never have two multi-platform specialization instances at runtime, only at compile time. Inheritance could be useful to fulfill your public interface needs but after that it won’t add anything useful at runtime, just costs.
Also you will have to use an ugly #ifdef to use the class because you can’t make an instance from an abstract class:
Then let’s say for win32:
Let’s think that both win32Method() and macMethod() calls genericMethod(), and you will have to use the class like this:
Now thinking a while the inheritance was only useful for giving them both a genericMethod() that is dependent on the platform-specific one, but you have the cost of calling two constructors because of that. Also you have ugly #ifdef scattered around the code.
That’s why I was looking for partial classes. I could at compile-time define the specific platform dependent partial end, of course that on this silly example I still need an ugly #ifdef inside genericMethod() but there is another ways to avoid that.
19 Answers 19
This is not possible in C++, it will give you an error about redefining already-defined classes. If you’d like to share behavior, consider inheritance.
You can’t partially define classes in C++.
Here’s a way to get the «polymorphism, where there’s only one subclass» effect you’re after without overhead and with a bare minimum of #define or code duplication. It’s called simulated dynamic binding:
Then off in some other headers you’ll have:
This gives you proper polymorphism at compile time. genericTest can non-virtually call doClassDependentThing in a way that gives it the platform version, (almost like a virtual method), and when win32Method calls genericMethod it of course gets the base class version.
Client code just creates instances of Test, and can call methods on them which are either in genericTest or in the correct version for the platform. To help with type safety in code which doesn’t care about the platform and doesn’t want to accidentally make use of platform-specific calls, you could additionally do:
You have to be a bit careful using BaseTest, but not much more so than is always the case with base classes in C++. For instance, don’t slice it with an ill-judged pass-by-value. And don’t instantiate it directly, because if you do and call a method that ends up attempting a «fake virtual» call, you’re in trouble. The latter can be enforced by ensuring that all of genericTest’s constructors are protected.
or you could try PIMPL
common header file:
Then create the cpp files doing the custom implementations that are platform specific.
Now Funcs is a class known at compile-time, so no overheads are caused by abstract base classes or whatever.
As written, it is not possible.
But, you may want to look up a technique called «Policy Classes». Basically, you make micro-classes (that aren’t useful on their own) then glue them together at some later point.
Either use inheritance, as Jamie said, or #ifdef to make different parts compile on different platforms.
For me it seems quite useful for definining multi-platform classes that have common functions between them that are platform-independent.
Except developers have been doing this for decades without this ‘feature’.
I believe partial was created because Microsoft has had, for decades also, a bad habit of generating code and handing it off to developers to develop and maintain.
Generated code is often a maintenance nightmare. What habits to that entire MFC generated framework when you need to bump your MFC version? Or how do you port all that code in *.designer.cs files when you upgrade Visual Studio?
Most other platforms rely more heavily on generating configuration files instead that the user/developer can modify. Those, having a more limited vocabulary and not prone to be mixed with unrelated code. The configuration files can even be inserted in the binary as a resource file if deemed necessary.
I have never seen ‘partial’ used in a place where inheritance or a configuration resource file wouldn’t have done a better job.
Since headers are just textually inserted, one of them could omit the «class Test <" and ">» and be #included in the middle of the other.
I’ve actually seen this in production code, albeit Delphi not C++. It particularly annoyed me because it broke the IDE’s code navigation features.
Dirty but practical way is using #include preprocessor:
Suppose that I have:
MyClass_Part1.hpp, MyClass_Part2.hpp and MyClass_Part3.hpp
Theoretically someone can develop a GUI tool that reads all these hpp files above and creates the following hpp file:
The user can theoretically tell the GUI tool where is each input hpp file and where to create the output hpp file.
Of course that the developer can theoretically program the GUI tool to work with any varying number of hpp files (not necessarily 3 only) whose prefix can be any arbitrary string (not necessarily «MyClass» only).
Just don’t forget to #include to use the class «MyClass» in your projects.
As written, it is not possible, and in some cases it is actually annoying.
There was an official proposal to the ISO, with in mind embedded software, in particular to avoid the RAM ovehead given by both inheritance and pimpl pattern (both approaches require an additional pointer for each object):
Unfortunately the proposal was rejected.
Declaring a class body twice will likely generate a type redefinition error. If you’re looking for a work around. I’d suggest #ifdef’ing, or using an Abstract Base Class to hide platform specific details.
You can get something like partial classes using template specialization and partial specialization. Before you invest too much time, check your compiler’s support for these. Older compilers like MSC++ 6.0 didn’t support partial specialization.
This is not possible in C++, it will give you an error about redefining already-defined classes. If you’d like to share behavior, consider inheritance.
I do agree on this. Partial classes is strange construct that makes it very difficult to maintain afterwards. It is difficult to locate on which partial class each member is declared and redefinition or even reimplementation of features are hard to avoid.
Do you want to extend the std::vector, you have to inherit from it. This is because of several reasons. First of all you change the responsibility of the class and (properly?) its class invariants. Secondly, from a security point of view this should be avoided. Consider a class that handles user authentication.
A lot of other reasons could be mentioned.
Let platform independent and platform dependent classes/functions be each-others friend classes/functions. 🙂
And their separate name identifiers permit finer control over instantiation, so coupling is looser. Partial breaks encapsulation foundation of OO far too absolutely, whereas the requisite friend declarations barely relax it just enough to facilitate multi-paradigm Separation of Concerns like Platform Specific aspects from Domain-Specific platform independent ones.
I’ve been doing something similar in my rendering engine. I have a templated IResource interface class from which a variety of resources inherit (stripped down for brevity):
The Create static function calls back to a templated ResourceManager class that is responsible for loading, unloading, and storing instances of the type of resource it manages with unique keys, ensuring duplicate calls are simply retrieved from the store, rather than reloaded as separate resources.
Concrete resource classes inherit from IResource utilizing the CRTP. ResourceManagers specialized to each resource type are declared as friends to those classes, so that the ResourceManager’s Load function can call the concrete resource’s Initialize function. One such resource is a texture class, which further uses a pImpl idiom to hide its privates:
Classes
A class is a data structure that may contain data members (constants and fields), function members (methods, properties, events, indexers, operators, instance constructors, destructors and static constructors), and nested types. Class types support inheritance, a mechanism whereby a derived class can extend and specialize a base class.
Class declarations
A class_declaration is a type_declaration (Type declarations) that declares a new class.
A class declaration cannot supply type_parameter_constraints_clauses unless it also supplies a type_parameter_list.
A class declaration that supplies a type_parameter_list is a generic class declaration. Additionally, any class nested inside a generic class declaration or a generic struct declaration is itself a generic class declaration, since type parameters for the containing type must be supplied to create a constructed type.
Class modifiers
A class_declaration may optionally include a sequence of class modifiers:
It is a compile-time error for the same modifier to appear multiple times in a class declaration.
The new modifier is permitted on nested classes. It specifies that the class hides an inherited member by the same name, as described in The new modifier. It is a compile-time error for the new modifier to appear on a class declaration that is not a nested class declaration.
Abstract classes
The abstract modifier is used to indicate that a class is incomplete and that it is intended to be used only as a base class. An abstract class differs from a non-abstract class in the following ways:
When a non-abstract class is derived from an abstract class, the non-abstract class must include actual implementations of all inherited abstract members, thereby overriding those abstract members. In the example
Sealed classes
The sealed modifier is used to prevent derivation from a class. A compile-time error occurs if a sealed class is specified as the base class of another class.
A sealed class cannot also be an abstract class.
The sealed modifier is primarily used to prevent unintended derivation, but it also enables certain run-time optimizations. In particular, because a sealed class is known to never have any derived classes, it is possible to transform virtual function member invocations on sealed class instances into non-virtual invocations.
Static classes
The static modifier is used to mark the class being declared as a static class. A static class cannot be instantiated, cannot be used as a type and can contain only static members. Only a static class can contain declarations of extension methods (Extension methods).
A static class declaration is subject to the following restrictions:
It is a compile-time error to violate any of these restrictions.
A static class has no instance constructors. It is not possible to declare an instance constructor in a static class, and no default instance constructor (Default constructors) is provided for a static class.
The members of a static class are not automatically static, and the member declarations must explicitly include a static modifier (except for constants and nested types). When a class is nested within a static outer class, the nested class is not a static class unless it explicitly includes a static modifier.
Referencing static class types
A namespace_or_type_name (Namespace and type names) is permitted to reference a static class if
A primary_expression (Function members) is permitted to reference a static class if
In any other context it is a compile-time error to reference a static class. For example, it is an error for a static class to be used as a base class, a constituent type (Nested types) of a member, a generic type argument, or a type parameter constraint. Likewise, a static class cannot be used in an array type, a pointer type, a new expression, a cast expression, an is expression, an as expression, a sizeof expression, or a default value expression.
Partial modifier
The partial modifier is used to indicate that this class_declaration is a partial type declaration. Multiple partial type declarations with the same name within an enclosing namespace or type declaration combine to form one type declaration, following the rules specified in Partial types.
Having the declaration of a class distributed over separate segments of program text can be useful if these segments are produced or maintained in different contexts. For instance, one part of a class declaration may be machine generated, whereas the other is manually authored. Textual separation of the two prevents updates by one from conflicting with updates by the other.
Type parameters
A type parameter is a simple identifier that denotes a placeholder for a type argument supplied to create a constructed type. A type parameter is a formal placeholder for a type that will be supplied later. By contrast, a type argument (Type arguments) is the actual type that is substituted for the type parameter when a constructed type is created.
Each type parameter in a class declaration defines a name in the declaration space (Declarations) of that class. Thus, it cannot have the same name as another type parameter or a member declared in that class. A type parameter cannot have the same name as the type itself.
Class base specification
A class declaration may include a class_base specification, which defines the direct base class of the class and the interfaces (Interfaces) directly implemented by the class.
The base class specified in a class declaration can be a constructed class type (Constructed types). A base class cannot be a type parameter on its own, though it can involve the type parameters that are in scope.
Base classes
For a constructed class type, if a base class is specified in the generic class declaration, the base class of the constructed type is obtained by substituting, for each type_parameter in the base class declaration, the corresponding type_argument of the constructed type. Given the generic class declarations
The direct base class of a class type must be at least as accessible as the class type itself (Accessibility domains). For example, it is a compile-time error for a public class to derive from a private or internal class.
is erroneous because the class depends on itself. Likewise, the example
is in error because the classes circularly depend on themselves. Finally, the example
Note that a class does not depend on the classes that are nested within it. In the example
B depends on A (because A is both its direct base class and its immediately enclosing class), but A does not depend on B (since B is neither a base class nor an enclosing class of A ). Thus, the example is valid.
It is not possible to derive from a sealed class. In the example
Interface implementations
A class_base specification may include a list of interface types, in which case the class is said to directly implement the given interface types. Interface implementations are discussed further in Interface implementations.
Type parameter constraints
Generic type and method declarations can optionally specify type parameter constraints by including type_parameter_constraints_clauses.
The reference type constraint specifies that a type argument used for the type parameter must be a reference type. All class types, interface types, delegate types, array types, and type parameters known to be a reference type (as defined below) satisfy this constraint.
The value type constraint specifies that a type argument used for the type parameter must be a non-nullable value type. All non-nullable struct types, enum types, and type parameters having the value type constraint satisfy this constraint. Note that although classified as a value type, a nullable type (Nullable types) does not satisfy the value type constraint. A type parameter having the value type constraint cannot also have the constructor_constraint.
Pointer types are never allowed to be type arguments and are not considered to satisfy either the reference type or value type constraints.
If a constraint is a class type, an interface type, or a type parameter, that type specifies a minimal «base type» that every type argument used for that type parameter must support. Whenever a constructed type or generic method is used, the type argument is checked against the constraints on the type parameter at compile-time. The type argument supplied must satisfy the conditions described in Satisfying constraints.
A class_type constraint must satisfy the following rules:
A type specified as an interface_type constraint must satisfy the following rules:
In either case, the constraint can involve any of the type parameters of the associated type or method declaration as part of a constructed type, and can involve the type being declared.
Any class or interface type specified as a type parameter constraint must be at least as accessible (Accessibility constraints) as the generic type or method being declared.
A type specified as a type_parameter constraint must satisfy the following rules:
In addition there must be no cycles in the dependency graph of type parameters, where dependency is a transitive relation defined by:
Given this relation, it is a compile-time error for a type parameter to depend on itself (directly or indirectly).
Any constraints must be consistent among dependent type parameters. If type parameter S depends on type parameter T then:
If the where clause for a type parameter includes a constructor constraint (which has the form new() ), it is possible to use the new operator to create instances of the type (Object creation expressions). Any type argument used for a type parameter with a constructor constraint must have a public parameterless constructor (this constructor implicitly exists for any value type) or be a type parameter having the value type constraint or constructor constraint (see Type parameter constraints for details).
The following are examples of constraints:
The following example is in error because it causes a circularity in the dependency graph of the type parameters:
The following examples illustrate additional invalid situations:
The effective base class of a type parameter T is defined as follows:
For the purpose of these rules, if T has a constraint V that is a value_type, use instead the most specific base type of V that is a class_type. This can never happen in an explicitly given constraint, but may occur when the constraints of a generic method are implicitly inherited by an overriding method declaration or an explicit implementation of an interface method.
These rules ensure that the effective base class is always a class_type.
The effective interface set of a type parameter T is defined as follows:
Values of a constrained type parameter type can be used to access the instance members implied by the constraints. In the example
Class body
The class_body of a class defines the members of that class.
Partial types
A type declaration can be split across multiple partial type declarations. The type declaration is constructed from its parts by following the rules in this section, whereupon it is treated as a single declaration during the remainder of the compile-time and run-time processing of the program.
Each part of a partial type declaration must include a partial modifier. It must have the same name and be declared in the same namespace or type declaration as the other parts. The partial modifier indicates that additional parts of the type declaration may exist elsewhere, but the existence of such additional parts is not a requirement; it is valid for a type with a single declaration to include the partial modifier.
All parts of a partial type must be compiled together such that the parts can be merged at compile-time into a single type declaration. Partial types specifically do not allow already compiled types to be extended.
Nested types may be declared in multiple parts by using the partial modifier. Typically, the containing type is declared using partial as well, and each part of the nested type is declared in a different part of the containing type.
The partial modifier is not permitted on delegate or enum declarations.
Attributes
The attributes of a partial type are determined by combining, in an unspecified order, the attributes of each of the parts. If an attribute is placed on multiple parts, it is equivalent to specifying the attribute multiple times on the type. For example, the two parts:
are equivalent to a declaration such as:
Attributes on type parameters combine in a similar fashion.
Modifiers
If one or more partial declarations of a nested type include a new modifier, no warning is reported if the nested type hides an inherited member (Hiding through inheritance).
If one or more partial declarations of a class include an abstract modifier, the class is considered abstract (Abstract classes). Otherwise, the class is considered non-abstract.
If one or more partial declarations of a class include a sealed modifier, the class is considered sealed (Sealed classes). Otherwise, the class is considered unsealed.
Note that a class cannot be both abstract and sealed.
When the unsafe modifier is used on a partial type declaration, only that particular part is considered an unsafe context (Unsafe contexts).
Type parameters and constraints
If a generic type is declared in multiple parts, each part must state the type parameters. Each part must have the same number of type parameters, and the same name for each type parameter, in order.
When a partial generic type declaration includes constraints ( where clauses), the constraints must agree with all other parts that include constraints. Specifically, each part that includes constraints must have constraints for the same set of type parameters, and for each type parameter the sets of primary, secondary, and constructor constraints must be equivalent. Two sets of constraints are equivalent if they contain the same members. If no part of a partial generic type specifies type parameter constraints, the type parameters are considered unconstrained.
is correct because those parts that include constraints (the first two) effectively specify the same set of primary, secondary, and constructor constraints for the same set of type parameters, respectively.
Base class
When a partial class declaration includes a base class specification it must agree with all other parts that include a base class specification. If no part of a partial class includes a base class specification, the base class becomes System.Object (Base classes).
Base interfaces
The set of base interfaces for a type declared in multiple parts is the union of the base interfaces specified on each part. A particular base interface may only be named once on each part, but it is permitted for multiple parts to name the same base interface(s). There must only be one implementation of the members of any given base interface.
Typically, each part provides an implementation of the interface(s) declared on that part; however, this is not a requirement. A part may provide the implementation for an interface declared on a different part:
Members
With the exception of partial methods (Partial methods), the set of members of a type declared in multiple parts is simply the union of the set of members declared in each part. The bodies of all parts of the type declaration share the same declaration space (Declarations), and the scope of each member (Scopes) extends to the bodies of all the parts. The accessibility domain of any member always includes all the parts of the enclosing type; a private member declared in one part is freely accessible from another part. It is a compile-time error to declare the same member in more than one part of the type, unless that member is a type with the partial modifier.
The ordering of members within a type is rarely significant to C# code, but may be significant when interfacing with other languages and environments. In these cases, the ordering of members within a type declared in multiple parts is undefined.
Partial methods
Partial methods can be defined in one part of a type declaration and implemented in another. The implementation is optional; if no part implements the partial method, the partial method declaration and all calls to it are removed from the type declaration resulting from the combination of the parts.
There are two kinds of partial method declarations: If the body of the method declaration is a semicolon, the declaration is said to be a defining partial method declaration. If the body is given as a block, the declaration is said to be an implementing partial method declaration. Across the parts of a type declaration there can be only one defining partial method declaration with a given signature, and there can be only one implementing partial method declaration with a given signature. If an implementing partial method declaration is given, a corresponding defining partial method declaration must exist, and the declarations must match as specified in the following:
An implementing partial method declaration can appear in the same part as the corresponding defining partial method declaration.
If no part of a partial type declaration contains an implementing declaration for a given partial method, any expression statement invoking it is simply removed from the combined type declaration. Thus the invocation expression, including any constituent expressions, has no effect at run-time. The partial method itself is also removed and will not be a member of the combined type declaration.
If an implementing declaration exist for a given partial method, the invocations of the partial methods are retained. The partial method gives rise to a method declaration similar to the implementing partial method declaration except for the following:
If a defining declaration but not an implementing declaration is given for a partial method M, the following restrictions apply:
Partial methods are useful for allowing one part of a type declaration to customize the behavior of another part, e.g., one that is generated by a tool. Consider the following partial class declaration:
If this class is compiled without any other parts, the defining partial method declarations and their invocations will be removed, and the resulting combined class declaration will be equivalent to the following:
Assume that another part is given, however, which provides implementing declarations of the partial methods:
Then the resulting combined class declaration will be equivalent to the following:
Name binding
Although each part of an extensible type must be declared within the same namespace, the parts are typically written within different namespace declarations. Thus, different using directives (Using directives) may be present for each part. When interpreting simple names (Type inference) within one part, only the using directives of the namespace declaration(s) enclosing that part are considered. This may result in the same identifier having different meanings in different parts:
Class members
The members of a class consist of the members introduced by its class_member_declarations and the members inherited from the direct base class.
The members of a class type are divided into the following categories:
Members that can contain executable code are collectively known as the function members of the class type. The function members of a class type are the methods, properties, events, indexers, operators, instance constructors, destructors, and static constructors of that class type.
A class_declaration creates a new declaration space (Declarations), and the class_member_declarations immediately contained by the class_declaration introduce new members into this declaration space. The following rules apply to class_member_declarations:
The inherited members of a class type (Inheritance) are not part of the declaration space of a class. Thus, a derived class is allowed to declare a member with the same name or signature as an inherited member (which in effect hides the inherited member).
The instance type
Each class declaration has an associated bound type (Bound and unbound types), the instance type. For a generic class declaration, the instance type is formed by creating a constructed type (Constructed types) from the type declaration, with each of the supplied type arguments being the corresponding type parameter. Since the instance type uses the type parameters, it can only be used where the type parameters are in scope; that is, inside the class declaration. The instance type is the type of this for code written inside the class declaration. For non-generic classes, the instance type is simply the declared class. The following shows several class declarations along with their instance types:
Members of constructed types
The non-inherited members of a constructed type are obtained by substituting, for each type_parameter in the member declaration, the corresponding type_argument of the constructed type. The substitution process is based on the semantic meaning of type declarations, and is not simply textual substitution.
For example, given the generic class declaration
the constructed type Gen > has the following members:
Within instance function members, the type of this is the instance type (The instance type) of the containing declaration.
All members of a generic class can use type parameters from any enclosing class, either directly or as part of a constructed type. When a particular closed constructed type (Open and closed types) is used at run-time, each use of a type parameter is replaced with the actual type argument supplied to the constructed type. For example:
Inheritance
A class inherits the members of its direct base class type. Inheritance means that a class implicitly contains all members of its direct base class type, except for the instance constructors, destructors and static constructors of the base class. Some important aspects of inheritance are:
The inherited member of a constructed class type are the members of the immediate base class type (Base classes), which is found by substituting the type arguments of the constructed type for each occurrence of the corresponding type parameters in the class_base specification. These members, in turn, are transformed by substituting, for each type_parameter in the member declaration, the corresponding type_argument of the class_base specification.
The new modifier
A class_member_declaration is permitted to declare a member with the same name or signature as an inherited member. When this occurs, the derived class member is said to hide the base class member. Hiding an inherited member is not considered an error, but it does cause the compiler to issue a warning. To suppress the warning, the declaration of the derived class member can include a new modifier to indicate that the derived member is intended to hide the base member. This topic is discussed further in Hiding through inheritance.
If a new modifier is included in a declaration that doesn’t hide an inherited member, a warning to that effect is issued. This warning is suppressed by removing the new modifier.
Access modifiers
Constituent types
Types that are used in the declaration of a member are called the constituent types of that member. Possible constituent types are the type of a constant, field, property, event, or indexer, the return type of a method or operator, and the parameter types of a method, indexer, operator, or instance constructor. The constituent types of a member must be at least as accessible as that member itself (Accessibility constraints).
Static and instance members
Members of a class are either static members or instance members. Generally speaking, it is useful to think of static members as belonging to class types and instance members as belonging to objects (instances of class types).
When a field, method, property, event, operator, or constructor declaration includes a static modifier, it declares a static member. In addition, a constant or type declaration implicitly declares a static member. Static members have the following characteristics:
When a field, method, property, event, indexer, constructor, or destructor declaration does not include a static modifier, it declares an instance member. (An instance member is sometimes called a non-static member.) Instance members have the following characteristics:
The following example illustrates the rules for accessing static and instance members:
The F method shows that in an instance function member, a simple_name (Simple names) can be used to access both instance members and static members. The G method shows that in a static function member, it is a compile-time error to access an instance member through a simple_name. The Main method shows that in a member_access (Member access), instance members must be accessed through instances, and static members must be accessed through types.
Nested types
A type declared within a class or struct declaration is called a nested type. A type that is declared within a compilation unit or namespace is called a non-nested type.
Fully qualified name
The fully qualified name (Fully qualified names) for a nested type is S.N where S is the fully qualified name of the type in which type N is declared.
Declared accessibility
Non-nested types can have public or internal declared accessibility and have internal declared accessibility by default. Nested types can have these forms of declared accessibility too, plus one or more additional forms of declared accessibility, depending on whether the containing type is a class or struct:
Hiding
A nested type may hide (Name hiding) a base member. The new modifier is permitted on nested type declarations so that hiding can be expressed explicitly. The example
this access
A nested type and its containing type do not have a special relationship with regard to this_access (This access). Specifically, this within a nested type cannot be used to refer to instance members of the containing type. In cases where a nested type needs access to the instance members of its containing type, access can be provided by providing the this for the instance of the containing type as a constructor argument for the nested type. The following example
shows this technique. An instance of C creates an instance of Nested and passes its own this to Nested ‘s constructor in order to provide subsequent access to C ‘s instance members.
Access to private and protected members of the containing type
A nested type has access to all of the members that are accessible to its containing type, including members of the containing type that have private and protected declared accessibility. The example
A nested type also may access protected members defined in a base type of its containing type. In the example
Nested types in generic classes
A generic class declaration can contain nested type declarations. The type parameters of the enclosing class can be used within the nested types. A nested type declaration can contain additional type parameters that apply only to the nested type.
Every type declaration contained within a generic class declaration is implicitly a generic type declaration. When writing a reference to a type nested within a generic type, the containing constructed type, including its type arguments, must be named. However, from within the outer class, the nested type can be used without qualification; the instance type of the outer class can be implicitly used when constructing the nested type. The following example shows three different correct ways to refer to a constructed type created from Inner ; the first two are equivalent:
Although it is bad programming style, a type parameter in a nested type can hide a member or type parameter declared in the outer type:
Reserved member names
To facilitate the underlying C# run-time implementation, for each source member declaration that is a property, event, or indexer, the implementation must reserve two method signatures based on the kind of the member declaration, its name, and its type. It is a compile-time error for a program to declare a member whose signature matches one of these reserved signatures, even if the underlying run-time implementation does not make use of these reservations.
The reserved names do not introduce declarations, thus they do not participate in member lookup. However, a declaration’s associated reserved method signatures do participate in inheritance (Inheritance), and can be hidden with the new modifier (The new modifier).
The reservation of these names serves three purposes:
The declaration of a destructor (Destructors) also causes a signature to be reserved (Member names reserved for destructors).
Member names reserved for properties
Both signatures are reserved, even if the property is read-only or write-only.
Member names reserved for events
Member names reserved for indexers
Both signatures are reserved, even if the indexer is read-only or write-only.
Furthermore the member name Item is reserved.
Member names reserved for destructors
For a class containing a destructor (Destructors), the following signature is reserved:
Constants
A constant is a class member that represents a constant value: a value that can be computed at compile-time. A constant_declaration introduces one or more constants of a given type.
A constant_declaration may include a set of attributes (Attributes), a new modifier (The new modifier), and a valid combination of the four access modifiers (Access modifiers). The attributes and modifiers apply to all of the members declared by the constant_declaration. Even though constants are considered static members, a constant_declaration neither requires nor allows a static modifier. It is an error for the same modifier to appear multiple times in a constant declaration.
The type of a constant_declaration specifies the type of the members introduced by the declaration. The type is followed by a list of constant_declarators, each of which introduces a new member. A constant_declarator consists of an identifier that names the member, followed by an » = » token, followed by a constant_expression (Constant expressions) that gives the value of the member.
The type of a constant must be at least as accessible as the constant itself (Accessibility constraints).
The value of a constant is obtained in an expression using a simple_name (Simple names) or a member_access (Member access).
A constant can itself participate in a constant_expression. Thus, a constant may be used in any construct that requires a constant_expression. Examples of such constructs include case labels, goto case statements, enum member declarations, attributes, and other constant declarations.
When a symbolic name for a constant value is desired, but when the type of that value is not permitted in a constant declaration, or when the value cannot be computed at compile-time by a constant_expression, a readonly field (Readonly fields) may be used instead.
A constant declaration that declares multiple constants is equivalent to multiple declarations of single constants with the same attributes, modifiers, and type. For example
Constants are permitted to depend on other constants within the same program as long as the dependencies are not of a circular nature. The compiler automatically arranges to evaluate the constant declarations in the appropriate order. In the example
Fields
A field is a member that represents a variable associated with an object or class. A field_declaration introduces one or more fields of a given type.
A field_declaration may include a set of attributes (Attributes), a new modifier (The new modifier), a valid combination of the four access modifiers (Access modifiers), and a static modifier (Static and instance fields). In addition, a field_declaration may include a readonly modifier (Readonly fields) or a volatile modifier (Volatile fields) but not both. The attributes and modifiers apply to all of the members declared by the field_declaration. It is an error for the same modifier to appear multiple times in a field declaration.
The type of a field_declaration specifies the type of the members introduced by the declaration. The type is followed by a list of variable_declarators, each of which introduces a new member. A variable_declarator consists of an identifier that names that member, optionally followed by an » = » token and a variable_initializer (Variable initializers) that gives the initial value of that member.
The type of a field must be at least as accessible as the field itself (Accessibility constraints).
The value of a field is obtained in an expression using a simple_name (Simple names) or a member_access (Member access). The value of a non-readonly field is modified using an assignment (Assignment operators). The value of a non-readonly field can be both obtained and modified using postfix increment and decrement operators (Postfix increment and decrement operators) and prefix increment and decrement operators (Prefix increment and decrement operators).
A field declaration that declares multiple fields is equivalent to multiple declarations of single fields with the same attributes, modifiers, and type. For example
Static and instance fields
When a field declaration includes a static modifier, the fields introduced by the declaration are static fields. When no static modifier is present, the fields introduced by the declaration are instance fields. Static fields and instance fields are two of the several kinds of variables (Variables) supported by C#, and at times they are referred to as static variables and instance variables, respectively.
A static field is not part of a specific instance; instead, it is shared amongst all instances of a closed type (Open and closed types). No matter how many instances of a closed class type are created, there is only ever one copy of a static field for the associated application domain.
An instance field belongs to an instance. Specifically, every instance of a class contains a separate set of all the instance fields of that class.
The differences between static and instance members are discussed further in Static and instance members.
Readonly fields
When a field_declaration includes a readonly modifier, the fields introduced by the declaration are readonly fields. Direct assignments to readonly fields can only occur as part of that declaration or in an instance constructor or static constructor in the same class. (A readonly field can be assigned to multiple times in these contexts.) Specifically, direct assignments to a readonly field are permitted only in the following contexts:
Attempting to assign to a readonly field or pass it as an out or ref parameter in any other context is a compile-time error.
Using static readonly fields for constants
A static readonly field is useful when a symbolic name for a constant value is desired, but when the type of the value is not permitted in a const declaration, or when the value cannot be computed at compile-time. In the example
Versioning of constants and static readonly fields
Constants and readonly fields have different binary versioning semantics. When an expression references a constant, the value of the constant is obtained at compile-time, but when an expression references a readonly field, the value of the field is not obtained until run-time. Consider an application that consists of two separate programs:
The Program1 and Program2 namespaces denote two programs that are compiled separately. Because Program1.Utils.X is declared as a static readonly field, the value output by the Console.WriteLine statement is not known at compile-time, but rather is obtained at run-time. Thus, if the value of X is changed and Program1 is recompiled, the Console.WriteLine statement will output the new value even if Program2 isn’t recompiled. However, had X been a constant, the value of X would have been obtained at the time Program2 was compiled, and would remain unaffected by changes in Program1 until Program2 is recompiled.
Volatile fields
When a field_declaration includes a volatile modifier, the fields introduced by that declaration are volatile fields.
For non-volatile fields, optimization techniques that reorder instructions can lead to unexpected and unpredictable results in multi-threaded programs that access fields without synchronization such as that provided by the lock_statement (The lock statement). These optimizations can be performed by the compiler, by the run-time system, or by hardware. For volatile fields, such reordering optimizations are restricted:
These restrictions ensure that all threads will observe volatile writes performed by any other thread in the order in which they were performed. A conforming implementation is not required to provide a single total ordering of volatile writes as seen from all threads of execution. The type of a volatile field must be one of the following:
produces the output:
Field initialization
The initial value of a field, whether it be a static field or an instance field, is the default value (Default values) of the field’s type. It is not possible to observe the value of a field before this default initialization has occurred, and a field is thus never «uninitialized». The example
produces the output
because b and i are both automatically initialized to default values.
Variable initializers
Field declarations may include variable_initializers. For static fields, variable initializers correspond to assignment statements that are executed during class initialization. For instance fields, variable initializers correspond to assignment statements that are executed when an instance of the class is created.
produces the output
because an assignment to x occurs when static field initializers execute and assignments to i and s occur when the instance field initializers execute.
The default value initialization described in Field initialization occurs for all fields, including fields that have variable initializers. Thus, when a class is initialized, all static fields in that class are first initialized to their default values, and then the static field initializers are executed in textual order. Likewise, when an instance of a class is created, all instance fields in that instance are first initialized to their default values, and then the instance field initializers are executed in textual order.
It is possible for static fields with variable initializers to be observed in their default value state. However, this is strongly discouraged as a matter of style. The example
exhibits this behavior. Despite the circular definitions of a and b, the program is valid. It results in the output
Static field initialization
The static field variable initializers of a class correspond to a sequence of assignments that are executed in the textual order in which they appear in the class declaration. If a static constructor (Static constructors) exists in the class, execution of the static field initializers occurs immediately prior to executing that static constructor. Otherwise, the static field initializers are executed at an implementation-dependent time prior to the first use of a static field of that class. The example
might produce either the output:
because the execution of X ‘s initializer and Y ‘s initializer could occur in either order; they are only constrained to occur before the references to those fields. However, in the example:
the output must be:
because the rules for when static constructors execute (as defined in Static constructors) provide that B ‘s static constructor (and hence B ‘s static field initializers) must run before A ‘s static constructor and field initializers.
Instance field initialization
The instance field variable initializers of a class correspond to a sequence of assignments that are executed immediately upon entry to any one of the instance constructors (Constructor initializers) of that class. The variable initializers are executed in the textual order in which they appear in the class declaration. The class instance creation and initialization process is described further in Instance constructors.
A variable initializer for an instance field cannot reference the instance being created. Thus, it is a compile-time error to reference this in a variable initializer, as it is a compile-time error for a variable initializer to reference any instance member through a simple_name. In the example
the variable initializer for y results in a compile-time error because it references a member of the instance being created.
Methods
A method is a member that implements a computation or action that can be performed by an object or class. Methods are declared using method_declarations:
A method_declaration may include a set of attributes (Attributes) and a valid combination of the four access modifiers (Access modifiers), the new (The new modifier), static (Static and instance methods), virtual (Virtual methods), override (Override methods), sealed (Sealed methods), abstract (Abstract methods), and extern (External methods) modifiers.
A declaration has a valid combination of modifiers if all of the following are true:
A method that has the async modifier is an async function and follows the rules described in Async functions.
The optional type_parameter_list specifies the type parameters of the method (Type parameters). If a type_parameter_list is specified the method is a generic method. If the method has an extern modifier, a type_parameter_list cannot be specified.
The optional formal_parameter_list specifies the parameters of the method (Method parameters).
The optional type_parameter_constraints_clauses specify constraints on individual type parameters (Type parameter constraints) and may only be specified if a type_parameter_list is also supplied, and the method does not have an override modifier.
The return_type and each of the types referenced in the formal_parameter_list of a method must be at least as accessible as the method itself (Accessibility constraints).
The method_body is either a semicolon, a statement body or an expression body. A statement body consists of a block, which specifies the statements to execute when the method is invoked. An expression body consists of => followed by an expression and a semicolon, and denotes a single expression to perform when the method is invoked.
For abstract and extern methods, the method_body consists simply of a semicolon. For partial methods the method_body may consist of either a semicolon, a block body or an expression body. For all other methods, the method_body is either a block body or an expression body.
If the method_body consists of a semicolon, then the declaration may not include the async modifier.
The name, the type parameter list and the formal parameter list of a method define the signature (Signatures and overloading) of the method. Specifically, the signature of a method consists of its name, the number of type parameters and the number, modifiers, and types of its formal parameters. For these purposes, any type parameter of the method that occurs in the type of a formal parameter is identified not by its name, but by its ordinal position in the type argument list of the method.The return type is not part of a method’s signature, nor are the names of the type parameters or the formal parameters.
The method’s type_parameters are in scope throughout the method_declaration, and can be used to form types throughout that scope in return_type, method_body, and type_parameter_constraints_clauses but not in attributes.
All formal parameters and type parameters must have different names.
Method parameters
The parameters of a method, if any, are declared by the method’s formal_parameter_list.
The formal parameter list consists of one or more comma-separated parameters of which only the last may be a parameter_array.
A fixed_parameter with a default_argument is known as an optional parameter, whereas a fixed_parameter without a default_argument is a required parameter. A required parameter may not appear after an optional parameter in a formal_parameter_list.
A ref or out parameter cannot have a default_argument. The expression in a default_argument must be one of the following:
The expression must be implicitly convertible by an identity or nullable conversion to the type of the parameter.
A parameter_array consists of an optional set of attributes (Attributes), a params modifier, an array_type, and an identifier. A parameter array declares a single parameter of the given array type with the given name. The array_type of a parameter array must be a single-dimensional array type (Array types). In a method invocation, a parameter array permits either a single argument of the given array type to be specified, or it permits zero or more arguments of the array element type to be specified. Parameter arrays are described further in Parameter arrays.
The following example illustrates different kinds of parameters:
A method declaration creates a separate declaration space for parameters, type parameters and local variables. Names are introduced into this declaration space by the type parameter list and the formal parameter list of the method and by local variable declarations in the block of the method. It is an error for two members of a method declaration space to have the same name. It is an error for the method declaration space and the local variable declaration space of a nested declaration space to contain elements with the same name.
A method invocation (Method invocations) creates a copy, specific to that invocation, of the formal parameters and local variables of the method, and the argument list of the invocation assigns values or variable references to the newly created formal parameters. Within the block of a method, formal parameters can be referenced by their identifiers in simple_name expressions (Simple names).
There are four kinds of formal parameters:
As described in Signatures and overloading, the ref and out modifiers are part of a method’s signature, but the params modifier is not.
Value parameters
A parameter declared with no modifiers is a value parameter. A value parameter corresponds to a local variable that gets its initial value from the corresponding argument supplied in the method invocation.
When a formal parameter is a value parameter, the corresponding argument in a method invocation must be an expression that is implicitly convertible (Implicit conversions) to the formal parameter type.
A method is permitted to assign new values to a value parameter. Such assignments only affect the local storage location represented by the value parameter—they have no effect on the actual argument given in the method invocation.
Reference parameters
A parameter declared with a ref modifier is a reference parameter. Unlike a value parameter, a reference parameter does not create a new storage location. Instead, a reference parameter represents the same storage location as the variable given as the argument in the method invocation.
When a formal parameter is a reference parameter, the corresponding argument in a method invocation must consist of the keyword ref followed by a variable_reference (Precise rules for determining definite assignment) of the same type as the formal parameter. A variable must be definitely assigned before it can be passed as a reference parameter.
Within a method, a reference parameter is always considered definitely assigned.
A method declared as an iterator (Iterators) cannot have reference parameters.
produces the output
In a method that takes reference parameters it is possible for multiple names to represent the same storage location. In the example
Output parameters
A parameter declared with an out modifier is an output parameter. Similar to a reference parameter, an output parameter does not create a new storage location. Instead, an output parameter represents the same storage location as the variable given as the argument in the method invocation.
When a formal parameter is an output parameter, the corresponding argument in a method invocation must consist of the keyword out followed by a variable_reference (Precise rules for determining definite assignment) of the same type as the formal parameter. A variable need not be definitely assigned before it can be passed as an output parameter, but following an invocation where a variable was passed as an output parameter, the variable is considered definitely assigned.
Within a method, just like a local variable, an output parameter is initially considered unassigned and must be definitely assigned before its value is used.
Every output parameter of a method must be definitely assigned before the method returns.
A method declared as a partial method (Partial methods) or an iterator (Iterators) cannot have output parameters.
Output parameters are typically used in methods that produce multiple return values. For example:
The example produces the output:
Parameter arrays
A parameter array permits arguments to be specified in one of two ways in a method invocation:
Except for allowing a variable number of arguments in an invocation, a parameter array is precisely equivalent to a value parameter (Value parameters) of the same type.
produces the output
The first invocation of F simply passes the array a as a value parameter. The second invocation of F automatically creates a four-element int[] with the given element values and passes that array instance as a value parameter. Likewise, the third invocation of F creates a zero-element int[] and passes that instance as a value parameter. The second and third invocations are precisely equivalent to writing:
When performing overload resolution, a method with a parameter array may be applicable either in its normal form or in its expanded form (Applicable function member). The expanded form of a method is available only if the normal form of the method is not applicable and only if an applicable method with the same signature as the expanded form is not already declared in the same type.
produces the output
In the example, two of the possible expanded forms of the method with a parameter array are already included in the class as regular methods. These expanded forms are therefore not considered when performing overload resolution, and the first and third method invocations thus select the regular methods. When a class declares a method with a parameter array, it is not uncommon to also include some of the expanded forms as regular methods. By doing so it is possible to avoid the allocation of an array instance that occurs when an expanded form of a method with a parameter array is invoked.
produces the output
Static and instance methods
When a method declaration includes a static modifier, that method is said to be a static method. When no static modifier is present, the method is said to be an instance method.
A static method does not operate on a specific instance, and it is a compile-time error to refer to this in a static method.
An instance method operates on a given instance of a class, and that instance can be accessed as this (This access).
The differences between static and instance members are discussed further in Static and instance members.
Virtual methods
When an instance method declaration includes a virtual modifier, that method is said to be a virtual method. When no virtual modifier is present, the method is said to be a non-virtual method.
The implementation of a non-virtual method is invariant: The implementation is the same whether the method is invoked on an instance of the class in which it is declared or an instance of a derived class. In contrast, the implementation of a virtual method can be superseded by derived classes. The process of superseding the implementation of an inherited virtual method is known as overriding that method (Override methods).
In a virtual method invocation, the run-time type of the instance for which that invocation takes place determines the actual method implementation to invoke. In a non-virtual method invocation, the compile-time type of the instance is the determining factor. In precise terms, when a method named N is invoked with an argument list A on an instance with a compile-time type C and a run-time type R (where R is either C or a class derived from C ), the invocation is processed as follows:
For every virtual method declared in or inherited by a class, there exists a most derived implementation of the method with respect to that class. The most derived implementation of a virtual method M with respect to a class R is determined as follows:
The following example illustrates the differences between virtual and non-virtual methods:
Because methods are allowed to hide inherited methods, it is possible for a class to contain several virtual methods with the same signature. This does not present an ambiguity problem, since all but the most derived method are hidden. In the example
Note that it is possible to invoke the hidden virtual method by accessing an instance of D through a less derived type in which the method is not hidden.
Override methods
When an instance method declaration includes an override modifier, the method is said to be an override method. An override method overrides an inherited virtual method with the same signature. Whereas a virtual method declaration introduces a new method, an override method declaration specializes an existing inherited virtual method by providing a new implementation of that method.
A compile-time error occurs unless all of the following are true for an override declaration:
The following example demonstrates how the overriding rules work for generic classes:
An override declaration can access the overridden base method using a base_access (Base access). In the example
Only by including an override modifier can a method override another method. In all other cases, a method with the same signature as an inherited method simply hides the inherited method. In the example
Sealed methods
When an instance method declaration includes a sealed modifier, that method is said to be a sealed method. If an instance method declaration includes the sealed modifier, it must also include the override modifier. Use of the sealed modifier prevents a derived class from further overriding the method.
Abstract methods
An abstract method declaration introduces a new virtual method but does not provide an implementation of that method. Instead, non-abstract derived classes are required to provide their own implementation by overriding that method. Because an abstract method provides no actual implementation, the method_body of an abstract method simply consists of a semicolon.
Abstract method declarations are only permitted in abstract classes (Abstract classes).
the Shape class defines the abstract notion of a geometrical shape object that can paint itself. The Paint method is abstract because there is no meaningful default implementation. The Ellipse and Box classes are concrete Shape implementations. Because these classes are non-abstract, they are required to override the Paint method and provide an actual implementation.
It is a compile-time error for a base_access (Base access) to reference an abstract method. In the example
a compile-time error is reported for the base.F() invocation because it references an abstract method.
An abstract method declaration is permitted to override a virtual method. This allows an abstract class to force re-implementation of the method in derived classes, and makes the original implementation of the method unavailable. In the example
class A declares a virtual method, class B overrides this method with an abstract method, and class C overrides the abstract method to provide its own implementation.
External methods
When a method declaration includes an extern modifier, that method is said to be an external method. External methods are implemented externally, typically using a language other than C#. Because an external method declaration provides no actual implementation, the method_body of an external method simply consists of a semicolon. An external method may not be generic.
The extern modifier is typically used in conjunction with a DllImport attribute (Interoperation with COM and Win32 components), allowing external methods to be implemented by DLLs (Dynamic Link Libraries). The execution environment may support other mechanisms whereby implementations of external methods can be provided.
When an external method includes a DllImport attribute, the method declaration must also include a static modifier. This example demonstrates the use of the extern modifier and the DllImport attribute:
Partial methods (recap)
When a method declaration includes a partial modifier, that method is said to be a partial method. Partial methods can only be declared as members of partial types (Partial types), and are subject to a number of restrictions. Partial methods are further described in Partial methods.
Extension methods
The following is an example of a static class that declares two extension methods:
An extension method is a regular static method. In addition, where its enclosing static class is in scope, an extension method can be invoked using instance method invocation syntax (Extension method invocations), using the receiver expression as the first argument.
The following program uses the extension methods declared above:
Method body
The method_body of a method declaration consists of either a block body, an expression body or a semicolon.
When a method has a void result type and a block body, return statements (The return statement) in the block are not permitted to specify an expression. If execution of the block of a void method completes normally (that is, control flows off the end of the method body), that method simply returns to its current caller.
When a method has a void result and an expression body, the expression E must be a statement_expression, and the body is exactly equivalent to a block body of the form < E; >.
When a method has a non-void result type and a block body, each return statement in the block must specify an expression that is implicitly convertible to the result type. The endpoint of a block body of a value-returning method must not be reachable. In other words, in a value-returning method with a block body, control is not permitted to flow off the end of the method body.
When a method has a non-void result type and an expression body, the expression must be implicitly convertible to the result type, and the body is exactly equivalent to a block body of the form < return E; >.
the value-returning F method results in a compile-time error because control can flow off the end of the method body. The G and H methods are correct because all possible execution paths end in a return statement that specifies a return value. The I method is correct, because its body is equivalent to a statement block with just a single return statement in it.
Method overloading
The method overload resolution rules are described in Type inference.
Properties
A property is a member that provides access to a characteristic of an object or a class. Examples of properties include the length of a string, the size of a font, the caption of a window, the name of a customer, and so on. Properties are a natural extension of fields—both are named members with associated types, and the syntax for accessing fields and properties is the same. However, unlike fields, properties do not denote storage locations. Instead, properties have accessors that specify the statements to be executed when their values are read or written. Properties thus provide a mechanism for associating actions with the reading and writing of an object’s attributes; furthermore, they permit such attributes to be computed.
Properties are declared using property_declarations:
A property_declaration may include a set of attributes (Attributes) and a valid combination of the four access modifiers (Access modifiers), the new (The new modifier), static (Static and instance methods), virtual (Virtual methods), override (Override methods), sealed (Sealed methods), abstract (Abstract methods), and extern (External methods) modifiers.
Property declarations are subject to the same rules as method declarations (Methods) with regard to valid combinations of modifiers.
The type of a property must be at least as accessible as the property itself (Accessibility constraints).
A property_body may either consist of an accessor body or an expression body. In an accessor body, accessor_declarations, which must be enclosed in » < " and " >» tokens, declare the accessors (Accessors) of the property. The accessors specify the executable statements associated with reading and writing the property.
A property_initializer may only be given for an automatically implemented property (Automatically implemented properties), and causes the initialization of the underlying field of such properties with the value given by the expression.
Even though the syntax for accessing a property is the same as that for a field, a property is not classified as a variable. Thus, it is not possible to pass a property as a ref or out argument.
When a property declaration includes an extern modifier, the property is said to be an external property. Because an external property declaration provides no actual implementation, each of its accessor_declarations consists of a semicolon.
Static and instance properties
When a property declaration includes a static modifier, the property is said to be a static property. When no static modifier is present, the property is said to be an instance property.
A static property is not associated with a specific instance, and it is a compile-time error to refer to this in the accessors of a static property.
An instance property is associated with a given instance of a class, and that instance can be accessed as this (This access) in the accessors of that property.
The differences between static and instance members are discussed further in Static and instance members.
Accessors
The accessor_declarations of a property specify the executable statements associated with reading and writing that property.
The accessor declarations consist of a get_accessor_declaration, a set_accessor_declaration, or both. Each accessor declaration consists of the token get or set followed by an optional accessor_modifier and an accessor_body.
The use of accessor_modifiers is governed by the following restrictions:
For abstract and extern properties, the accessor_body for each accessor specified is simply a semicolon. A non-abstract, non-extern property may have each accessor_body be a semicolon, in which case it is an automatically implemented property (Automatically implemented properties). An automatically implemented property must have at least a get accessor. For the accessors of any other non-abstract, non-extern property, the accessor_body is a block which specifies the statements to be executed when the corresponding accessor is invoked.
A get accessor corresponds to a parameterless method with a return value of the property type. Except as the target of an assignment, when a property is referenced in an expression, the get accessor of the property is invoked to compute the value of the property (Values of expressions). The body of a get accessor must conform to the rules for value-returning methods described in Method body. In particular, all return statements in the body of a get accessor must specify an expression that is implicitly convertible to the property type. Furthermore, the endpoint of a get accessor must not be reachable.
Based on the presence or absence of the get and set accessors, a property is classified as follows:
the Button control declares a public Caption property. The get accessor of the Caption property returns the string stored in the private caption field. The set accessor checks if the new value is different from the current value, and if so, it stores the new value and repaints the control. Properties often follow the pattern shown above: The get accessor simply returns a value stored in a private field, and the set accessor modifies that private field and then performs any additional actions required to fully update the state of the object.
Given the Button class above, the following is an example of use of the Caption property:
Here, the set accessor is invoked by assigning a value to the property, and the get accessor is invoked by referencing the property in an expression.
The get and set accessors of a property are not distinct members, and it is not possible to declare the accessors of a property separately. As such, it is not possible for the two accessors of a read-write property to have different accessibility. The example
does not declare a single read-write property. Rather, it declares two properties with the same name, one read-only and one write-only. Since two members declared in the same class cannot have the same name, the example causes a compile-time error to occur.
When a derived class declares a property by the same name as an inherited property, the derived property hides the inherited property with respect to both reading and writing. In the example
the P property in B hides the P property in A with respect to both reading and writing. Thus, in the statements
Unlike public fields, properties provide a separation between an object’s internal state and its public interface. Consider the example:
Had x and y instead been public readonly fields, it would have been impossible to make such a change to the Label class.
Exposing state through properties is not necessarily any less efficient than exposing fields directly. In particular, when a property is non-virtual and contains only a small amount of code, the execution environment may replace calls to accessors with the actual code of the accessors. This process is known as inlining, and it makes property access as efficient as field access, yet preserves the increased flexibility of properties.
Since invoking a get accessor is conceptually equivalent to reading the value of a field, it is considered bad programming style for get accessors to have observable side-effects. In the example
the value of the Next property depends on the number of times the property has previously been accessed. Thus, accessing the property produces an observable side-effect, and the property should be implemented as a method instead.
The «no side-effects» convention for get accessors doesn’t mean that get accessors should always be written to simply return values stored in fields. Indeed, get accessors often compute the value of a property by accessing multiple fields or invoking methods. However, a properly designed get accessor performs no actions that cause observable changes in the state of the object.
Properties can be used to delay initialization of a resource until the moment it is first referenced. For example:
the underlying TextWriter for the output device is created. But if the application makes no reference to the In and Error properties, then no objects are created for those devices.
Automatically implemented properties
An automatically implemented property (or auto-property for short), is a non-abstract non-extern property with semicolon-only accessor bodies. Auto-properties must have a get accessor and can optionally have a set accessor.
When a property is specified as an automatically implemented property, a hidden backing field is automatically available for the property, and the accessors are implemented to read from and write to that backing field. If the auto-property has no set accessor, the backing field is considered readonly (Readonly fields). Just like a readonly field, a getter-only auto-property can also be assigned to in the body of a constructor of the enclosing class. Such an assignment assigns directly to the readonly backing field of the property.
An auto-property may optionally have a property_initializer, which is applied directly to the backing field as a variable_initializer (Variable initializers).
The following example:
is equivalent to the following declaration:
The following example:
is equivalent to the following declaration:
Notice that the assignments to the readonly field are legal, because they occur within the constructor.
Accessibility
If an accessor has an accessor_modifier, the accessibility domain (Accessibility domains) of the accessor is determined using the declared accessibility of the accessor_modifier. If an accessor does not have an accessor_modifier, the accessibility domain of the accessor is determined from the declared accessibility of the property or indexer.
The presence of an accessor_modifier never affects member lookup (Operators) or overload resolution (Overload resolution). The modifiers on the property or indexer always determine which property or indexer is bound to, regardless of the context of the access.
Once a particular property or indexer has been selected, the accessibility domains of the specific accessors involved are used to determine if that usage is valid:
An accessor that is used to implement an interface may not have an accessor_modifier. If only one accessor is used to implement an interface, the other accessor may be declared with an accessor_modifier:
Virtual, sealed, override, and abstract property accessors
A virtual property declaration specifies that the accessors of the property are virtual. The virtual modifier applies to both accessors of a read-write property—it is not possible for only one accessor of a read-write property to be virtual.
An abstract property declaration specifies that the accessors of the property are virtual, but does not provide an actual implementation of the accessors. Instead, non-abstract derived classes are required to provide their own implementation for the accessors by overriding the property. Because an accessor for an abstract property declaration provides no actual implementation, its accessor_body simply consists of a semicolon.
A property declaration that includes both the abstract and override modifiers specifies that the property is abstract and overrides a base property. The accessors of such a property are also abstract.
Abstract property declarations are only permitted in abstract classes (Abstract classes).The accessors of an inherited virtual property can be overridden in a derived class by including a property declaration that specifies an override directive. This is known as an overriding property declaration. An overriding property declaration does not declare a new property. Instead, it simply specializes the implementations of the accessors of an existing virtual property.
An overriding property declaration must specify the exact same accessibility modifiers, type, and name as the inherited property. If the inherited property has only a single accessor (i.e., if the inherited property is read-only or write-only), the overriding property must include only that accessor. If the inherited property includes both accessors (i.e., if the inherited property is read-write), the overriding property can include either a single accessor or both accessors.
An overriding property declaration may include the sealed modifier. Use of this modifier prevents a derived class from further overriding the property. The accessors of a sealed property are also sealed.
Except for differences in declaration and invocation syntax, virtual, sealed, override, and abstract accessors behave exactly like virtual, sealed, override and abstract methods. Specifically, the rules described in Virtual methods, Override methods, Sealed methods, and Abstract methods apply as if accessors were methods of a corresponding form:
X is a virtual read-only property, Y is a virtual read-write property, and Z is an abstract read-write property. Because Z is abstract, the containing class A must also be declared abstract.
A class that derives from A is show below:
Events
An event is a member that enables an object or class to provide notifications. Clients can attach executable code for events by supplying event handlers.
Events are declared using event_declarations:
An event_declaration may include a set of attributes (Attributes) and a valid combination of the four access modifiers (Access modifiers), the new (The new modifier), static (Static and instance methods), virtual (Virtual methods), override (Override methods), sealed (Sealed methods), abstract (Abstract methods), and extern (External methods) modifiers.
Event declarations are subject to the same rules as method declarations (Methods) with regard to valid combinations of modifiers.
The type of an event declaration must be a delegate_type (Reference types), and that delegate_type must be at least as accessible as the event itself (Accessibility constraints).
An event declaration may include event_accessor_declarations. However, if it does not, for non-extern, non-abstract events, the compiler supplies them automatically (Field-like events); for extern events, the accessors are provided externally.
An event declaration that omits event_accessor_declarations defines one or more events—one for each of the variable_declarators. The attributes and modifiers apply to all of the members declared by such an event_declaration.
It is a compile-time error for an event_declaration to include both the abstract modifier and brace-delimited event_accessor_declarations.
When an event declaration includes an extern modifier, the event is said to be an external event. Because an external event declaration provides no actual implementation, it is an error for it to include both the extern modifier and event_accessor_declarations.
It is a compile-time error for a variable_declarator of an event declaration with an abstract or external modifier to include a variable_initializer.
The following example shows how event handlers are attached to instances of the Button class:
Here, the LoginDialog instance constructor creates two Button instances and attaches event handlers to the Click events.
Field-like events
Click is used as a field within the Button class. As the example demonstrates, the field can be examined, modified, and used in delegate invocation expressions. The OnClick method in the Button class «raises» the Click event. The notion of raising an event is precisely equivalent to invoking the delegate represented by the event—thus, there are no special language constructs for raising events. Note that the delegate invocation is preceded by a check that ensures the delegate is non-null.
which appends a delegate to the invocation list of the Click event, and
which removes a delegate from the invocation list of the Click event.
When compiling a field-like event, the compiler automatically creates storage to hold the delegate, and creates accessors for the event that add or remove event handlers to the delegate field. The addition and removal operations are thread safe, and may (but are not required to) be done while holding the lock (The lock statement) on the containing object for an instance event, or the type object (Anonymous object creation expressions) for a static event.
Thus, an instance event declaration of the form:
will be compiled to something equivalent to:
Event accessors
Event declarations typically omit event_accessor_declarations, as in the Button example above. One situation for doing so involves the case in which the storage cost of one field per event is not acceptable. In such cases, a class can include event_accessor_declarations and use a private mechanism for storing the list of event handlers.
The event_accessor_declarations of an event specify the executable statements associated with adding and removing event handlers.
The accessor declarations consist of an add_accessor_declaration and a remove_accessor_declaration. Each accessor declaration consists of the token add or remove followed by a block. The block associated with an add_accessor_declaration specifies the statements to execute when an event handler is added, and the block associated with a remove_accessor_declaration specifies the statements to execute when an event handler is removed.
the Control class implements an internal storage mechanism for events. The AddEventHandler method associates a delegate value with a key, the GetEventHandler method returns the delegate currently associated with a key, and the RemoveEventHandler method removes a delegate as an event handler for the specified event. Presumably, the underlying storage mechanism is designed such that there is no cost for associating a null delegate value with a key, and thus unhandled events consume no storage.
Static and instance events
When an event declaration includes a static modifier, the event is said to be a static event. When no static modifier is present, the event is said to be an instance event.
A static event is not associated with a specific instance, and it is a compile-time error to refer to this in the accessors of a static event.
An instance event is associated with a given instance of a class, and this instance can be accessed as this (This access) in the accessors of that event.
The differences between static and instance members are discussed further in Static and instance members.
Virtual, sealed, override, and abstract event accessors
A virtual event declaration specifies that the accessors of that event are virtual. The virtual modifier applies to both accessors of an event.
An abstract event declaration specifies that the accessors of the event are virtual, but does not provide an actual implementation of the accessors. Instead, non-abstract derived classes are required to provide their own implementation for the accessors by overriding the event. Because an abstract event declaration provides no actual implementation, it cannot provide brace-delimited event_accessor_declarations.
An event declaration that includes both the abstract and override modifiers specifies that the event is abstract and overrides a base event. The accessors of such an event are also abstract.
Abstract event declarations are only permitted in abstract classes (Abstract classes).
The accessors of an inherited virtual event can be overridden in a derived class by including an event declaration that specifies an override modifier. This is known as an overriding event declaration. An overriding event declaration does not declare a new event. Instead, it simply specializes the implementations of the accessors of an existing virtual event.
An overriding event declaration must specify the exact same accessibility modifiers, type, and name as the overridden event.
An overriding event declaration may include the sealed modifier. Use of this modifier prevents a derived class from further overriding the event. The accessors of a sealed event are also sealed.
It is a compile-time error for an overriding event declaration to include a new modifier.
Except for differences in declaration and invocation syntax, virtual, sealed, override, and abstract accessors behave exactly like virtual, sealed, override and abstract methods. Specifically, the rules described in Virtual methods, Override methods, Sealed methods, and Abstract methods apply as if accessors were methods of a corresponding form. Each accessor corresponds to a method with a single value parameter of the event type, a void return type, and the same modifiers as the containing event.
Indexers
An indexer is a member that enables an object to be indexed in the same way as an array. Indexers are declared using indexer_declarations:
An indexer_declaration may include a set of attributes (Attributes) and a valid combination of the four access modifiers (Access modifiers), the new (The new modifier), virtual (Virtual methods), override (Override methods), sealed (Sealed methods), abstract (Abstract methods), and extern (External methods) modifiers.
Indexer declarations are subject to the same rules as method declarations (Methods) with regard to valid combinations of modifiers, with the one exception being that the static modifier is not permitted on an indexer declaration.
The formal_parameter_list specifies the parameters of the indexer. The formal parameter list of an indexer corresponds to that of a method (Method parameters), except that at least one parameter must be specified, and that the ref and out parameter modifiers are not permitted.
The type of an indexer and each of the types referenced in the formal_parameter_list must be at least as accessible as the indexer itself (Accessibility constraints).
An indexer_body may either consist of an accessor body or an expression body. In an accessor body, accessor_declarations, which must be enclosed in » < " and " >» tokens, declare the accessors (Accessors) of the property. The accessors specify the executable statements associated with reading and writing the property.
Even though the syntax for accessing an indexer element is the same as that for an array element, an indexer element is not classified as a variable. Thus, it is not possible to pass an indexer element as a ref or out argument.
The formal parameter list of an indexer defines the signature (Signatures and overloading) of the indexer. Specifically, the signature of an indexer consists of the number and types of its formal parameters. The element type and names of the formal parameters are not part of an indexer’s signature.
The signature of an indexer must differ from the signatures of all other indexers declared in the same class.
Indexers and properties are very similar in concept, but differ in the following ways:
Aside from these differences, all rules defined in Accessors and Automatically implemented properties apply to indexer accessors as well as to property accessors.
When an indexer declaration includes an extern modifier, the indexer is said to be an external indexer. Because an external indexer declaration provides no actual implementation, each of its accessor_declarations consists of a semicolon.
The example below declares a BitArray class that implements an indexer for accessing the individual bits in the bit array.
The following CountPrimes class uses a BitArray and the classical «sieve» algorithm to compute the number of primes between 1 and a given maximum:
The following example shows a 26 * 10 grid class that has an indexer with two parameters. The first parameter is required to be an upper- or lowercase letter in the range A-Z, and the second is required to be an integer in the range 0-9.
Indexer overloading
The indexer overload resolution rules are described in Type inference.
Operators
An operator is a member that defines the meaning of an expression operator that can be applied to instances of the class. Operators are declared using operator_declarations:
There are three categories of overloadable operators: Unary operators (Unary operators), binary operators (Binary operators), and conversion operators (Conversion operators).
The operator_body is either a semicolon, a statement body or an expression body. A statement body consists of a block, which specifies the statements to execute when the operator is invoked. The block must conform to the rules for value-returning methods described in Method body. An expression body consists of => followed by an expression and a semicolon, and denotes a single expression to perform when the operator is invoked.
For extern operators, the operator_body consists simply of a semicolon. For all other operators, the operator_body is either a block body or an expression body.
The following rules apply to all operator declarations:
Each operator category imposes additional restrictions, as described in the following sections.
Like other members, operators declared in a base class are inherited by derived classes. Because operator declarations always require the class or struct in which the operator is declared to participate in the signature of the operator, it is not possible for an operator declared in a derived class to hide an operator declared in a base class. Thus, the new modifier is never required, and therefore never permitted, in an operator declaration.
Additional information on unary and binary operators can be found in Operators.
Additional information on conversion operators can be found in User-defined conversions.
Unary operators
The following rules apply to unary operator declarations, where T denotes the instance type of the class or struct that contains the operator declaration:
The true and false unary operators require pair-wise declaration. A compile-time error occurs if a class declares one of these operators without also declaring the other. The true and false operators are described further in User-defined conditional logical operators and Boolean expressions.
The following example shows an implementation and subsequent usage of operator ++ for an integer vector class:
Note how the operator method returns the value produced by adding 1 to the operand, just like the postfix increment and decrement operators (Postfix increment and decrement operators), and the prefix increment and decrement operators (Prefix increment and decrement operators). Unlike in C++, this method need not modify the value of its operand directly. In fact, modifying the operand value would violate the standard semantics of the postfix increment operator.
Binary operators
The following rules apply to binary operator declarations, where T denotes the instance type of the class or struct that contains the operator declaration:
Certain binary operators require pair-wise declaration. For every declaration of either operator of a pair, there must be a matching declaration of the other operator of the pair. Two operator declarations match when they have the same return type and the same type for each parameter. The following operators require pair-wise declaration:
Conversion operators
A conversion operator declaration introduces a user-defined conversion (User-defined conversions) which augments the pre-defined implicit and explicit conversions.
A conversion operator declaration that includes the implicit keyword introduces a user-defined implicit conversion. Implicit conversions can occur in a variety of situations, including function member invocations, cast expressions, and assignments. This is described further in Implicit conversions.
A conversion operator declaration that includes the explicit keyword introduces a user-defined explicit conversion. Explicit conversions can occur in cast expressions, and are described further in Explicit conversions.
A conversion operator converts from a source type, indicated by the parameter type of the conversion operator, to a target type, indicated by the return type of the conversion operator.
For the purposes of these rules, any type parameters associated with S or T are considered to be unique types that have no inheritance relationship with other types, and any constraints on those type parameters are ignored.
It is not possible to directly redefine a pre-defined conversion. Thus, conversion operators are not allowed to convert from or to object because implicit and explicit conversions already exist between object and all other types. Likewise, neither the source nor the target types of a conversion can be a base type of the other, since a conversion would then already exist.
However, it is possible to declare operators on generic types that, for particular type arguments, specify conversions that already exist as pre-defined conversions. In the example
In cases where a pre-defined conversion exists between two types, any user-defined conversions between those types are ignored. Specifically:
If T is an interface type, user-defined implicit conversions from S to T are ignored.
Otherwise, user-defined implicit conversions from S to T are still considered.
User-defined conversions are not allowed to convert from or to interface_types. In particular, this restriction ensures that no user-defined transformations occur when converting to an interface_type, and that a conversion to an interface_type succeeds only if the object being converted actually implements the specified interface_type.
The signature of a conversion operator consists of the source type and the target type. (Note that this is the only form of member for which the return type participates in the signature.) The implicit or explicit classification of a conversion operator is not part of the operator’s signature. Thus, a class or struct cannot declare both an implicit and an explicit conversion operator with the same source and target types.
In general, user-defined implicit conversions should be designed to never throw exceptions and never lose information. If a user-defined conversion can give rise to exceptions (for example, because the source argument is out of range) or loss of information (such as discarding high-order bits), then that conversion should be defined as an explicit conversion.
Instance constructors
An instance constructor is a member that implements the actions required to initialize an instance of a class. Instance constructors are declared using constructor_declarations:
A constructor_declaration may include a set of attributes (Attributes), a valid combination of the four access modifiers (Access modifiers), and an extern (External methods) modifier. A constructor declaration is not permitted to include the same modifier multiple times.
The identifier of a constructor_declarator must name the class in which the instance constructor is declared. If any other name is specified, a compile-time error occurs.
The optional formal_parameter_list of an instance constructor is subject to the same rules as the formal_parameter_list of a method (Methods). The formal parameter list defines the signature (Signatures and overloading) of an instance constructor and governs the process whereby overload resolution (Type inference) selects a particular instance constructor in an invocation.
Each of the types referenced in the formal_parameter_list of an instance constructor must be at least as accessible as the constructor itself (Accessibility constraints).
The optional constructor_initializer specifies another instance constructor to invoke before executing the statements given in the constructor_body of this instance constructor. This is described further in Constructor initializers.
When a constructor declaration includes an extern modifier, the constructor is said to be an external constructor. Because an external constructor declaration provides no actual implementation, its constructor_body consists of a semicolon. For all other constructors, the constructor_body consists of a block which specifies the statements to initialize a new instance of the class. This corresponds exactly to the block of an instance method with a void return type (Method body).
Instance constructors are not inherited. Thus, a class has no instance constructors other than those actually declared in the class. If a class contains no instance constructor declarations, a default instance constructor is automatically provided (Default constructors).
Instance constructors are invoked by object_creation_expressions (Object creation expressions) and through constructor_initializers.
Constructor initializers
All instance constructors (except those for class object ) implicitly include an invocation of another instance constructor immediately before the constructor_body. The constructor to implicitly invoke is determined by the constructor_initializer:
If an instance constructor has no constructor initializer, a constructor initializer of the form base() is implicitly provided. Thus, an instance constructor declaration of the form
is exactly equivalent to
The scope of the parameters given by the formal_parameter_list of an instance constructor declaration includes the constructor initializer of that declaration. Thus, a constructor initializer is permitted to access the parameters of the constructor. For example:
An instance constructor initializer cannot access the instance being created. Therefore it is a compile-time error to reference this in an argument expression of the constructor initializer, as is it a compile-time error for an argument expression to reference any instance member through a simple_name.
Instance variable initializers
Constructor execution
Variable initializers are transformed into assignment statements, and these assignment statements are executed before the invocation of the base class instance constructor. This ordering ensures that all instance fields are initialized by their variable initializers before any statements that have access to that instance are executed.
The value of x is 1 because the variable initializer is executed before the base class instance constructor is invoked. However, the value of y is 0 (the default value of an int ) because the assignment to y is not executed until after the base class constructor returns.
It is useful to think of instance variable initializers and constructor initializers as statements that are automatically inserted before the constructor_body. The example
contains several variable initializers; it also contains constructor initializers of both forms ( base and this ). The example corresponds to the code shown below, where each comment indicates an automatically inserted statement (the syntax used for the automatically inserted constructor invocations isn’t valid, but merely serves to illustrate the mechanism).
Default constructors
If a class contains no instance constructor declarations, a default instance constructor is automatically provided. That default constructor simply invokes the parameterless constructor of the direct base class. If the class is abstract then the declared accessibility for the default constructor is protected. Otherwise, the declared accessibility for the default constructor is public. Thus, the default constructor is always of the form
where C is the name of the class. If overload resolution is unable to determine a unique best candidate for the base class constructor initializer then a compile-time error occurs.
a default constructor is provided because the class contains no instance constructor declarations. Thus, the example is precisely equivalent to
Private constructors
The Trig class groups related methods and constants, but is not intended to be instantiated. Therefore it declares a single empty private instance constructor. At least one instance constructor must be declared to suppress the automatic generation of a default constructor.
Optional instance constructor parameters
The this(. ) form of constructor initializer is commonly used in conjunction with overloading to implement optional instance constructor parameters. In the example
the first two instance constructors merely provide the default values for the missing arguments. Both use a this(. ) constructor initializer to invoke the third instance constructor, which actually does the work of initializing the new instance. The effect is that of optional constructor parameters:
Static constructors
A static constructor is a member that implements the actions required to initialize a closed class type. Static constructors are declared using static_constructor_declarations:
A static_constructor_declaration may include a set of attributes (Attributes) and an extern modifier (External methods).
The identifier of a static_constructor_declaration must name the class in which the static constructor is declared. If any other name is specified, a compile-time error occurs.
When a static constructor declaration includes an extern modifier, the static constructor is said to be an external static constructor. Because an external static constructor declaration provides no actual implementation, its static_constructor_body consists of a semicolon. For all other static constructor declarations, the static_constructor_body consists of a block which specifies the statements to execute in order to initialize the class. This corresponds exactly to the method_body of a static method with a void return type (Method body).
Static constructors are not inherited, and cannot be called directly.
The static constructor for a closed class type executes at most once in a given application domain. The execution of a static constructor is triggered by the first of the following events to occur within an application domain:
If a class contains the Main method (Application Startup) in which execution begins, the static constructor for that class executes before the Main method is called.
To initialize a new closed class type, first a new set of static fields (Static and instance fields) for that particular closed type is created. Each of the static fields is initialized to its default value (Default values). Next, the static field initializers (Static field initialization) are executed for those static fields. Finally, the static constructor is executed.
must produce the output:
It is possible to construct circular dependencies that allow static fields with variable initializers to be observed in their default value state.
produces the output
Because the static constructor is executed exactly once for each closed constructed class type, it is a convenient place to enforce run-time checks on the type parameter that cannot be checked at compile-time via constraints (Type parameter constraints). For example, the following type uses a static constructor to enforce that the type argument is an enum:
Destructors
A destructor is a member that implements the actions required to destruct an instance of a class. A destructor is declared using a destructor_declaration:
A destructor_declaration may include a set of attributes (Attributes).
The identifier of a destructor_declaration must name the class in which the destructor is declared. If any other name is specified, a compile-time error occurs.
When a destructor declaration includes an extern modifier, the destructor is said to be an external destructor. Because an external destructor declaration provides no actual implementation, its destructor_body consists of a semicolon. For all other destructors, the destructor_body consists of a block which specifies the statements to execute in order to destruct an instance of the class. A destructor_body corresponds exactly to the method_body of an instance method with a void return type (Method body).
Destructors are not inherited. Thus, a class has no destructors other than the one which may be declared in that class.
Since a destructor is required to have no parameters, it cannot be overloaded, so a class can have, at most, one destructor.
Destructors are invoked automatically, and cannot be invoked explicitly. An instance becomes eligible for destruction when it is no longer possible for any code to use that instance. Execution of the destructor for the instance may occur at any time after the instance becomes eligible for destruction. When an instance is destructed, the destructors in that instance’s inheritance chain are called, in order, from most derived to least derived. A destructor may be executed on any thread. For further discussion of the rules that govern when and how a destructor is executed, see Automatic memory management.
The output of the example
since destructors in an inheritance chain are called in order, from most derived to least derived.
contains two errors.
The compiler behaves as if this method, and overrides of it, do not exist at all. Thus, this program:
is valid, and the method shown hides System.Object ‘s Finalize method.
For a discussion of the behavior when an exception is thrown from a destructor, see How exceptions are handled.
Iterators
A function member (Function members) implemented using an iterator block (Blocks) is called an iterator.
An iterator block may be used as the body of a function member as long as the return type of the corresponding function member is one of the enumerator interfaces (Enumerator interfaces) or one of the enumerable interfaces (Enumerable interfaces). It can occur as a method_body, operator_body or accessor_body, whereas events, instance constructors, static constructors and destructors cannot be implemented as iterators.
When a function member is implemented using an iterator block, it is a compile-time error for the formal parameter list of the function member to specify any ref or out parameters.
Enumerator interfaces
Enumerable interfaces
Yield type
An iterator produces a sequence of values, all of the same type. This type is called the yield type of the iterator.
Enumerator objects
When a function member returning an enumerator interface type is implemented using an iterator block, invoking the function member does not immediately execute the code in the iterator block. Instead, an enumerator object is created and returned. This object encapsulates the code specified in the iterator block, and execution of the code in the iterator block occurs when the enumerator object’s MoveNext method is invoked. An enumerator object has the following characteristics:
An enumerator object is typically an instance of a compiler-generated enumerator class that encapsulates the code in the iterator block and implements the enumerator interfaces, but other methods of implementation are possible. If an enumerator class is generated by the compiler, that class will be nested, directly or indirectly, in the class containing the function member, it will have private accessibility, and it will have a name reserved for compiler use (Identifiers).
An enumerator object may implement more interfaces than those specified above.
Note that enumerator objects do not support the IEnumerator.Reset method. Invoking this method causes a System.NotSupportedException to be thrown.
The MoveNext method
The MoveNext method of an enumerator object encapsulates the code of an iterator block. Invoking the MoveNext method executes code in the iterator block and sets the Current property of the enumerator object as appropriate. The precise action performed by MoveNext depends on the state of the enumerator object when MoveNext is invoked:
When MoveNext executes the iterator block, execution can be interrupted in four ways: By a yield return statement, by a yield break statement, by encountering the end of the iterator block, and by an exception being thrown and propagated out of the iterator block.
The Current property
An enumerator object’s Current property is affected by yield return statements in the iterator block.
The Dispose method
The Dispose method is used to clean up the iteration by bringing the enumerator object to the after state.
Enumerable objects
When a function member returning an enumerable interface type is implemented using an iterator block, invoking the function member does not immediately execute the code in the iterator block. Instead, an enumerable object is created and returned. The enumerable object’s GetEnumerator method returns an enumerator object that encapsulates the code specified in the iterator block, and execution of the code in the iterator block occurs when the enumerator object’s MoveNext method is invoked. An enumerable object has the following characteristics:
An enumerable object is typically an instance of a compiler-generated enumerable class that encapsulates the code in the iterator block and implements the enumerable interfaces, but other methods of implementation are possible. If an enumerable class is generated by the compiler, that class will be nested, directly or indirectly, in the class containing the function member, it will have private accessibility, and it will have a name reserved for compiler use (Identifiers).
The GetEnumerator method
An enumerable object provides an implementation of the GetEnumerator methods of the IEnumerable and IEnumerable interfaces. The two GetEnumerator methods share a common implementation that acquires and returns an available enumerator object. The enumerator object is initialized with the argument values and instance value saved when the enumerable object was initialized, but otherwise the enumerator object functions as described in Enumerator objects.
Implementation example
This section describes a possible implementation of iterators in terms of standard C# constructs. The implementation described here is based on the same principles used by the Microsoft C# compiler, but it is by no means a mandated implementation or the only one possible.
The following Stack class implements its GetEnumerator method using an iterator. The iterator enumerates the elements of the stack in top to bottom order.
The GetEnumerator method can be translated into an instantiation of a compiler-generated enumerator class that encapsulates the code in the iterator block, as shown in the following.
The following example prints a simple multiplication table of the integers 1 through 10. The FromTo method in the example returns an enumerable object and is implemented using an iterator.
The FromTo method can be translated into an instantiation of a compiler-generated enumerable class that encapsulates the code in the iterator block, as shown in the following.
The from and to parameters are turned into fields in the enumerable class. Because from is modified in the iterator block, an additional __from field is introduced to hold the initial value given to from in each enumerator.
The following example shows a simple tree class. The Tree class implements its GetEnumerator method using an iterator. The iterator enumerates the elements of the tree in infix order.
The GetEnumerator method can be translated into an instantiation of a compiler-generated enumerator class that encapsulates the code in the iterator block, as shown in the following.
The compiler generated temporaries used in the foreach statements are lifted into the __left and __right fields of the enumerator object. The __state field of the enumerator object is carefully updated so that the correct Dispose() method will be called correctly if an exception is thrown. Note that it is not possible to write the translated code with simple foreach statements.
Async functions
A method (Methods) or anonymous function (Anonymous function expressions) with the async modifier is called an async function. In general, the term async is used to describe any kind of function that has the async modifier.
It is a compile-time error for the formal parameter list of an async function to specify any ref or out parameters.
Evaluation of a task-returning async function
Invocation of a task-returning async function causes an instance of the returned task type to be generated. This is called the return task of the async function. The task is initially in an incomplete state.
The async function body is then evaluated until it is either suspended (by reaching an await expression) or terminates, at which point control is returned to the caller, along with the return task.
When the body of the async function terminates, the return task is moved out of the incomplete state:
Evaluation of a void-returning async function
This allows the context to keep track of how many void-returning async functions are running under it, and to decide how to propagate exceptions coming out of them.