10.7.2 Accessors

The accessor-declarations of a property specify the executable statements associated with reading and writing that property.

        accessor-declarations:
                get-accessor-declaration set-accessor-declarationopt
                set-accessor-declaration get-accessor-declarationopt

        get-accessor-declaration:
                attributesopt accessor-modifieropt get accessor-body

        set-accessor-declaration:
                attributesopt accessor-modifieropt set accessor-body

        accessor-modifier:
                protected
                internal
                private
                protected internal
                internal protected
        accessor-body:
                 block
                 ;

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:

•  An accessor-modifier may not be used in an interface or in an explicit interface member implementation.

•  For a property or indexer that has no override modifier, an accessor-modifier is permitted only if the property or indexer has both get and set accessors, and then is permitted only on one of those accessors.

•  For a property or indexer that includes an override modifier, an accessor must match the accessor-modifier (if any) of the accessor being overridden.

•  The accessor-modifier must declare an accessibility that is strictly more restrictive than the declared accessibility of the property or indexer itself. To be precise:

-  If the property or indexer has a declared accessibility of public, the accessor-modifier may be either protected internal, internal, protected, or private.

-  If the property or indexer has a declared accessibility of protected internal, the accessor-modifier may be either internal, protected, or private.

-  If the property or indexer has a declared accessibility of internal or protected, the accessor-modifier must be private.

-  If the property or indexer has a declared accessibility of private, no accessor-modifier may be used.

For abstract and extern properties, the accessor-body for each accessor specified is simply a semicolon. A non-abstract, non-extern property may be an automatically implemented property, in which case both get and set accessors must be given, both with a semicolon body (§10.7.3). For the accessors of any other non-abstract, non-extern property, the accessor-body is a block that 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 (§7.1.1). The body of a get accessor must conform to the rules for value-returning methods described in §10.6.10. 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 end point of a get accessor must not be reachable.

A set accessor corresponds to a method with a single value parameter of the property type and a void return type. The implicit parameter of a set accessor is always named value. When a property is referenced as the target of an assignment (§7.16), or as the operand of ++ or -7.5.9, §7.6.5), the set accessor is invoked with an argument (whose value is that of the right-hand side of the assignment or the operand of the ++ or - operator) that provides the new value (§7.16.1). The body of a set accessor must conform to the rules for void methods described in §10.6.10. In particular, return statements in the set accessor body are not permitted to specify an expression. Because a set accessor implicitly has a parameter named value, it is a compile-time error for a local variable or constant declaration in a set accessor to have that name.

Based on the presence or absence of the get and set accessors, a property is classified as follows:

•  A property that includes both a get accessor and a set accessor is said to be a read-write property.

•  A property that has only a get accessor is said to be a read-only property. It is a compile-time error for a read-only property to be the target of an assignment.

•  A property that has only a set accessor is said to be a write-only property. Except as the target of an assignment, it is a compile-time error to reference a write-only property in an expression.

In the example

        public class Button: Control
        {
                private string caption;
                public string Caption {
                        get {
                                return caption;
                        }
                        set {
                                if (caption != value) {
                                        caption = value;
                                        Repaint();
                        }
                }
        }
        public override void Paint(Graphics g, Rectangle r) {
                // Painting code goes here
                }
        }

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 whether the new value is different from the current value; if the two values are different, it stores the new value and repaints the control. Properties often follow the pattern shown in the example: 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 in the preceding code, the following is an example of use of the Caption property:

        Button okButton = new Button();
        okButton.Caption = "OK";                                // Invokes set accessor
        string s = okButton.Caption;                            // Invokes get accessor

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

        class A
        {
                private string name;
                public string Name {                                // Error, duplicate member name
                        get { return name; }
                }
                public string Name {                                // Error, duplicate member name
                        set { name = value; }
                }
        }

does not declare a single read-write property, but rather declares two properties with the same name—one read-only and one write-only. Because two members declared in the same class cannot have the same name, this 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

        class A
        {
                public int P {
                        set {}
                }
        }
        class B: A
        {
                new public int P {
                        get {}
                }
        }

the P property in B hides the P property in A with respect to both reading and writing. Thus, in the statements

        B b = new B();
        b.P = 1;                                // Error, B.P is read-only
        ((A)b).P = 1;                        // Okay, reference to A.P

the assignment to b.P causes a compile-time error to be reported, because the read-only P property in B hides the write-only P property in A. Note, however, that a cast can be used to access the hidden P property.

Unlike public fields, properties provide a separation between an object’s internal state and its public interface. Consider the following example:

        class Label
        {
                private int x, y;
                private string caption;
                public Label(int x, int y, string caption) {
                        this.x = x;
                        this.y = y;
                        this.caption = caption;
                }
                public int X {
                        get { return x; }
                }
                public int Y {
                        get { return y; }
                }
                public Point Location {
                        get { return new Point(x, y); }
                }
                public string Caption {
                        get { return caption; }
                }
        }

Here, the Label class uses two int fields, x and y, to store its location. The location is publicly exposed both as an X and a Y property and as a Location property of type Point. If, in a future version of Label, it becomes more convenient to store the location as a Point internally, the change can be made without affecting the public interface of the class:

        class Label
        {
                private Point location;
                private string caption;
                public Label(int x, int y, string caption) {
                        this.location = new Point(x, y);
                        this.caption = caption;
                }
                public int X {
                        get { return location.x; }
                }
                public int Y {
                        get { return location.y; }
                }
                public Point Location {
                        get { return location; }
                }
                public string Caption {
                        get { return caption; }
                }
        }

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 nonvirtual 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, which is known as inlining, makes property access as efficient as field access, yet preserves the increased flexibility of properties.

Given that 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

        class Counter
        {
                private int next;
                public int Next {
                        get { return next++; }
                }
        }

the value of the Next property depends on the number of times the property has previously been accessed. In this case, 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,

        using System.IO;
        public class Console
        {
                private static TextReader reader;
                private static TextWriter writer;
                private static TextWriter error;
                public static TextReader In {
                        get {
                                if (reader == null) {
                                        reader = new StreamReader(Console.OpenStandardInput());
                                }
                                return reader;
                        }
                }
                public static TextWriter Out {
                        get {
                                if (writer == null) {
                                        writer = new StreamWriter(Console.OpenStandardOutput());
                                }
                                return writer;
                        }
                }
                public static TextWriter Error {
                        get {
                                if (error == null) {
                                        error = new StreamWriter(Console.OpenStandardError());
                                }
                                return error;
                        }
                }
        }

The Console class contains three properties—In, Out, and Error—that represent the standard input, output, and error devices, respectively. By exposing these members as properties, the Console class can delay their initialization until they are actually used. For example, upon first referencing the Out property, as in

        Console.Out.WriteLine("hello, world");

the underlying TextWriter for the output device is created. If the application does not make any reference to the In and Error properties, however, no objects are created for those devices.

10.7.3 Automatically Implemented Properties

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.

The example

        public class Point {
                public int X { get; set; } // Automatically implemented
                public int Y { get; set; } // Automatically implemented
        }

is equivalent to the following declaration:

        public class Point {
                private int x;
                private int y;
                public int X { get { return x; } set { x = value; } }
                public int Y { get { return y; } set { y = value; } }
        }

Because the backing field is inaccessible, it can be read and written only through the property accessors, even within the containing type. As a consequence, automatically implemented read-only or write-only properties do not make sense and, therefore, are disallowed. Nevertheless, the access level of each accessor may be set differently. Thus the effect of a read-only property with a private backing field can be mimicked like this:

        public class ReadOnlyPoint {
                public int X { get; private set; }
                public int Y { get; private set; }
                public ReadOnlyPoint(int x, int y) { X = x; Y = y; }
        }

This restriction also means that definite assignment of struct types with auto-implemented properties can be achieved only using the standard constructor of the struct, as assigning to the property itself requires the struct to be definitely assigned. To do so, user-defined constructors must call the default constructor.

10.7.4 Accessibility

If an accessor has an accessor-modifier, the accessibility domain (§3.5.2) 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 (§7.3) or overload resolution (§7.4.3). 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:

•  If the usage is as a value (§7.1.1), the get accessor must exist and be accessible.

•  If the usage is as the target of a simple assignment (§7.16.1), the set accessor must exist and be accessible.

•  If the usage is as the target of compound assignment (§7.16.2), or as the target of the ++ or - operators (§7.5.9, §7.6.5), both the get and set accessors must exist and be accessible.

In the following example, the property A.Text is hidden by the property B.Text, even in contexts where only the set accessor is called. In contrast, the property B.Count is not accessible to class M, so the accessible property A.Count is used instead.

        class A
        {
                public string Text {
                        get { return "hello"; }
                        set { }
                }
                public int Count {
                        get { return 5; }
                        set { }
                }
        }

        class B: A
        {
                private string text = "goodbye";
                private int count = 0;
                new public string Text {
                        get { return text; }
                        protected set { text = value; }
                }
                new protected int Count {
                        get { return count; }
                        set { count = value; }
                }
        }
        class M
        {
                static void Main() {
                        B b = new B();
                        b.Count = 12;                                // Calls A.Count set accessor
                   int i = b.Count;                                  // Calls A.Count get accessor
                        b.Text = "howdy";                        // Error, B.Text set accessor not accessible
                        string s = b.Text;                         // Calls B.Text get accessor
                }
        }

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:

        public interface I
        {
                string Prop { get; }
        }
        public class C: I
        {
                public Prop {
                        get { return "April"; }    // Must not have a modifier here
                        internal set {…}                // Okay, because I.Prop has no set accessor
                }
        }

10.7.5 Virtual, Sealed, Override, and Abstract 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 permitted only in abstract classes (§10.1.1.1).The accessors of an inherited virtual property can be overridden in a derived class by including a property declaration that specifies an override directive—an approach 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, respectively. Specifically, the rules described in §10.6.3, §10.6.4, §10.6.5, and §10.6.6 apply as if accessors were methods of a corresponding form:

•  A get accessor corresponds to a parameterless method with a return value of the property type and the same modifiers as the containing property.

•  A set accessor corresponds to a method with a single value parameter of the property type, a void return type, and the same modifiers as the containing property.

In the example

        abstract class A
        {
                int y;
                public virtual int X {
                        get { return 0; }
                }
                public virtual int Y {
                        get { return y; }
                        set { y = value; }
                }
                public abstract int Z { get; set; }
        }

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 as abstract.

A class that derives from A is shown below:

        class B: A
        {
                int z;
                public override int X {
                        get { return base.X + 1; }
                }
                public override int Y {
                        set { base.Y = value < 0? 0: value; }
                }
                public override int Z {
                        get { return z; }
                        set { z = value; }
                }
        }

Here, the declarations of X, Y, and Z are overriding property declarations. Each property declaration exactly matches the accessibility modifiers, type, and name of the corresponding inherited property. The get accessor of X and the set accessor of Y use the base keyword to access the inherited accessors. The declaration of Z overrides both abstract accessors—thus there are no outstanding abstract function members in B, and B is permitted to be a nonabstract class.

When a property is declared as an override, any overridden accessors must be accessible to the overriding code. In addition, the declared accessibility of both the property or the indexer itself, and of the accessors, must match that of the overridden member and accessors. For example,

        public class B
        {
                public virtual int P {
                        protected set {…}
                        get {…}
                }
        }
        public class D: B
        {
                public override int P {
                        protected set {…}                                // Must specify protected here
                        get {…}                                                 // Must not have a modifier here
                }
        }

10.8 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:

        event-declaration:
                attributesopt event-modifiersopt event type variable-declarators ;
                attributesopt event-modifiersopt event type member-name { event-accessor-declarations }

        event-modifiers:
                event-modifier
                event-modifiers event-modifier

        event-modifier:
                new
                public
                protected
                internal
                private
                static
                virtual
                sealed
                override
                abstract
                extern

        event-accessor-declarations:
                add-accessor-declaration remove-accessor-declaration
                remove-accessor-declaration add-accessor-declaration

        add-accessor-declaration:
                attributesopt add block

        remove-accessor-declaration:
                attributesopt remove block

An event-declaration may include a set of attributes (§17); a valid combination of the four access modifiers (§10.3.5); and the new10.3.4), static10.6.2), virtual10.6.3), override10.6.4), sealed10.6.5), abstract10.6.6), and extern10.6.7) modifiers.

Event declarations are subject to the same rules as method declarations (§10.6) with regard to valid combinations of modifiers.

The type of an event declaration must be a delegate-type4.2), and that delegate-type must be at least as accessible as the event itself (§3.5.4).

An event declaration may include event-accessor-declarations. However, if it does not, for non-extern, non-abstract events, the compiler supplies them automatically (§10.8.1); 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.

An event can be used as the left-hand operand of the += and -= operators (§7.16.3). These operators are used to attach event handlers to or to remove event handlers from an event, respectively, and the access modifiers of the event control the contexts in which such operations are permitted.

Because += and -= are the only operations that are permitted on an event outside the type that declares the event, external code can add and remove handlers for an event, but cannot in any other way obtain or modify the underlying list of event handlers.

In an operation of the form x += y or x -= y, when x is an event and the reference takes place outside the type that contains the declaration of x, the result of the operation has type void (as opposed to having the type of x, with the value of x after the assignment). This rule prohibits external code from indirectly examining the underlying delegate of an event.

The following example shows how event handlers are attached to instances of the Button class.

        public delegate void EventHandler(object sender, EventArgs e);
        public class Button: Control
        {
                public event EventHandler Click;
        }
        public class LoginDialog: Form
        {
                Button OkButton;
                Button CancelButton;
                public LoginDialog() {
                        OkButton = new Button(…);
                        OkButton.Click += new EventHandler(OkButtonClick);
                        CancelButton = new Button(…);
                        CancelButton.Click += new EventHandler(CancelButtonClick);
                }
                void OkButtonClick(object sender, EventArgs e) {
                        // Handle OkButton.Click event
                }
                void CancelButtonClick(object sender, EventArgs e) {
                        // Handle CancelButton.Click event
                }
        }

Here, the LoginDialog instance constructor creates two Button instances and attaches event handlers to the Click events.

10.8.1 Field-like Events

Within the program text of the class or struct that contains the declaration of an event, certain events can be used like fields. To be used in this way, an event must not be abstract or extern, and it must not explicitly include event-accessor-declarations. Such an event can be used in any context that permits a field. The field contains a delegate (§15) that refers to the list of event handlers that have been added to the event. If no event handlers have been added, the field contains null.

In the example

        public delegate void EventHandler(object sender, EventArgs e);
        public class Button: Control
        {
                public event EventHandler Click;
                protected void OnClick(EventArgs e) {
                        if (Click != null) Click(this, e);
                }
                public void Reset() {
                        Click = null;
                }
        }

Click is used as a field within the Button class. As the example demonstrates, this 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.

Outside the declaration of the Button class, the Click member can be used only on the left-hand side of the += and –= operators. For example,

        b.Click += new EventHandler();

appends a delegate to the invocation list of the Click event. Similarly,

        b.Click –= new EventHandler();

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 it creates accessors for the event that add or remove event handlers to the delegate field. To be thread safe, the addition or removal operations are performed while holding the lock (§8.12) on the containing object for an instance event, or the type object (§7.5.10.6) for a static event.

For example, an instance event declaration of the form

        class X
        {
                public event D Ev;
        }

could be compiled to something equivalent to

        class X
        {
                private D __Ev; // Field to hold the delegate
                public event D Ev {
                        add {
                                lock(this) { __Ev = __Ev + value; }
                }
                remove {
                                lock(this) { __Ev = __Ev - value; }
                        }
                }
        }

Within the class X, references to Ev are compiled to reference the hidden field __Ev instead. The name “__Ev” is arbitrary; the hidden field could have any name or no name at all.

Similarly, a static event declaration of the form

        class X
        {
                public static event D Ev;
        }

could be compiled to something equivalent to

        class X
        {
                private static D __Ev; // Field to hold the delegate
                public static event D Ev {
                        add {
                                lock(typeof(X)) { __Ev = __Ev + value; }
                        }
                        remove {
                                lock(typeof(X)) { __Ev = __Ev - value; }
                        }
                }
        }

10.8.2 Event Accessors

Event declarations typically omit event-accessor-declarations, as in the Button example given earlier in this section. One situation in which doing so is appropriate is when 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.

Each add-accessor-declaration and remove-accessor-declaration corresponds to a method with a single value parameter of the event type and a void return type. The implicit parameter of an event accessor is named value. When an event is used in an event assignment, the appropriate event accessor is used. Specifically, if the assignment operator is +=, then the add accessor is used; if the assignment operator is -=, then the remove accessor is used. In either case, the right-hand operand of the assignment operator is used as the argument to the event accessor. The block of an add-accessor-declaration or a remove-accessor-declaration must conform to the rules for void methods described in §10.6.10. In particular, return statements in such a block are not permitted to specify an expression.

Because an event accessor implicitly has a parameter named value, it is a compile-time error for a local variable or constant declared in an event accessor to have that name.

In the example

        class Control: Component
        {
                // Unique keys for events
                static readonly object mouseDownEventKey = new object();
                static readonly object mouseUpEventKey = new object();
                // Return event handler associated with key
                protected Delegate GetEventHandler(object key) {…}
                // Add event handler associated with key
                protected void AddEventHandler(object key, Delegate handler) {…}
                // Remove event handler associated with key
                protected void RemoveEventHandler(object key, Delegate handler) {…}
                // MouseDown event
                public event MouseEventHandler MouseDown {
                        add { AddEventHandler(mouseDownEventKey, value); }
                        remove { RemoveEventHandler(mouseDownEventKey, value); }
                }
                // MouseUp event
                public event MouseEventHandler MouseUp {
                        add { AddEventHandler(mouseUpEventKey, value); }
                        remove { RemoveEventHandler(mouseUpEventKey, value); }
                }
                // Invoke the MouseUp event
                protected void OnMouseUp(MouseEventArgs args) {
                        MouseEventHandler handler;
                        handler = (MouseEventHandler)GetEventHandler(mouseUpEventKey);
                        if (handler != null)
                                handler(this, args);
                }
        }

the Control class implements an internal storage mechanism for events. The AddEvent-Handler 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.

10.8.3 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 this7.5.7) in the accessors of that event.

When an event is referenced in a member-access7.5.4) of the form E.M, if M is a static event, E must denote a type containing M; if M is an instance event, E must denote an instance of a type containing M.

The differences between static and instance members are discussed further in §10.3.7.

10.8.4 Virtual, Sealed, Override, and Abstract 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 permitted only in abstract classes (§10.1.1.1).

The accessors of an inherited virtual event can be overridden in a derived class by including an event declaration that specifies an override modifier—an approach 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, respectively. Specifically, the rules described in §10.6.3, §10.6.4, §10.6.5, and §10.6.6 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.

10.9 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:

        indexer-declaration:
                attributesopt indexer-modifiersopt indexer-declarator { accessor-declarations }

        indexer-modifiers:
                indexer-modifier
                indexer-modifiers indexer-modifier

        indexer-modifier:
                new
                public
                protected
                internal
                private
                virtual
                sealed
                override
                abstract
                extern

        indexer-declarator:
                type this [ formal-parameter-list ]
                type interface-type . this [ formal-parameter-list ]

An indexer-declaration may include a set of attributes (§17); a valid combination of the four access modifiers (§10.3.5); and the new10.3.4), virtual10.6.3), override10.6.4), sealed10.6.5), abstract10.6.6), and extern10.6.7) modifiers.

Indexer declarations are subject to the same rules as method declarations (§10.6) with regard to valid combinations of modifiers, with the one exception being that the static modifier is not permitted on an indexer declaration.

The modifiers virtual, override, and abstract are mutually exclusive except in one case. Specifically, the abstract and override modifiers may be used together so that an abstract indexer can override a virtual one.

The type of an indexer declaration specifies the element type of the indexer introduced by the declaration. Unless the indexer is an explicit interface member implementation, the type is followed by the keyword this. For an explicit interface member implementation, the type is followed by an interface-type, a “.”, and the keyword this. Unlike other members, indexers do not have user-defined names.

The formal-parameter-list specifies the parameters of the indexer. The formal parameter list of an indexer corresponds to that of a method (§10.6.1), 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 (§3.5.4).

The accessor-declarations10.7.2), which must be enclosed in “{” and “}” tokens, declare the accessors of the indexer. The accessors specify the executable statements associated with reading and writing indexer elements.

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 (§3.6) 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:

•  A property is identified by its name, whereas an indexer is identified by its signature.

•  A property is accessed through a simple-name7.5.2) or a member-access7.5.4), whereas an indexer element is accessed through an element-access7.5.6.2).

•  A property can be a static member, whereas an indexer is always an instance member.

•  A get accessor of a property corresponds to a method with no parameters, whereas a get accessor of an indexer corresponds to a method with the same formal parameter list as the indexer.

•  A set accessor of a property corresponds to a method with a single parameter named value, whereas a set accessor of an indexer corresponds to a method with the same formal parameter list as the indexer, plus an additional parameter named value.

•  It is a compile-time error for an indexer accessor to declare a local variable with the same name as an indexer parameter.

•  In an overriding property declaration, the inherited property is accessed using the syntax base.P, where P is the property name. In an overriding indexer declaration, the inherited indexer is accessed using the syntax base[E], where E is a comma-separated list of expressions.

Aside from these differences, all rules defined in §10.7.2 and §10.7.3 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 following example declares a BitArray class that implements an indexer for accessing the individual bits in the bit array.

        using System;
        class BitArray
        {
                int[] bits;
                int length;
                public BitArray(int length) {
                        if (length < 0) throw new ArgumentException();
                        bits = new int[((length - 1) >> 5) + 1];
                        this.length = length;
                }
                public int Length {
                        get { return length; }
                }
                public bool this[int index] {
                        get {
                                if (index < 0 || index >= length) {
                                        throw new IndexOutOfRangeException();
                                }
                                return (bits[index >> 5] & 1 << index) != 0;
                        }
                        set {
                                if (index < 0 || index >= length) {
                                        throw new IndexOutOfRangeException();
                                }
                                if (value) {
                                        bits[index >> 5] |= 1 << index;
                                }
                                else {
                                        bits[index >> 5] &= ~(1 << index);
                                }
                        }
                }
        }

An instance of the BitArray class consumes substantially less memory than a corresponding bool[] (because each value of the former class occupies only one bit instead of the latter’s one byte), but it permits the same operations as a bool[].

The following CountPrimes class uses a BitArray and the classical “sieve” algorithm to compute the number of primes between 1 and a given maximum.

        class CountPrimes
        {
                static int Count(int max) {
                BitArray flags = new BitArray(max + 1);
                int count = 1;
                for (int i = 2; i <= max; i++) {
                        if (!flags[i]) {
                                for (int j = i * 2; j <= max; j += i) flags[j] = true;
                                count++;
                                }
                        }
                        return count;
                }
                static void Main(string[] args) {
                        int max = int.Parse(args[0]);
                        int count = Count(max);
                        Console.WriteLine("Found {0} primes between 1 and {1}", count, max);
                }
        }

Note that the syntax for accessing elements of the BitArray is precisely the same as for a bool[].

The following example shows a 26 × 10 grid class that has an indexer with two parameters. The first parameter is required to be an uppercase or lowercase letter in the range A–Z, and the second is required to be an integer in the range 0–9.

        using System;
        class Grid
        {
                const int NumRows = 26;
                const int NumCols = 10;
                int[,] cells = new int[NumRows, NumCols];
                public int this[char c, int col] {
                        get {
                                c = Char.ToUpper(c);
                                if (c < 'A' || c > 'Z') {
                                        throw new ArgumentException();
                                }
                                if (col < 0 || col >= NumCols) {
                                        throw new IndexOutOfRangeException();
                                }
                                return cells[c - 'A', col];
                        }
                        set {
                                c = Char.ToUpper(c);
                                if (c < 'A' || c > 'Z') {
                                        throw new ArgumentException();
                                }
                                if (col < 0 || col >= NumCols) {
                                        throw new IndexOutOfRangeException();
                                }
                                cells[c - 'A', col] = value;
                        }
                }
        }

10.9.1 Indexer Overloading

The indexer overload resolution rules are described in §7.4.2.

10.10 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:

        operator-declaration:
                attributesopt operator-modifiers operator-declarator operator-body

        operator-modifiers:
                operator-modifier
                operator-modifiers operator-modifier

        operator-modifier:
                public
                static
                extern

        operator-declarator:
                unary-operator-declarator
                binary-operator-declarator
                conversion-operator-declarator

        unary-operator-declarator:
                type operator overloadable-unary-operator ( type identifier )

        overloadable-unary-operator: one of

                      +    -    !    ~    ++    -    true    false

        binary-operator-declarator:
                type operator overloadable-binary-operator ( type identifier , type identifier )

        overloadable-binary-operator:
                +
                -
                *
                /
                %
                &
                |
                ^
                <<
                right-shift
                ==
                !=
                >
                <
                >=
                <=

        conversion-operator-declarator:
                implicit operator type ( type identifier )
                explicit operator type ( type identifier )

        operator-body:
                block
                ;

Three categories of overloadable operators are distinguished: unary operators (§10.10.1), binary operators (§10.10.2), and conversion operators (§10.10.3).

When an operator declaration includes an extern modifier, the operator is said to be an external operator. Because an external operator provides no actual implementation, its operator-body consists of a semicolon. For all other operators, the operator-body consists of a block, which specifies the statements to execute when the operator is invoked. The block of an operator must conform to the rules for value-returning methods described in §10.6.10.

The following rules apply to all operator declarations:

•  An operator declaration must include both a public modifier and a static modifier.

•  The parameter(s) of an operator must be value parameters. It is a compile-time error for an operator declaration to specify ref or out parameters.

•  The signature of an operator (§10.10.1, §10.10.2, §10.10.3) must differ from the signatures of all other operators declared in the same class.

•  All types referenced in an operator declaration must be at least as accessible as the operator itself (§3.5.4).

•  It is an error for the same modifier to appear multiple times in an operator declaration.

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 §7.2.

Additional information on conversion operators can be found in §6.4.

10.10.1 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:

•  A unary +, -, !, or ~ operator must take a single parameter of type T or T? and can return any type.

•  A unary ++ or - operator must take a single parameter of type T or T? and must return that same type or a type derived from it.

•  A unary true or false operator must take a single parameter of type T or T? and must return type bool.

The signature of a unary operator consists of the operator token (+, -, !, ~, ++, -, true, or false) and the type of the single formal parameter. The return type is not part of a unary operator’s signature, nor is the name of the formal parameter.

The true and false unary operators require pairwise 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 §7.11.2 and §7.19.

The following example shows an implementation and subsequent usage of operator ++ for an integer vector class.

        public class IntVector
        {
                public IntVector(int length) {…}
                public int Length {…}                                                // Read-only property
                public int this[int index] {…}                                    // Read-write indexer
                public static IntVector operator ++(IntVector iv) {
                        IntVector temp = new IntVector(iv.Length);
                        for (int i = 0; i < iv.Length; i++)
                                temp[i] = iv[i] + 1;
                        return temp;
                }
        }
        class Test
        {
                static void Main() {
                        IntVector iv1 = new IntVector(4); // Vector of 4 x 0
                        IntVector iv2;
                        iv2 = iv1++; // iv2 contains 4 x 0, iv1 contains 4 x 1
                        iv2 = ++iv1; // iv2 contains 4 x 2, iv1 contains 4 x 2
                }
        }

The operator method returns the value produced by adding 1 to the operand, just like the postfix increment and decrement operators (§7.5.9), and the prefix increment and decrement operators (§7.6.5). 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.

10.10.2 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:

•  A binary nonshift operator must take two parameters, at least one of which must have type T or T?, and can return any type.

•  A binary << or >> operator must take two parameters, the first of which must have type T or T? and the second of which must have type int or int?, and can return any type.

The signature of a binary operator consists of the operator token (+, -, *, /, %, &, |, ^, <<, >>, ==, !=, >, <, >=, or <=) plus the types of the two formal parameters. The return type and the names of the formal parameters are not part of a binary operator’s signature.

Certain binary operators require pairwise declaration; that is, 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 pairwise declaration:

•  operator == and operator !=

•  operator > and operator <

•  operator >= and operator <=

10.10.3 Conversion Operators

A conversion operator declaration introduces a user-defined conversion6.4) that augments the predefined 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 issue is described further in §6.1.

A conversion operator declaration that includes the explicit keyword introduces a user-defined explicit conversion. Explicit conversions can occur in cast expressions; they are described further in §6.2.

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 a given source type S and target type T, if S or T is a nullable type, let S0 and T0 refer to their underlying types; otherwise, S0 and T0 are equal to S and T, respectively. A class or struct is permitted to declare a conversion from a source type S to a target type T only if all of the following criteria are met:

•  S0 and T0 are different types.

•  Either S0 or T0 is the class or struct type in which the operator declaration takes place.

•  Neither S0 nor T0 is an interface-type.

•  Excluding user-defined conversions, a conversion does not exist from S to T or from T to S.

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.

In the example

        class C<T> {…}
        class D<T>: C<T>
        {
                public static implicit operator C<int>(D<T> value) {…}                // Okay
                public static implicit operator C<string>(D<T> value) {…}          // Okay
                public static implicit operator C<T>(D<T> value) {…}                   // Error
        }

the first two operator declarations are permitted because, for the purposes of §10.9.3, T and int and string, respectively, are considered unique types with no relationship. However, the third operator is an error because C<T> is the base class of D<T>.

From the second rule, it follows that a conversion operator must convert either to or from the class or struct type in which the operator is declared. For example, it is possible for a class or struct type C to define a conversion from C to int and from int to C, but not from int to bool.

It is not possible to directly redefine a predefined 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 types nor the target types of a conversion can be a base type of the other, because 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 predefined conversions. In the example

        struct Convertible<T>
        {
                public static implicit operator Convertible<T>(T value) {…}
                public static explicit operator T(Convertible<T> value) {…}
        }

when type object is specified as a type argument for T, the second operator declares a conversion that already exists (an implicit conversion—and therefore an explicit conversion—exists from any type to type object).

In cases where a predefined conversion exists between two types, any user-defined conversions between those types are ignored. Specifically:

•  If a predefined implicit conversion (§6.1) exists from type S to type T, all user-defined conversions (implicit or explicit) from S to T are ignored.

•  If a predefined explicit conversion (§6.2) exists from type S to type T, any user-defined explicit conversions from S to T are ignored. However, user-defined implicit conversions from S to T are still considered.

For all types except object, the operators declared by the Convertible<T> type in the preceding example do not conflict with predefined conversions. For example,

        void F(int i, Convertible<int> n) {
                i = n;                                                                // Error
                i = (int)n;                                                        // User-defined explicit conversion
                n = i;                                                                // User-defined implicit conversion
                n = (Convertible<int>)i;                               // User-defined implicit conversion
        }

However, for type object, predefined conversions hide the user-defined conversions in all cases except one:

        void F(object o, Convertible<object> n) {
                o = n;                                                             // Predefined boxing conversion
                o = (object)n;                                                // Predefined boxing conversion
                n = o;                                                             // User-defined implicit conversion
                n = (Convertible<object>)o;                     // Predefined unboxing conversion
        }

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. (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 implicit and explicit conversion operators 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.

In the example

        using System;
        public struct Digit
        {
                byte value;
                public Digit(byte value) {
                        if (value < 0 || value > 9) throw new ArgumentException();
                        this.value = value;
                }
                public static implicit operator byte(Digit d) {
                        return d.value;
                }
                public static explicit operator Digit(byte b) {
                        return new Digit(b);
                }
        }

the conversion from Digit to byte is implicit because it never throws exceptions or loses information. By contrast, the conversion from byte to Digit is explicit because Digit can only represent a subset of the possible values of a byte.

10.11 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:

        constructor-declaration:
                attributesopt constructor-modifiersopt constructor-declarator constructor-body

        constructor-modifiers:
                constructor-modifier
                constructor-modifiers constructor-modifier

        constructor-modifier:
                public
                protected
                internal
                private
                extern

        constructor-declarator:
                identifier ( formal-parameter-listopt ) constructor-initializeropt

        constructor-initializer:
                : base( argument-listopt )
                : this( argument-listopt )

        constructor-body:
                block
                ;

A constructor-declaration may include a set of attributes (§17), a valid combination of the four access modifiers (§10.3.5), and an extern10.6.7) 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 (§10.6). The formal parameter list defines the signature (§3.6) of an instance constructor and governs the process whereby overload resolution (§7.4.2) 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 (§3.5.4).

The optional constructor-initializer specifies another instance constructor to invoke before executing the statements given in the constructor-body of this instance constructor. This issue is described further in §10.11.1.

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 that 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 (§10.6.10).

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 (§10.11.4).

Instance constructors are invoked by object-creation-expressions (§7.5.10.1) and through constructor-initializers.

10.11.1 Constructor Initializers

All instance constructors (except those for class object) implicitly include an invocation of another instance constructor immediately before the constructor-body. Which constructor to implicitly invoke is determined by the constructor-initializer:

•  An instance constructor initializer of the form base (argument-listopt) causes an instance constructor from the direct base class to be invoked. That constructor is selected using argument-list and the overload resolution rules of §7.4.3. The set of candidate instance constructors consists of all accessible instance constructors contained in the direct base class, or the default constructor (§10.11.4) if no instance constructors are declared in the direct base class. If this set is empty, or if a single best instance constructor cannot be identified, a compile-time error occurs.

•  An instance constructor initializer of the form this (argument-listopt) causes an instance constructor from the class itself to be invoked. The constructor is selected using argument-list and the overload resolution rules of §7.4.3. The set of candidate instance constructors consists of all accessible instance constructors declared in the class itself. If this set is empty, or if a single best instance constructor cannot be identified, a compile-time error occurs. If an instance constructor declaration includes a constructor initializer that invokes the constructor itself, a compile-time error occurs.

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

        C(…) {…}

is exactly equivalent to

        C(…): base() {…}

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,

        class A
        {
                public A(int x, int y) {}
        }
        class B: A
        {
                public B(int x, int y): base(x + y, x - y) {}
        }

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, because is it a compile-time error for an argument expression to reference any instance member through a simple-name.

10.11.2 Instance Variable Initializers

When an instance constructor has no constructor initializer, or when it has a constructor initializer of the form base(…), that constructor implicitly performs the initializations specified by the variable-initializers of the instance fields declared in its class. This corresponds to a sequence of assignments that are executed immediately upon entry to the constructor and before the implicit invocation of the direct base class constructor. The variable initializers are executed in the textual order in which they appear in the class declaration.

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

In the example

        using System;
        class A
        {
                public A() {
                        PrintFields();
                }
                public virtual void PrintFields() {}
        }
        class B: A
        {
                int x = 1;
                int y;
                public B() {
                        y = -1;
                }
                public override void PrintFields() {
                        Console.WriteLine("x = {0}, y = {1}", x, y);
                }
        }

when new B() is used to create an instance of B, the following output is produced:

        x = 1, y = 0

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

        using System;
        using System.Collections;
        class A
        {
                int x = 1, y = -1, count;
                public A() {
                        count = 0;
                }
                public A(int n) {
                        count = n;
                }
        }

        class B: A
        {
                double sqrt2 = Math.Sqrt(2.0);
                ArrayList items = new ArrayList(100);
                int max;
                public B(): this(100) {
                        items.Add("default");
                }
                public B(int n): base(n – 1) {
                        max = n;
                }
        }

contains several variable initializers; it also contains constructor initializers of both forms (base and this). The example corresponds to the following code, 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).

        using System.Collections;
        class A
        {
                int x, y, count;
                public A() {
                        x = 1;                                                                // Variable initializer
                        y = -1;                                                              // Variable initializer
                        object();                                                           // Invoke object() constructor
                        count = 0;
                }
                public A(int n) {
                        x = 1;                                                                // Variable initializer
                        y = -1;                                                              // Variable initializer
                        object();                                                          // Invoke object() constructor
                        count = n;
                }
        }
        class B: A
        {
                double sqrt2;
                ArrayList items;
                int max;
                public B(): this(100) {
                      B(100);                                                                // Invoke B(int) constructor
                        items.Add("default");
                }
                public B(int n): base(n - 1) {
                        sqrt2 = Math.Sqrt(2.0);                                    // Variable initializer
                        items = new ArrayList(100);                          // Variable initializer
                        A(n – 1);                                                             // Invoke A(int) constructor
                        max = n;
                }
        }

10.11.4 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 direct base class does not have an accessible parameterless instance constructor, a compile-time error occurs. 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

        protected C(): base() {}

or

        public C(): base() {}

where C is the name of the class.

In the example

        class Message
        {
                object sender;
                string text;
        }

a default constructor is provided because the class contains no instance constructor declarations. This example is precisely equivalent to the following code:

        class Message {
                object sender;
                string text;
                public Message(): base() {}
        }

10.11.5 Private Constructors

When a class T declares only private instance constructors, it is not possible for classes outside the program text of T to derive from T or to directly create instances of T. Thus, if a class contains only static members and isn’t intended to be instantiated, adding an empty private instance constructor will prevent instantiation.

For example,

        public class Trig
        {
                private Trig() {}                // Prevent instantiation
                public const double PI = 3.14159265358979323846;
                public static double Sin(double x) {…}
                public static double Cos(double x) {…}
                public static double Tan(double x) {…}
        }

The Trig class groups related methods and constants, but is not intended to be instantiated, so 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.

10.11.6 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

        class Text {
                public Text(): this(0, 0, null) {}
                public Text(int x, int y): this(x, y, null) {}
                public Text(int x, int y, string s) {
                        // Actual constructor implementation
                }
        }

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:

        Text t1 = new Text();                                                // Same as Text(0, 0, null)
        Text t2 = new Text(5, 10);                                      // Same as Text(5, 10, null)
        Text t3 = new Text(5, 20, "Hello");

10.12 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:

        static-constructor-declaration:
                attributesopt static-constructor-modifiers identifier ( ) static-constructor-body

        static-constructor-modifiers:
                externopt static
                static externopt
        static-constructor-body:
                block
                ;

A static-constructor-declaration may include a set of attributes (§17) and an extern modifier (§10.6.7).

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 that specifies the statements to execute so as to initialize the class. This block corresponds exactly to the method-body of a static method with a void return type (§10.6.10).

Static constructors are not inherited, and they 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:

•  An instance of the class type is created.

•  Any of the static members of the class type are referenced.

If a class contains the Main method (§3.1) 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 (§10.5.1) for that particular closed type is created. Each of the static fields is initialized to its default value (§5.2). Next, the static field initializers (§10.4.5.1) are executed for those static fields. Finally, the static constructor is executed.

The example

        using System;
        class Test
        {
                static void Main() {
                        A.F();
                        B.F();
                }
        }

        class A
        {
                static A() {
                        Console.WriteLine("Init A");
                }
                public static void F() {
                        Console.WriteLine("A.F");
                }
        }
        class B
        {
                static B() {
                        Console.WriteLine("Init B");
                }
                public static void F() {
                        Console.WriteLine("B.F");
                }
        }

must produce the following output:

        Init A
        A.F
        Init B
        B.F

Here, the execution of A’s static constructor is triggered by the call to A.F, and the execution of B’s static constructor is triggered by the call to B.F.

It is possible to construct circular dependencies that allow static fields with variable initializers to be observed in their default value state.

The example

        using System;
        class A
        {
                public static int X;
                static A() {
                        X = B.Y + 1;
                }
        }
        class B
        {
                public static int Y = A.X + 1;
                static B() {}
                static void Main() {
                        Console.WriteLine("X = {0}, Y = {1}", A.X, B.Y);
                }
        }

produces the following output:

        X = 1, Y = 2

To execute the Main method, the system first runs the initializer for B.Y, prior to class B’s static constructor. Y’s initializer causes A’s static constructor to be run because the value of A.X is referenced. The static constructor of A, in turn, computes the value of X, and in doing so fetches the default value of Y, which is zero. As a consequence, A.X is initialized to 1. The process of running A’s static field initializers and static constructor then finishes, returning to the calculation of the initial value of Y, the result of which becomes 2.

Because the static constructor is executed exactly once for each closed constructed class type, it is a convenient place to enforce runtime checks on the type parameter that cannot be checked at compile time via constraints (§10.1.5). For example, the following type uses a static constructor to enforce that the type argument is an enum:

        class Gen<T> where T: struct
        {
                static Gen() {
                        if (!typeof(T).IsEnum) {
                                throw new ArgumentException("T must be an enum");
                        }
                }
        }

10.13 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:

        destructor-declaration:
                attributesopt externopt ~ identifier ( ) destructor-body

        destructor-body:
                block
                ;

A destructor-declaration may include a set of attributes (§17).

The identifier of a destructor-declarator 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 that specifies the statements to execute so as to destroy an instance of the class. A destructor-body corresponds exactly to the method-body of an instance method with a void return type (§10.6.10).

Destructors are not inherited, so a class has no destructors other than the one that may be declared in that class.

Because a destructor is required to have no parameters, it cannot be overloaded. As a result, a class can have, at most, one destructor.

Destructors are invoked automatically; they 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 §3.9.

The example

        using System;
        class A
        {
                ~A() {
                        Console.WriteLine("A's destructor");
                }
        }
        class B: A
        {
                ~B() {
                        Console.WriteLine("B's destructor");
                }
        }
        class Test
        {
                static void Main() {
                        B b = new B();
                        b = null;
                        GC.Collect();
                        GC.WaitForPendingFinalizers();
                }
        }

produces the following output:

        B's destructor
        A's destructor

This result occurs because destructors in an inheritance chain are called in order, from most derived to least derived.

Destructors are implemented by overriding the virtual method Finalize on System.Object. C# programs are not permitted to override this method or call it (or overrides of it) directly. For instance, the program

        class A
        {
                override protected void Finalize() {}        // Error
                public void F() {
                        this.Finalize();                                    // Error
                }
        }

contains two errors. The compiler behaves as if this method, and overrides of it, do not exist at all. By comparison, the program

        class A
        {
                void Finalize() {}                                                // Permitted
        }

is valid, because the method shown hides System.Object’s Finalize method.

For a discussion of the behavior when an exception is thrown from a destructor, see §16.3.

10.14 Iterators

A function member (§7.4) implemented using an iterator block (§8.2) 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 (§10.14.1) or one of the enumerable interfaces (§10.14.2). 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.

10.14.1 Enumerator Interfaces

The enumerator interfaces are the nongeneric interface System.Collections.IEnumerator and all instantiations of the generic interface System.Collections.Generic.IEnumerator<T>. For the sake of brevity, in this section these interfaces are referenced as IEnumerator and IEnumerator<T>, respectively.

10.14.2 Enumerable Interfaces

The enumerable interfaces are the nongeneric interface System.Collections.IEnumerable and all instantiations of the generic interface System.Collections.Generic.IEnumerable<T>. For the sake of brevity, in this section these interfaces are referenced as IEnumerable and IEnumerable<T>, respectively.

10.14.3 Yield Type

An iterator produces a sequence of values, all of the same type. This type is called the yield type of the iterator.

•  The yield type of an iterator that returns IEnumerator or IEnumerable is object.

•  The yield type of an iterator that returns IEnumerator<T> or IEnumerable<T> is T.

10.14.4 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:

•  It implements IEnumerator and IEnumerator<T>, where T is the yield type of the iterator.

•  It implements System.IDisposable.

•  It is initialized with a copy of the argument values (if any) and instance value passed to the function member.

•  It has four potential states— before, running, suspended, and after—and is initially in the before state.

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; other methods of implementation are also possible, however. 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 (§2.4.2).

An enumerator object may implement more interfaces than those specified above.

The following sections describe the exact behavior of the MoveNext, Current, and Dispose members of the IEnumerable and IEnumerable<T> interface implementations provided by an enumerator object.

Enumerator objects do not support the IEnumerator.Reset method. Invoking this method causes a System.NotSupportedException to be thrown.

10.14.4.1 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:

•  If the state of the enumerator object is before, invoking MoveNext:

-  Changes the state to running.

-  Initializes the parameters (including this) of the iterator block to the argument values and instance value saved when the enumerator object was initialized.

-  Executes the iterator block from the beginning until execution is interrupted (as described below).

•  If the state of the enumerator object is running, the result of invoking MoveNext is unspecified.

•  If the state of the enumerator object is suspended, invoking MoveNext:

-  Changes the state to running.

-  Restores the values of all local variables and parameters (including this) to the values saved when execution of the iterator block was last suspended. Note that the contents of any objects referenced by these variables may have changed since the previous call to MoveNext.

-  Resumes execution of the iterator block immediately following the yield return statement that caused the suspension of execution and continues until execution is interrupted (as described below).

•  If the state of the enumerator object is after, invoking MoveNext returns false.

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.

•  When a yield return statement is encountered (§8.14):

-  The expression given in the statement is evaluated, implicitly converted to the yield type, and assigned to the Current property of the enumerator object.

-  Execution of the iterator body is suspended. The values of all local variables and parameters (including this) are saved, as is the location of this yield return statement. If the yield return statement is within one or more try blocks, the associated finally blocks are not executed at this time.

-  The state of the enumerator object is changed to suspended.

-  The MoveNext method returns true to its caller, indicating that the iteration successfully advanced to the next value.

•  When a yield break statement is encountered (§8.14):

-  If the yield break statement is within one or more try blocks, the associated finally blocks are executed.

-  The state of the enumerator object is changed to after.

-  The MoveNext method returns false to its caller, indicating that the iteration is complete.

•  When the end of the iterator body is encountered:

-  The state of the enumerator object is changed to after.

-  The MoveNext method returns false to its caller, indicating that the iteration is complete.

•  When an exception is thrown and propagated out of the iterator block:

-  Appropriate finally blocks in the iterator body will have been executed by the exception propagation.

-  The state of the enumerator object is changed to after.

-  The exception propagation continues to the caller of the MoveNext method.

10.14.4.2 The Current Property

An enumerator object’s Current property is affected by yield return statements in the iterator block.

When an enumerator object is in the suspended state, the value of Current is the value set by the previous call to MoveNext. When an enumerator object is in the before, running, or after state, the result of accessing Current is unspecified.

For an iterator with a yield type other than object, the result of accessing Current through the enumerator object’s IEnumerable implementation corresponds to accessing Current through the enumerator object’s IEnumerator<T> implementation and casting the result to object.

10.14.4.3 The Dispose Method

The Dispose method is used to clean up the iteration by bringing the enumerator object to the after state.

•  If the state of the enumerator object is before, invoking Dispose changes the state to after.

•  If the state of the enumerator object is running, the result of invoking Dispose is unspecified.

•  If the state of the enumerator object is suspended, invoking Dispose:

-  Changes the state to running.

-  Executes any finally blocks as if the last executed yield return statement were a yield break statement. If this causes an exception to be thrown and propagated out of the iterator body, the state of the enumerator object is set to after and the exception is propagated to the caller of the Dispose method.

-  Changes the state to after.

•  If the state of the enumerator object is after, invoking Dispose has no effect.

10.14.5 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:

•  It implements IEnumerable and IEnumerable<T>, where T is the yield type of the iterator.

•  It is initialized with a copy of the argument values (if any) and instance value passed to the function member.

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 (§2.4.2).

An enumerable object may implement more interfaces than those specified above. In particular, an enumerable object may also implement IEnumerator and IEnumerator<T>, enabling it to serve as both an enumerable and an enumerator. In that type of implementation, the first time an enumerable object’s GetEnumerator method is invoked, the enumerable object itself is returned. Subsequent invocations of the enumerable object’s GetEnumerator, if any, return a copy of the enumerable object. Thus each returned enumerator has its own state and changes in one enumerator will not affect another.

10.14.5.1 The GetEnumerator Method

An enumerable object provides an implementation of the GetEnumerator methods of the IEnumerable and IEnumerable<T> 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 §10.14.4.

10.14.6 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<T> class implements its GetEnumerator method using an iterator. The iterator enumerates the elements of the stack in top-to-bottom order.

        using System;
        using System.Collections;
        using System.Collections.Generic;
        class Stack<T>: IEnumerable<T>
        {
                T[] items;
                int count;
                public void Push(T item) {
                if (items == null) {
                        items = new T[4];
                }
                else if (items.Length == count) {
                        T[] newItems = new T[count * 2];
                        Array.Copy(items, 0, newItems, 0, count);
                        items = newItems;
                 }
                items[count++] = item;
        }
        public T Pop() {
                T result = items[-count];
                items[count] = default(T);
                return result;
         }
        public IEnumerator<T> GetEnumerator() {
                for (int i = count - 1; i >= 0; -i) yield return items[i];
                }
        }

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

        class Stack<T>: IEnumerable<T>
        {
                …
                public IEnumerator<T> GetEnumerator() {
                        return new __Enumerator1(this);
                }
                class __Enumerator1: IEnumerator<T>, IEnumerator
                {
                         int __state;
                         T __current;
                         Stack<T> __this;
                         int i;
                         public __Enumerator1(Stack<T> __this) {
                                this.__this = __this;
                         }
                         public T Current {
                                get { return __current; }
                         }
                         object IEnumerator.Current {
                                get { return __current; }
                         }
                         public bool MoveNext() {
                                switch (__state) {
                                        case 1: goto __state1;
                                        case 2: goto __state2;
                                }
                                i = __this.count - 1;
                        __loop:
                                if (i < 0) goto __state2;
                                __current = __this.items[i];
                                __state = 1;
                                return true;
                        __state1:
                                -i;
                                goto __loop;
                        __state2:
                                __state = 2;
                                return false;
                        }
                        public void Dispose() {
                                __state = 2;
                        }
                        void IEnumerator.Reset() {
                                throw new NotSupportedException();
                        }
                }
        }

In the preceding translation, the code in the iterator block is turned into a state machine and placed in the MoveNext method of the enumerator class. Furthermore, the local variable i is turned into a field in the enumerator object so it can continue to exist across invocations of MoveNext.

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.

        using System;
        using System.Collections.Generic;
        class Test
        {
                static IEnumerable<int> FromTo(int from, int to) {
                        while (from <= to) yield return from++;
                }
                static void Main() {
                        IEnumerable<int> e = FromTo(1, 10);
                        foreach (int x in e) {
                                foreach (int y in e) {
                                        Console.Write("{0,3} ", x * y);
                                }
                                Console.WriteLine();
                        }
                }
        }

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

        using System;
        using System.Threading;
        using System.Collections;
        using System.Collections.Generic;
        class Test
        {
                …
                static IEnumerable<int> FromTo(int from, int to) {
                        return new __Enumerable1(from, to);
                }
                class __Enumerable1:
                        IEnumerable<int>, IEnumerable,
                        IEnumerator<int>, IEnumerator
                {
                        int __state;
                        int __current;
                        int __from;
                        int from;
                        int to;
                        int i;
                        public __Enumerable1(int __from, int to) {
                                this.__from = __from;
                                this.to = to;
                }
                public IEnumerator<int> GetEnumerator() {
                        __Enumerable1 result = this;
                        if (Interlocked.CompareExchange(ref __state, 1, 0) != 0) {
                                result = new __Enumerable1(__from, to);
                                result.__state = 1;
                        }
                        result.from = result.__from;
                        return result;
                }
                IEnumerator IEnumerable.GetEnumerator() {
                        return (IEnumerator)GetEnumerator();
                }
                public int Current {
                        get { return __current; }
                }
                object IEnumerator.Current {
                        get { return __current; }
                }
                public bool MoveNext() {
                        switch (__state) {
                        case 1:
                                if (from > to) goto case 2;
                                __current = from++;
                                __state = 1;
                                return true;
                                case 2:
                                        __state = 2;
                                        return false;
                                default:
                                        throw new InvalidOperationException();
                                }
                      }
                      public void Dispose() {
                                __state = 2;
                      }
                      void IEnumerator.Reset() {
                                throw new NotSupportedException();
                      }
                }
        }

The enumerable class implements both the enumerable interfaces and the enumerator interfaces, enabling it to serve as both an enumerable and an enumerator. The first time the GetEnumerator method is invoked, the enumerable object itself is returned. Subsequent invocations of the enumerable object’s GetEnumerator, if any, return a copy of the enumerable object. Thus each returned enumerator has its own state, and changes in one enumerator will not affect another. The Interlocked.CompareExchange method is used to ensure thread-safe operation.

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 MoveNext method throws an InvalidOperationException if it is called when __state is 0. This protects against use of the enumerable object as an enumerator object without first calling GetEnumerator.

The following example shows a simple tree class. The Tree<T> class implements its GetEnumerator method using an iterator. The iterator enumerates the elements of the tree in infix order.

        using System;
        using System.Collections.Generic;
        class Tree<T>: IEnumerable<T>
        {
                T value;
                Tree<T> left;
                Tree<T> right;
                public Tree(T value, Tree<T> left, Tree<T> right) {
                        this.value = value;
                        this.left = left;
                        this.right = right;
                }
                public IEnumerator<T> GetEnumerator() {
                        if (left != null) foreach (T x in left) yield x;
                        yield value;
                        if (right != null) foreach (T x in right) yield x;
                }
           }
                class program
               (
              static Tree<T> MakeTree<T>(T[] items, int left, int right) {
                   if (left > right) return null;
                   int i = (left + right) / 2;
                   return new Tree<T>(items[i],
                     MakeTree(items, left, i - 1),
                     MakeTree(items, i + 1, right));
              )
                static Tree<T> MakeTree<T>(params T[] items) {
                   return MakeTree(items, 0, items.Length - 1);
              )
              // The output of the program is:
             // 1 2 3 4 5 6 7 8 9
             // Mon Tue Wed Thu Fri Sat Sun
             static void Main() {
                  Tree<int> ints = MakeTree(1, 2, 3, 4, 5, 6, 7, 8, 9);
                   foreach (int i in ints) Console.Write("{0} ", i);
                  Console.WriteLine();
                Tree<string> strings = MakeTree(
                      "Mon", "Tue", "Wed", "Thu", "Fri", "Sat", "Sun");
                    foreach (string s in strings) Console.Write("{0} ", s);
                  Console.WriteLine();
               }
        }

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

                   class Tree<T>: IEnumerable<T>
               {
                   …
                    public IEnumerator<T> GetEnumerator() {
                       return new __Enumerator1(this);
                }
                class __Enumerator1 : IEnumerator<T>, IEnumerator
              {
                  Node<T> __this;
                  IEnumerator<T> __left, __right;
                  int __state;
                  T __current;
                  public __Enumerator1(Node<T> __this) {
                  this.__this = __this;
                 }
                   public T Current {
                       get { return __current; }
                 }
                   object IEnumerator.Current {
                       get { return __current; }
                 }
                      public bool MoveNext() {
                            try {
                                 switch (__state) {
                                 case 0:
                                                __state = -1;
                                                if (__this.left == null) goto __yield_value;
                                                __left = __this.left.GetEnumerator();
                                                goto case 1;
                                          case 1:
                                          __state = -2;
                                                if (!__left.MoveNext()) goto __left_dispose;
                                                __current = __left.Current;
                                               __state = 1;
                                                return true;
                                                __left_dispose:
                                                        __state = -1;
                                                        __left.Dispose();
                                                __yield_value:
                                                        __current = __this.value;
                                                        __state = 2;
                                                        return true;
                                                case 2:
                                                        __state = -1;
                                                        if (__this.right == null) goto __end;
                                                        __right = __this.right.GetEnumerator();
                                                        goto case 3;
                                                case 3:
                                                        __state = -3;
;                                                        if (!__right.MoveNext()) goto __right_dispose;
                                                        __current = __right.Current;
                                                        __state = 3;
                                                        return true;
                                                __right_dispose:
                                                        __state = -1;
                                                        __right.Dispose();
                                                __end:
                                                        __state = 4;
                                                        break;
                                                }
                                        }
                                        finally {
                                                if (__state < 0) Dispose();
                                }
                                return false;
                        }
                        public void Dispose() {
                                try {
                                        switch (__state) {
                                        case 1:
                                        case -2:
                                                __left.Dispose();
                                                break;
                                        case 3:
                                        case -3:
                                                __right.Dispose();
                                                break;
                                        }
                                }
                                finally {
                                        __state = 4;
                                }
                        }
                        void IEnumerator.Reset() {
                                throw new NotSupportedException();
                        }
                }
        }

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 if an exception is thrown. Note that it is not possible to write the translated code with simple foreach statements.

..................Content has been hidden....................

You can't read the all page of ebook, please click here login for view all page.
Reset