Добавил:
Опубликованный материал нарушает ваши авторские права? Сообщите нам.
Вуз: Предмет: Файл:
C-sharp language specification.2004.pdf
Скачиваний:
12
Добавлен:
23.08.2013
Размер:
2.55 Mб
Скачать

C# LANGUAGE SPECIFICATION

1 defines an attribute class named HelpAttribute, or Help for short, that has one positional parameter 2 (string url) and one named parameter (string Topic). Positional parameters are defined by the

3formal parameters for public instance constructors of the attribute class, and named parameters are defined

4by public non-static read-write fields and properties of the attribute class.

5The example

6[Help("http://www.mycompany.com/…/Class1.htm")]

7public class Class1

8{

9

[Help("http://www.mycompany.com/…/Class1.htm", Topic = "F")]

10public void F() {}

11}

12shows several uses of the attribute Help.

13Attribute information for a given program element can be retrieved at run-time by using reflection support.

14The example

15using System;

16class Test

17{

18

static void Main() {

19

Type type = typeof(Class1);

20

object[] arr = type.GetCustomAttributes(

21

typeof(HelpAttribute), true);

22

if (arr.Length == 0)

23

Console.WriteLine("Class1 has no Help attribute.");

24

else {

25

HelpAttribute ha = (HelpAttribute) arr[0];

26

Console.WriteLine("Url = {0}, Topic = {1}", ha.Url, ha.Topic);

27

}

28}

29}

30checks to see if Class1 has a Help attribute, and writes out the associated Url and Topic values if the

31attribute is present.

328.16 Generics

33C# allows classes, structs, interfaces, and methods to be parameterized by the types of data they store and

34manipulate. This feature is really a set of features known collectively as generics. C# generics will

35immediately be familiar to users of generics in Eiffel or Ada, or to users of templates in C++.

36Many common classes and structs can be parameterized by the types of data being stored and manipulated—

37these are called generic class declarations and generic struct declarations, respectively. Similarly, many

38interfaces define contracts that can be parameterized by the types of data they handle—these are called

39generic interface declarations. In order to implement “generic algorithms,” methods can also be

40parameterized by type; such methods are known as generic methods.

418.16.1 Why generics?

42Without generics, programmers can store data of any type in variables of the base type object. To

43illustrate, let’s create a simple Stack type with two actions, “Push” and “Pop”. The Stack class stores its

44data in an array of object, and the Push and Pop methods use the object type to accept and return data,

45respectively:

46public class Stack

47{

48

private object[] items = new object[100];

49

public void Push(object data) {…}

50public object Pop() {…}

51}

52We can then push a value of any type, such as a Customer type, for example, onto the stack. However,

53when we wanted to retrieve that value, we would need to explicitly cast the result of the Pop method, an

52

Chapter 8 Language overview

1object, into a Customer type, which is tedious to write, and carries a performance penalty for run-time

2type checking:

3Stack s = new Stack();

4s.Push(new Customer());

5Customer c = (Customer)s.Pop();

6If we pass to the Push method a value type, such as an int, it will automatically be boxed. Similarly, if we

7want to retrieve an int from the stack, we would need to explicitly unbox the object type we obtain from

8the Pop method:

9Stack s = new Stack();

10s.Push(3);

11int i = (int)s.Pop();

12Such boxing and unboxing operations can impact performance.

13Furthermore, in the implementation shown, it is not possible to enforce the kind of data placed in the stack.

14Indeed, we could create a stack and push a Customer type onto it. However, later, we could use the same

15stack and try to pop data off of it and cast it into an incompatible type:

16Stack s = new Stack();

17s.Push(new Customer());

18Employee e = (Employee)s.Pop(); // runtime error

19While the code above is an improper use of the Stack class we presumably intended to implement, and

20should be a compile-time error, it is actually valid code. However, at run-time, the application will fail

21because we have performed an invalid cast operation.

228.16.2 Creating and consuming generics

23Generics provide a facility for creating high-performance data structures that are specialized by the compiler

24and/or execution engine based on the types that they use. These so-called generic type declarations are

25created so that their internal algorithms remain the same, yet the types of their external interface and internal

26data can vary based on user preference.

27In order to minimize the learning curve for developers, generics are used in much the same way as C++

28templates. Programmers can create classes and structures just as they normally have, and by using the angle

29bracket notation (< and >) they can specify type parameters. When the generic class declaration is used, each

30type parameter shall be replaced by a type argument that the user of the class supplies.

31In the example below, we create a Stack generic class declaration where we specify a type parameter,

32called ItemType, declared in angle brackets after the declaration. Rather than forcing conversions to and

33from object, instances of the generic Stack class will accept the type for which they are created and store

34data of that type without conversion. The type parameter ItemType acts as a placeholder until an actual

35type is specified at use. Note that ItemType is used as the element type for the internal items array, the type

36for the parameter to the Push method, and the return type for the Pop method:

37public class Stack<ItemType>

38{

39

private ItemType[] items = new ItemType[100];

40

public void Push(ItemType data) {…}

41public ItemType Pop() {…}

42}

43When we use the generic class declaration Stack, as in the short example below, we can specify the actual

44type to be used by the generic class. In this case, we instruct the Stack to use an int type by specifying it

45as a type argument using the angle brackets after the name:

46Stack<int> s = new Stack<int>();

47s.Push(3);

48int x = s.Pop();

49In so doing, we have created a new constructed type, Stack<int>, for which every ItemType inside the

50declaration of Stack is replaced with the supplied type argument int. Indeed, when we create our new

51instance of Stack<int>, the native storage of the items array is now an int[] rather than object[],

53

C# LANGUAGE SPECIFICATION

1providing substantial storage efficiency. Additionally, we have eliminated the boxing penalty associated

2with pushing an int onto the stack. Further, when we pop an item off the stack, we no longer need to

3explicitly cast it to the appropriate type because this particular kind of Stack class natively stores an int in

4its data structure.

5If we wanted to store items other than an int into a Stack, we would have to create a different constructed

6type from Stack, specifying a new type argument. Suppose we had a simple Customer type and we

7wanted to use a Stack to store it. To do so, we simply use the Customer class as the type argument to

8Stack and easily reuse our code:

9Stack<Customer> s = new Stack<Customer>();

10s.Push(new Customer());

11Customer c = s.Pop();

12Of course, once we’ve created a Stack with a Customer type as its type argument, we are now limited to

13storing only Customer objects (or objects of a class derived from Customer). Generics provide strong

14typing, meaning we can no longer improperly store an integer into the stack, like so:

15Stack<Customer> s = new Stack<Customer>();

16s.Push(new Customer());

17

s.Push(3);

//

compile-time error

18

Customer c = s.Pop();

//

no cast required

198.16.3 Multiple type parameters

20Generic type declarations can have any number of type parameters. In our Stack example, we used only

21one type parameter. Suppose we created a simple Dictionary generic class declaration that stored values

22alongside keys. We could define a generic version of a Dictionary by declaring two type parameters,

23separated by commas within the angle brackets of the declaration:

24public class Dictionary<KeyType, ElementType>

25{

26

public void Add(KeyType key, ElementType val) {…}

27public ElementType this[KeyType key] {…}

28}

29When we use Dictionary, we need to supply two type arguments within the angle brackets. Then when

30we call the Add function or use the indexer, the compiler checks that we supplied the right types:

31Dictionary<string, Customer> dict = new Dictionary<string, Customer>();

32dict.Add("Peter", new Customer());

33Customer c = dict["Peter"];

348.16.4 Constraints

35In many cases, we will do more than store data based on a given type parameter. Often, we will also want to

36use members of the type parameter to execute statements within our generic type declaration.

37For example, suppose in the Add method of our Dictionary we wanted to compare items using the

38CompareTo method of the supplied key, like so:

39public class Dictionary<KeyType, ElementType>

40{

41

public void Add(KeyType key, ElementType val)

42

{

43

44

if (key.CompareTo(x) < 0) {…} // compile-time error

45

46}

47}

48Unfortunately, at compile-time the type parameter KeyType is, as expected, generic. As written, the

49compiler will assume that only the operations available to object, such as ToString, are available on the

50variable key of type KeyType. As a result, the compiler will display an error because the CompareTo

51method would not be found. However, we can cast the key variable to a type that does contain a

52CompareTo method, such as an IComparable interface, allowing the program to compile:

54

Chapter 8 Language overview

1public class Dictionary<KeyType, ElementType>

2{

3

public void Add(KeyType key, ElementType val)

4

{

5

6

if (((IComparable)key).CompareTo(x) < 0) {…}

7

8}

9}

10However, if we now construct a type from Dictionary and supply a type argument which does not

11implement IComparable, we will encounter a run-time error, specifically an InvalidCastException.

12Since one of the objectives of generics is to provide strong typing and to reduce the need for casts, a more

13elegant solution is needed.

14We can supply an optional list of constraints for each type parameter. A constraint indicates a requirement

15that a type shall fulfill in order to be accepted as a type argument. (For example, it might have to implement

16a given interface or be derived from a given base class.) A constraint is declared using the word where,

17followed by a type parameter and colon (:), followed by a comma-separated list of constraints, which can

18include a class type, interface types, other type parameters, the reference type constraint “class”, the value

19type constraint “struct”, and the constructor constraint “new()”.

20In order to satisfy our need to use the CompareTo method inside Dictionary, we can impose a constraint

21on KeyType, requiring any type passed as the first argument to Dictionary to implement IComparable,

22like so:

23public class Dictionary<KeyType, ElementType> where KeyType: IComparable

24{

25

public void Add(KeyType key, ElementType val)

26

{

27

28

if (key.CompareTo(x) < 0) {…}

29

30}

31}

32When compiled, this code will now be checked to ensure that each time we construct a Dictionary type

33we are passing a first type argument that implements IComparable. Further, we no longer have to

34explicitly cast variable key to IComparable before calling the CompareTo method.

35Constraints are most useful when they are used in the context of defining a framework, i.e. a collection of

36related classes, where it is advantageous to ensure that a number of types support some common signatures

37and/or base types. Constraints can be used to help define “generic algorithms” that plug together

38functionality provided by different types. This can also be achieved by subclassing and runtime

39polymorphism, but static, constrained polymorphism can, in many cases, result in more efficient code, more

40flexible specifications of generic algorithms, and more errors being caught at compile-time rather than run-

41time. However, constraints need to be used with care and taste. Types that do not implement the constraints

42will not easily be usable in conjunction with generic code.

43For any given type parameter, we can specify any number of interfaces and type parameters as constraints,

44but no more than one class. Each constrained type parameter has a separate where clause. In the example

45below, the KeyType type parameter has two interface constraints, while the ElementType type parameter

46has one class constraint:

47public class Dictionary<KeyType, ElementType >

48

where KeyType: IComparable, IEnumerable

49where ElementType: Customer

50{

51

public void Add(KeyType key, ElementType val)

52

{

53

55

Соседние файлы в предмете Электротехника