Virtual, sealed, override, and abstract accessors 


Мы поможем в написании ваших работ!



ЗНАЕТЕ ЛИ ВЫ?

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 only permitted 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. This is known as an overriding event declaration. An overriding event declaration does not declare a new event. Instead, it simply specializes the implementations of the accessors of an existing virtual event.

An overriding event declaration must specify the exact same accessibility modifiers, type, and name as the overridden event.

An overriding event declaration may include the sealed modifier. Use of this modifier prevents a derived class from further overriding the event. The accessors of a sealed event are also sealed.

It is a compile-time error for an overriding event declaration to include a new modifier.

Except for differences in declaration and invocation syntax, virtual, sealed, override, and abstract accessors behave exactly like virtual, sealed, override and abstract methods. Specifically, the rules described in §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.

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) and a valid combination of the four access modifiers (§10.3.5), the new (§10.3.4), virtual (§10.6.3), override (§10.6.4), sealed (§10.6.5), abstract (§10.6.6), and extern (§10.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 abstractare mutually exclusive except in one case. 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-declarations (§10.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-name (§7.6.2) or a member-access (§7.6.4), whereas an indexer element is accessed through an element-access (§7.6.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 example below 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[] (since each value of the former 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 upper- 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;
}
}
}

Indexer overloading

The indexer overload resolution rules are described in §7.5.2.

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
;

There are three categories of overloadable operators: 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 semi-colon. 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 and a static modifier.

· The parameter(s) of an operator must be value parameters (§5.1.4). 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.3.

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

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 pair-wise declaration. A compile-time error occurs if a class declares one of these operators without also declaring the other. The true and false operators are described further in §7.12.2 and §7.20.

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
}
}

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

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 non-shift 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 <=) and 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 pair-wise declaration. For every declaration of either operator of a pair, there must be a matching declaration of the other operator of the pair. Two operator declarations match when they have the same return type and the same type for each parameter. The following operators require pair-wise declaration:

· operator == and operator!=

· operator > and operator <

· operator >= and operator <=

Conversion operators

A conversion operator declaration introduces a user-defined conversion (§6.4) which augments the pre-defined implicit and explicit conversions.

A conversion operator declaration that includes the implicit keyword introduces a user-defined implicit conversion. Implicit conversions can occur in a variety of situations, including function member invocations, cast expressions, and assignments. This is described further in §6.1.

A conversion operator declaration that includes the explicit keyword introduces a user-defined explicit conversion. Explicit conversions can occur in cast expressions, and are described further in §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 are nullable types, 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 are true:

· 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) {...} // Ok

public static implicit operator C<string>(D<T> value) {...} // Ok

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 pre-defined conversion. Thus, conversion operators are not allowed to convert from or to object because implicit and explicit conversions already exist between object and all other types. Likewise, neither the source nor the target types of a conversion can be a base type of the other, since a conversion would then already exist.

However, it is possible to declare operators on generic types that, for particular type arguments, specify conversions that already exist as pre-defined conversions. In the example

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, and therefore also an explicit, conversion exists from any type to type object).

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

· If a pre-defined 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 pre-defined explicit conversion (§6.2) exists from type S to type T, any user-defined explicit conversions from S to T are ignored. Furthermore:

o If T is an interface type, user-defined implicit conversions from S to T are ignored.

o Otherwise, user-defined implicit conversions from S to T are still considered.

For all types but object, the operators declared by the Convertible<T> type above do not conflict with pre-defined 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, pre-defined conversions hide the user-defined conversions in all cases but one:

void F(object o, Convertible<object> n) {
o = n; // Pre-defined boxing conversion
o = (object)n; // Pre-defined boxing conversion
n = o; // User-defined implicit conversion
n = (Convertible<object>)o; // Pre-defined 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. (Note that this is the only form of member for which the return type participates in the signature.) The implicit or explicit classification of a conversion operator is not part of the operator’s signature. Thus, a class or struct cannot declare both an implicit and an explicit conversion operator with the same source and target types.

In general, user-defined implicit conversions should be designed to never throw exceptions and never lose information. If a user-defined conversion can give rise to exceptions (for example, because the source argument is out of range) or loss of information (such as discarding high-order bits), then that conversion should be defined as an explicit conversion.

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, but the conversion from byte to Digit is explicit since Digit can only represent a subset of the possible values of a byte.

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 extern (§10.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.5.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 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 which specifies the statements to initialize a new instance of the class. This corresponds exactly to the block of an instance method with a void return type (§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.6.10.1) and through constructor-initializers.

Constructor initializers

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

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



Поделиться:


Последнее изменение этой страницы: 2016-08-10; просмотров: 250; Нарушение авторского права страницы; Мы поможем в написании вашей работы!

infopedia.su Все материалы представленные на сайте исключительно с целью ознакомления читателями и не преследуют коммерческих целей или нарушение авторских прав. Обратная связь - 18.116.239.195 (0.112 с.)