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



ЗНАЕТЕ ЛИ ВЫ?

Interface implementation inheritance

Поиск

A class inherits all interface implementations provided by its base classes.

Without explicitly re-implementing an interface, a derived class cannot in any way alter the interface mappings it inherits from its base classes. For example, in the declarations

interface IControl
{
void Paint();
}

class Control: IControl
{
public void Paint() {...}
}

class TextBox: Control
{
new public void Paint() {...}
}

the Paint method in TextBox hides the Paint method in Control, but it does not alter the mapping of Control.Paint onto IControl.Paint, and calls to Paint through class instances and interface instances will have the following effects

Control c = new Control();
TextBox t = new TextBox();
IControl ic = c;
IControl it = t;
c.Paint(); // invokes Control.Paint();
t.Paint(); // invokes TextBox.Paint();
ic.Paint(); // invokes Control.Paint();
it.Paint(); // invokes Control.Paint();

However, when an interface method is mapped onto a virtual method in a class, it is possible for derived classes to override the virtual method and alter the implementation of the interface. For example, rewriting the declarations above to

interface IControl
{
void Paint();
}

class Control: IControl
{
public virtual void Paint() {...}
}

class TextBox: Control
{
public override void Paint() {...}
}

the following effects will now be observed

Control c = new Control();
TextBox t = new TextBox();
IControl ic = c;
IControl it = t;
c.Paint(); // invokes Control.Paint();
t.Paint(); // invokes TextBox.Paint();
ic.Paint(); // invokes Control.Paint();
it.Paint(); // invokes TextBox.Paint();

Since explicit interface member implementations cannot be declared virtual, it is not possible to override an explicit interface member implementation. However, it is perfectly valid for an explicit interface member implementation to call another method, and that other method can be declared virtual to allow derived classes to override it. For example

interface IControl
{
void Paint();
}

class Control: IControl
{
void IControl.Paint() { PaintControl(); }

protected virtual void PaintControl() {...}
}

class TextBox: Control
{
protected override void PaintControl() {...}
}

Here, classes derived from Control can specialize the implementation of IControl.Paint by overriding the PaintControl method.

Interface re-implementation

A class that inherits an interface implementation is permitted to re-implement the interface by including it in the base class list.

A re-implementation of an interface follows exactly the same interface mapping rules as an initial implementation of an interface. Thus, the inherited interface mapping has no effect whatsoever on the interface mapping established for the re-implementation of the interface. For example, in the declarations

interface IControl
{
void Paint();
}

class Control: IControl
{
void IControl.Paint() {...}
}

class MyControl: Control, IControl
{
public void Paint() {}
}

the fact that Control maps IControl.Paint onto Control.IControl.Paint doesn’t affect the re-implementation in MyControl, which maps IControl.Paint onto MyControl.Paint.

Inherited public member declarations and inherited explicit interface member declarations participate in the interface mapping process for re-implemented interfaces. For example

interface IMethods
{
void F();
void G();
void H();
void I();
}

class Base: IMethods
{
void IMethods.F() {}
void IMethods.G() {}
public void H() {}
public void I() {}
}

class Derived: Base, IMethods
{
public void F() {}
void IMethods.H() {}
}

Here, the implementation of IMethods in Derived maps the interface methods onto Derived.F, Base.IMethods.G, Derived.IMethods.H, and Base.I.

When a class implements an interface, it implicitly also implements all of that interface’s base interfaces. Likewise, a re-implementation of an interface is also implicitly a re-implementation of all of the interface’s base interfaces. For example

interface IBase
{
void F();
}

interface IDerived: IBase
{
void G();
}

class C: IDerived
{
void IBase.F() {...}

void IDerived.G() {...}
}

class D: C, IDerived
{
public void F() {...}

public void G() {...}
}

Here, the re-implementation of IDerived also re-implements IBase, mapping IBase.F onto D.F.

Abstract classes and interfaces

Like a non-abstract class, an abstract class must provide implementations of all members of the interfaces that are listed in the base class list of the class. However, an abstract class is permitted to map interface methods onto abstract methods. For example

interface IMethods
{
void F();
void G();
}

abstract class C: IMethods
{
public abstract void F();
public abstract void G();
}

Here, the implementation of IMethods maps F and G onto abstract methods, which must be overridden in non-abstract classes that derive from C.

Note that explicit interface member implementations cannot be abstract, but explicit interface member implementations are of course permitted to call abstract methods. For example

interface IMethods
{
void F();
void G();
}

abstract class C: IMethods
{
void IMethods.F() { FF(); }

void IMethods.G() { GG(); }

protected abstract void FF();

protected abstract void GG();
}

Here, non-abstract classes that derive from C would be required to override FF and GG, thus providing the actual implementation of IMethods.


Enums

An enum type is a distinct value type (§4.1) that declares a set of named constants.

The example

enum Color
{
Red,
Green,
Blue
}

declares an enum type named Color with members Red, Green, and Blue.

Enum declarations

An enum declaration declares a new enum type. An enum declaration begins with the keyword enum, and defines the name, accessibility, underlying type, and members of the enum.

enum-declaration:
attributesopt enum-modifiersopt enum identifier enum-baseopt enum-body;opt

enum-base:
: integral-type

enum-body:
{ enum-member-declarationsopt }
{ enum-member-declarations, }

Each enum type has a corresponding integral type called the underlying type of the enum type. This underlying type must be able to represent all the enumerator values defined in the enumeration. An enum declaration may explicitly declare an underlying type of byte, sbyte, short, ushort, int, uint, long or ulong. Note that char cannot be used as an underlying type. An enum declaration that does not explicitly declare an underlying type has an underlying type of int.

The example

enum Color: long
{
Red,
Green,
Blue
}

declares an enum with an underlying type of long. A developer might choose to use an underlying type of long, as in the example, to enable the use of values that are in the range of long but not in the range of int, or to preserve this option for the future.

Enum modifiers

An enum-declaration may optionally include a sequence of enum modifiers:

enum-modifiers:
enum-modifier
enum-modifiers enum-modifier

enum-modifier:
new
public
protected
internal
private

It is a compile-time error for the same modifier to appear multiple times in an enum declaration.

The modifiers of an enum declaration have the same meaning as those of a class declaration (§10.1.1). Note, however, that the abstract and sealed modifiers are not permitted in an enum declaration. Enums cannot be abstract and do not permit derivation.

Enum members

The body of an enum type declaration defines zero or more enum members, which are the named constants of the enum type. No two enum members can have the same name.

enum-member-declarations:
enum-member-declaration
enum-member-declarations, enum-member-declaration

enum-member-declaration:
attributesopt identifier
attributesopt identifier = constant-expression

Each enum member has an associated constant value. The type of this value is the underlying type for the containing enum. The constant value for each enum member must be in the range of the underlying type for the enum. The example

enum Color: uint
{
Red = -1,
Green = -2,
Blue = -3
}

results in a compile-time error because the constant values -1, -2, and –3 are not in the range of the underlying integral type uint.

Multiple enum members may share the same associated value. The example

enum Color
{
Red,
Green,
Blue,

Max = Blue
}

shows an enum in which two enum members—Blue and Max—have the same associated value.

The associated value of an enum member is assigned either implicitly or explicitly. If the declaration of the enum member has a constant-expression initializer, the value of that constant expression, implicitly converted to the underlying type of the enum, is the associated value of the enum member. If the declaration of the enum member has no initializer, its associated value is set implicitly, as follows:

· If the enum member is the first enum member declared in the enum type, its associated value is zero.

· Otherwise, the associated value of the enum member is obtained by increasing the associated value of the textually preceding enum member by one. This increased value must be within the range of values that can be represented by the underlying type, otherwise a compile-time error occurs.

The example

using System;

enum Color
{
Red,
Green = 10,
Blue
}

class Test
{
static void Main() {
Console.WriteLine(StringFromColor(Color.Red));
Console.WriteLine(StringFromColor(Color.Green));
Console.WriteLine(StringFromColor(Color.Blue));
}

static string StringFromColor(Color c) {
switch (c) {
case Color.Red:
return String.Format("Red = {0}", (int) c);

case Color.Green:
return String.Format("Green = {0}", (int) c);

case Color.Blue:
return String.Format("Blue = {0}", (int) c);

default:
return "Invalid color";
}
}
}

prints out the enum member names and their associated values. The output is:

Red = 0
Green = 10
Blue = 11

for the following reasons:

· the enum member Red is automatically assigned the value zero (since it has no initializer and is the first enum member);

· the enum member Green is explicitly given the value 10;

· and the enum member Blue is automatically assigned the value one greater than the member that textually precedes it.

The associated value of an enum member may not, directly or indirectly, use the value of its own associated enum member. Other than this circularity restriction, enum member initializers may freely refer to other enum member initializers, regardless of their textual position. Within an enum member initializer, values of other enum members are always treated as having the type of their underlying type, so that casts are not necessary when referring to other enum members.

The example

enum Circular
{
A = B,
B
}

results in a compile-time error because the declarations of A and B are circular. A depends on B explicitly, and B depends on A implicitly.

Enum members are named and scoped in a manner exactly analogous to fields within classes. The scope of an enum member is the body of its containing enum type. Within that scope, enum members can be referred to by their simple name. From all other code, the name of an enum member must be qualified with the name of its enum type. Enum members do not have any declared accessibility—an enum member is accessible if its containing enum type is accessible.

The System.Enum type

The type System.Enum is the abstract base class of all enum types (this is distinct and different from the underlying type of the enum type), and the members inherited from System.Enum are available in any enum type. A boxing conversion (§4.3.1) exists from any enum type to System.Enum, and an unboxing conversion (§4.3.2) exists from System.Enum to any enum type.

Note that System.Enum is not itself an enum-type. Rather, it is a class-type from which all enum-types are derived. The type System.Enum inherits from the type System.ValueType (§4.1.1), which, in turn, inherits from type object. At run-time, a value of type System.Enum can be null or a reference to a boxed value of any enum type.

Enum values and operations

Each enum type defines a distinct type; an explicit enumeration conversion (§6.2.2) is required to convert between an enum type and an integral type, or between two enum types. The set of values that an enum type can take on is not limited by its enum members. In particular, any value of the underlying type of an enum can be cast to the enum type, and is a distinct valid value of that enum type.

Enum members have the type of their containing enum type (except within other enum member initializers: see §14.3). The value of an enum member declared in enum type E with associated value v is (E)v.

The following operators can be used on values of enum types: ==,!=, <, >, <=, >= (§7.10.5), binary + (§7.8.4), binary ‑ (§7.8.5), ^, &, | (§7.11.2), ~ (§7.7.4), ++ and -- (§7.6.9 and §7.7.5).

Every enumtype automatically derives from the class System.Enum (which, in turn, derives from System.ValueType and object). Thus, inherited methods and properties of this class can be used on values of an enum type.


Delegates

Delegates enable scenarios that other languages—such as C++, Pascal, and Modula—have addressed with function pointers. Unlike C++ function pointers, however, delegates are fully object oriented, and unlike C++ pointers to member functions, delegates encapsulate both an object instance and a method.

A delegate declaration defines a class that is derived from the class System.Delegate. A delegate instance encapsulates an invocation list, which is a list of one or more methods, each of which is referred to as a callable entity. For instance methods, a callable entity consists of an instance and a method on that instance. For static methods, a callable entity consists of just a method. Invoking a delegate instance with an appropriate set of arguments causes each of the delegate’s callable entities to be invoked with the given set of arguments.

An interesting and useful property of a delegate instance is that it does not know or care about the classes of the methods it encapsulates; all that matters is that those methods be compatible (§15.1) with the delegate’s type. This makes delegates perfectly suited for “anonymous” invocation.

Delegate declarations

A delegate-declaration is a type-declaration (§9.6) that declares a new delegate type.

delegate-declaration:
attributesopt delegate-modifiersopt delegate return-type
identifier variant-type-parameter-listopt
(formal-parameter-listopt) type-parameter-constraints-clausesopt;

delegate-modifiers:
delegate-modifier
delegate-modifiers delegate-modifier

delegate-modifier:
new
public
protected
internal
private

It is a compile-time error for the same modifier to appear multiple times in a delegate declaration.

The new modifier is only permitted on delegates declared within another type, in which case it specifies that such a delegate hides an inherited member by the same name, as described in §10.3.4.

The public, protected, internal, and private modifiers control the accessibility of the delegate type. Depending on the context in which the delegate declaration occurs, some of these modifiers may not be permitted (§3.5.1).

The delegate’s type name is identifier.

The optional formal-parameter-list specifies the parameters of the delegate, and return-type indicates the return type of the delegate.

The optional variant-type-parameter-list (§13.1.3) specifies the type parameters to the delegate itself.

The return type of a delegate type must be either void, or output-safe (§13.1.3.1).

All the formal parameter types of a delegate type must be input-safe. Additionally, any out or ref parameter types must also be output-safe. Note that even out parameters are required to be input-safe, due to a limitiation of the underlying execution platform.

Delegate types in C# are name equivalent, not structurally equivalent. Specifically, two different delegate types that have the same parameter lists and return type are considered different delegate types. However, instances of two distinct but structurally equivalent delegate types may compare as equal (§7.9.8).

For example:

delegate int D1(int i, double d);

class A
{
public static int M1(int a, double b) {...}
}

class B
{
delegate int D2(int c, double d);

public static int M1(int f, double g) {...}

public static void M2(int k, double l) {...}

public static int M3(int g) {...}

public static void M4(int g) {...}
}

The methods A.M1 and B.M1 are compatible with both the delegate types D1 and D2, since they have the same return type and parameter list; however, these delegate types are two different types, so they are not interchangeable. The methods B.M2, B.M3, and B.M4 are incompatible with the delegate types D1 and D2, since they have different return types or parameter lists.

Like other generic type declarations, type arguments must be given to create a constructed delegate type. The parameter types and return type of a constructed delegate type are created by substituting, for each type parameter in the delegate declaration, the corresponding type argument of the constructed delegate type. The resulting return type and parameter types are used in determining what methods are compatible with a constructed delegate type. For example:

delegate bool Predicate<T>(T value);

class X
{
static bool F(int i) {...}

static bool G(string s) {...}
}

The method X.F is compatible with the delegate type Predicate<int> and the method X.G is compatible with the delegate type Predicate<string>.

The only way to declare a delegate type is via a delegate-declaration. A delegate type is a class type that is derived from System.Delegate. Delegate types are implicitly sealed, so it is not permissible to derive any type from a delegate type. It is also not permissible to derive a non-delegate class type from System.Delegate. Note that System.Delegate is not itself a delegate type; it is a class type from which all delegate types are derived.

C# provides special syntax for delegate instantiation and invocation. Except for instantiation, any operation that can be applied to a class or class instance can also be applied to a delegate class or instance, respectively. In particular, it is possible to access members of the System.Delegate type via the usual member access syntax.

The set of methods encapsulated by a delegate instance is called an invocation list. When a delegate instance is created (§15.2) from a single method, it encapsulates that method, and its invocation list contains only one entry. However, when two non-null delegate instances are combined, their invocation lists are concatenated—in the order left operand then right operand—to form a new invocation list, which contains two or more entries.

Delegates are combined using the binary + (§7.8.4) and += operators (§7.17.2). A delegate can be removed from a combination of delegates, using the binary - (§7.8.5) and -= operators (§7.17.2). Delegates can be compared for equality (§7.10.8).

The following example shows the instantiation of a number of delegates, and their corresponding invocation lists:

delegate void D(int x);

class C
{
public static void M1(int i) {...}

public static void M2(int i) {...}

}

class Test
{
static void Main() {
D cd1 = new D(C.M1); // M1
D cd2 = new D(C.M2); // M2
D cd3 = cd1 + cd2; // M1 + M2
D cd4 = cd3 + cd1; // M1 + M2 + M1
D cd5 = cd4 + cd3; // M1 + M2 + M1 + M1 + M2
}

}

When cd1 and cd2 are instantiated, they each encapsulate one method. When cd3 is instantiated, it has an invocation list of two methods, M1 and M2, in that order. cd4’s invocation list contains M1, M2, and M1, in that order. Finally, cd5’s invocation list contains M1, M2, M1, M1, and M2, in that order. For more examples of combining (as well as removing) delegates, see §15.4.

Delegate compatibility

A method or delegate M is compatible with a delegate type D if all of the following are true:

· D and M have the same number of parameters, and each parameter in D has the same ref or out modifiers as the corresponding parameter in M.

· For each value parameter (a parameter with no ref or out modifier), an identity conversion (§6.1.1) or implicit reference conversion (§6.1.6) exists from the parameter type in D to the corresponding parameter type in M.

· For each ref or out parameter, the parameter type in D is the same as the parameter type in M.

· An identity or implicit reference conversion exists from the return type of M to the return type of D.

Delegate instantiation

An instance of a delegate is created by a delegate-creation-expression (§7.6.10.5) or a conversion to a delegate type. The newly created delegate instance then refers to either:

· The static method referenced in the delegate-creation-expression, or

· The target object (which cannot be null) and instance method referenced in the delegate-creation-expression, or

· Another delegate.

For example:

delegate void D(int x);

class C
{
public static void M1(int i) {...}
public void M2(int i) {...}
}

class Test
{
static void Main() {
D cd1 = new D(C.M1); // static method
C t = new C();
D cd2 = new D(t.M2); // instance method
D cd3 = new D(cd2); // another delegate
}
}

Once instantiated, delegate instances always refer to the same target object and method. Remember, when two delegates are combined, or one is removed from another, a new delegate results with its own invocation list; the invocation lists of the delegates combined or removed remain unchanged.

Delegate invocation

C# provides special syntax for invoking a delegate. When a non-null delegate instance whose invocation list contains one entry is invoked, it invokes the one method with the same arguments it was given, and returns the same value as the referred to method. (See §7.6.5.3 for detailed information on delegate invocation.) If an exception occurs during the invocation of such a delegate, and that exception is not caught within the method that was invoked, the search for an exception catch clause continues in the method that called the delegate, as if that method had directly called the method to which that delegate referred.

Invocation of a delegate instance whose invocation list contains multiple entries proceeds by invoking each of the methods in the invocation list, synchronously, in order. Each method so called is passed the same set of arguments as was given to the delegate instance. If such a delegate invocation includes reference parameters (§10.6.1.2), each method invocation will occur with a reference to the same variable; changes to that variable by one method in the invocation list will be visible to methods further down the invocation list. If the delegate invocation includes output parameters or a return value, their final value will come from the invocation of the last delegate in the list.

If an exception occurs during processing of the invocation of such a delegate, and that exception is not caught within the method that was invoked, the search for an exception catch clause continues in the method that called the delegate, and any methods further down the invocation list are not invoked.

Attempting to invoke a delegate instance whose value is null results in an exception of type System.NullReferenceException.

The following example shows how to instantiate, combine, remove, and invoke delegates:

using System;

delegate void D(int x);

class C
{
public static void M1(int i) {
Console.WriteLine("C.M1: " + i);
}

public static void M2(int i) {
Console.WriteLine("C.M2: " + i);
}

public void M3(int i) {
Console.WriteLine("C.M3: " + i);
}
}

class Test
{
static void Main() {
D cd1 = new D(C.M1);
cd1(-1); // call M1

D cd2 = new D(C.M2);
cd2(-2); // call M2

D cd3 = cd1 + cd2;
cd3(10); // call M1 then M2

cd3 += cd1;
cd3(20); // call M1, M2, then M1

C c = new C();
D cd4 = new D(c.M3);
cd3 += cd4;
cd3(30); // call M1, M2, M1, then M3

cd3 -= cd1; // remove last M1
cd3(40); // call M1, M2, then M3

cd3 -= cd4;
cd3(50); // call M1 then M2

cd3 -= cd2;
cd3(60); // call M1

cd3 -= cd2; // impossible removal is benign
cd3(60); // call M1

cd3 -= cd1; // invocation list is empty so cd3 is null

// cd3(70); // System.NullReferenceException thrown

cd3 -= cd1; // impossible removal is benign
}
}

As shown in the statement cd3 += cd1;, a delegate can be present in an invocation list multiple times. In this case, it is simply invoked once per occurrence. In an invocation list such as this, when that delegate is removed, the last occurrence in the invocation list is the one actually removed.

Immediately prior to the execution of the final statement, cd3 -= cd1;, the delegate cd3 refers to an empty invocation list. Attempting to remove a delegate from an empty list (or to remove a non-existent delegate from a non-empty list) is not an error.

The output produced is:

C.M1: -1
C.M2: -2
C.M1: 10
C.M2: 10
C.M1: 20
C.M2: 20
C.M1: 20
C.M1: 30
C.M2: 30
C.M1: 30
C.M3: 30
C.M1: 40
C.M2: 40
C.M3: 40
C.M1: 50
C.M2: 50
C.M1: 60
C.M1: 60


Exceptions

Exceptions in C# provide a structured, uniform, and type-safe way of handling both system level and application level error conditions. The exception mechanism in C# is quite similar to that of C++, with a few important differences:

· In C#, all exceptions must be represented by an instance of a class type derived from System.Exception. In C++, any value of any type can be used to represent an exception.

· In C#, a finally block (§8.10) can be used to write termination code that executes in both normal execution and exceptional conditions. Such code is difficult to write in C++ without duplicating code.

· In C#, system-level exceptions such as overflow, divide-by-zero, and null dereferences have well defined exception classes and are on a par with application-level error conditions.

Causes of exceptions

Exception can be thrown in two different ways.

· A throw statement (§8.9.5) throws an exception immediately and unconditionally. Control never reaches the statement immediately following the throw.

· Certain exceptional conditions that arise during the processing of C# statements and expression cause an exception in certain circumstances when the operation cannot be completed normally. For example, an integer division operation (§7.8.2) throws a System.DivideByZeroException if the denominator is zero. See §16.4 for a list of the various exceptions that can occur in this way.

The System.Exception class

The System.Exception class is the base type of all exceptions. This class has a few notable properties that all exceptions share:

· Message is a read-only property of type string that contains a human-readable description of the reason for the exception.

· InnerException is a read-only property of type Exception. If its value is non-null, it refers to the exception that caused the current exception—that is, the current exception was raised in a catch block handling the InnerException. Otherwise, its value is null, indicating that this exception was not caused by another exception. The number of exception objects chained together in this manner can be arbitrary.

The value of these properties can be specified in calls to the instance constructor for System.Exception.

How exceptions are handled

Exceptions are handled by a try statement (§8.10).

When an exception occurs, the system searches for the nearest catch clause that can handle the exception, as determined by the run-time type of the exception. First, the current method is searched for a lexically enclosing try statement, and the associated catch clauses of the try statement are considered in order. If that fails, the method that called the current method is searched for a lexically enclosing try statement that encloses the point of the call to the current method. This search continues until a catch clause is found that can handle the current exception, by naming an exception class that is of the same class, or a base class, of the run-time type of the exception being thrown. A catch clause that doesn’t name an exception class can handle any exception.

Once a matching catch clause is found, the system prepares to transfer control to the first statement of the catch clause. Before execution of the catch clause begins, the system first executes, in order, any finally clauses that were associated with try statements more nested that than the one that caught the exception.

If no matching catch clause is found, one of two things occurs:

· If the search for a matching catch clause reaches a static constructor (§10.12) or static field initializer, then a System.TypeInitializationException is thrown at the point that triggered the invocation of the static constructor. The inner exception of the System.TypeInitializationException contains the exception that was originally thrown.

· If the search for matching catch clauses reaches the code that initially started the thread, then execution of the thread is terminated. The impact of such termination is implementation-defined.

Exceptions that occur during destructor execution are worth special mention. If an exception occurs during destructor execution, and that exception is not caught, then the execution of that destructor is terminated and the destructor of the base class (if any) is called. If there is no base class (as in the case of the object type) or if there is no base class destructor, then the exception is discarded.

Common Exception Classes

The following exceptions are thrown by certain C# operations.

 

System.ArithmeticException A base class for exceptions that occur during arithmetic operations, such as System.DivideByZeroException and System.OverflowException.
System.ArrayTypeMismatchException Thrown when a store into an array fails because the actual type of the stored element is incompatible with the actual type of the array.
System.DivideByZeroException Thrown when an attempt to divide an integral value by zero occurs.
System.IndexOutOfRangeException Thrown when an attempt to index an array via an index that is less than zero or outside the bounds of the array.
System.InvalidCastException Thrown when an explicit conversion from a base type or interface to a derived type fails at run time.
System.NullReferenceException Thrown when a null reference is used in a way that causes the referenced object to be required.
System.OutOfMemoryException Thrown when an attempt to allocate memory (via new) fails.
System.OverflowException Thrown when an arithmetic operation in a checked context overflows.
System.StackOverflowException Thrown when the execution stack is exhausted by having too many pending method calls; typically indicative of very deep or unbounded recursion.
System.TypeInitializationException Thrown when a static constructor throws an exception, and no catch clauses exists to catch it.

 


Attributes

Much of the C# language enables the programmer to specify declarative information about the entities defined in the program. For example, the accessibility of a method in a class is specified by decorating it with the method-modifiers public, protected, internal, and private.

C# enables programmers to invent new kinds of declarative information, called attributes. Programmers can then attach attributes to various program entities, and retrieve attribute information in a run-time environment. For instance, a framework might define a HelpAttribute attribute that can be placed on certain program elements (such as classes and methods) to provide a mapping from those program elements to their documentation.

Attributes are defined through the declaration of attribute classes (§17.1), which may have positional and named parameters (§17.1.2). Attributes are attached to entities in a C# program using attribute specifications (§17.2), and can be retrieved at run-time as attribute instances (§17.3).

Attribute classes

A class that derives from the abstract class System.Attribute, whether directly or indirectly, is an attribute class. The declaration of an attribute class defines a new kind of attribute that can be placed on a declaration. By convention, attribute classes are named with a suffix of Attribute. Uses of an attribute may either include or omit this suffix.

Attribute usage

The attribute AttributeUsage (§17.4.1) is used to describe how an attribute class can be used.

AttributeUsage has a positional parameter (§17.1.2) that enables an attribute class to specify the kinds of declarations on which it can be used. The example

using System;

[AttributeUsage(AttributeTargets.Class | AttributeTargets.Interface)]
public class SimpleAttribute: Attribute
{
...
}

defines an attribute class named SimpleAttribute that can be placed on class-declarations and interface-declarations only. The example

[Simple] class Class1 {...}

[Simple] interface Interface1 {...}

shows several uses of the Simple attribute. Although this attribute is defined with the name SimpleAttribute, when this attribute is used, the Attribute suffix may be omitted, resulting in the short name Simple. Thus, the example above is semantically equivalent to the following:

[SimpleAttribute] class Class1 {...}

[SimpleAttribute] interface Interface1 {...}

AttributeUsage has a named parameter (§17.1.2) called AllowMultiple, which indicates whether the attribute can be specified more than once for a given entity. If AllowMultiple for an attribute class is true, then that attribute class is a multi-use attribute class, and can be specified more than once on an entity. If AllowMultiple for an attribute class is false or it is unspecified, then that attribute class is a single-use attribute class, and can be specified at most once on an entity.

The example

using System;

[AttributeUsage(AttributeTargets.Class, AllowMultiple = true)]
public class AuthorAttribute: Attribute
{
private string name;

public AuthorAttribute(string name) {
this.name = name;
}

public string Name {
get { return name; }
}
}

defines a multi-use attribute class named AuthorAttribute. The example

[Author("Brian Kernighan"), Author("Dennis Ritchie")]
class Class1
{
...
}

shows a class declaration with two uses of the Author attribute.

AttributeUsage has another named parameter called Inherited, which indicates whether the attribute, when specified on a base class, is also inherited by classes that derive from that base class. If Inherited for an attribute class is true, then that attribute is inherited. If Inherited for an attribute class is false then that attribute is not inherited. If it is unspecified, its default value is true.

An attribute class X not having an AttributeUsage attribute attached to it, as in

using System;

class X: Attribute {...}

is equivalent to the following:

using System;

[AttributeUsage(
AttributeTargets.All,
AllowMultiple = false,
Inherited = true)
]
class X: Attribute {...}



Поделиться:


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

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