Siêu thị PDFTải ngay đi em, trời tối mất

Thư viện tri thức trực tuyến

Kho tài liệu với 50,000+ tài liệu học thuật

© 2023 Siêu thị PDF - Kho tài liệu học thuật hàng đầu Việt Nam

accelerated c# 2010 trey nash phần 7 potx
PREMIUM
Số trang
64
Kích thước
7.3 MB
Định dạng
PDF
Lượt xem
1177

accelerated c# 2010 trey nash phần 7 potx

Nội dung xem thử

Mô tả chi tiết

CHAPTER 11 ■ GENERICS

329

public double TotalArea {

get {

double acc = 0;

foreach( T shape in shapes ) {

// DON'T DO THIS!!!

IShape theShape = (IShape) shape;

acc += theShape.Area;

}

return acc;

}

}

public void Add( T shape ) {

shapes.Add( shape );

}

private List<T> shapes = new List<T>();

}

This modification to Shapes<T> indeed does compile and work most of the time. However, this

generic has lost some of its innocence due to the type cast within the foreach loop. Just imagine that if

during a late-night caffeine-induced trance, you attempted to create a constructed type Shapes<int>.

The compiler would happily oblige. But what would happen if you tried to get the TotalArea property

from a Shapes<int> instance? As expected, you would be treated to a runtime exception as the TotalArea

property accessor attempted to cast an int into an IShape. One of the primary benefits of using generics

is better type safety, but in this example I tossed type safety right out the window. So, what are you

supposed to do? The answer lies in a concept called generic constraints. Check out the following correct

implementation:

public class Shapes<T>

where T: IShape

{

public double TotalArea {

get {

double acc = 0;

foreach( T shape in shapes ) {

acc += shape.Area;

}

return acc;

}

}

public void Add( T shape ) {

shapes.Add( shape );

}

private List<T> shapes = new List<T>();

}

Notice the extra line under the first line of the class declaration using the where keyword. This says,

“Define class Shapes<T> where T must implement IShape.” Now the compiler has everything it needs to

enforce type safety, and the JIT compiler has everything it needs to build working code at runtime. The

CHAPTER 11 ■ GENERICS

330

compiler has been given a hint to help it notify you, with a compile-time error, when you attempt to

create constructed types where T does not implement IShape.

The syntax for constraints is pretty simple. There can be one where clause for each type parameter.

Any number of constraints can be listed following the type parameter in the where clause. However, only

one constraint can name a class type (because the CLR has no concept of multiple inheritance), so that

constraint is known as the primary constraint. Additionally, instead of specifying a class name, the

primary constraint can list the special words class or struct, which are used to indicate that the type

parameter must be any class or any struct. The constraint clause can then include as many secondary

constraints as possible, such as a list of interfaces that the parameterized type must implement. Finally,

you can list a constructor constraint that takes the form new() at the end of the constraint list. This

constrains the parameterized type so it is required to have a default parameterless constructor. Class

types must have an explicitly defined default constructor to satisfy this constraint, whereas value types

have a system-generated default constructor.

It is customary to list each where clause on a separate line in any order under the class header. A

comma separates each constraint following the colon in the where clause. That said, let’s take a look at

some constraint examples:

using System.Collections.Generic;

public class MyValueList<T>

where T: struct

// But can't do the following

// where T: struct, new()

{

public void Add( T v ) {

imp.Add( v );

}

private List<T> imp = new List<T>();

}

public class EntryPoint

{

static void Main() {

MyValueList<int> intList =

new MyValueList<int>();

intList.Add( 123 );

// CAN'T DO THIS.

// MyValueList<object> objList =

// new MyValueList<object>();

}

}

In this code, you can see an example of the struct constraint in the declaration for a container that

can contain only value types. The constraint prevents one from declaring the objList variable that I have

commented out in this example because the result of attempting to compile it presents the following

error:

CHAPTER 11 ■ GENERICS

331

error CS0453: The type 'object' must be a non-nullable value type in order  to use it as

parameter 'T' in the generic type or method 'MyValueList<T>'

Alternatively, the constraint could have also claimed to allow only class types. Incidentally, in the

Visual Studio version of the C# compiler, I can’t create a constraint that includes both class and struct.

Of course, doing so is pointless because the same effect comes from including neither struct nor class

in the constraints list. Nevertheless, the compiler complains with an error if you try to do so, claiming

the following:

error CS0449: The 'class' or 'struct' constraint must come before any 

other constraints

This looks like the compiler error could be better stated by saying that only one primary constraint

is allowed in a constraint clause. You’ll also see that I commented out an alternate constraint line, in

which I attempted to include the new() constraint to force the type given for T to support a default

constructor. Clearly, for value types, this constraint is redundant and should be harmless to specify.

Even so, the compiler won’t allow you to provide the new() constraint together with the struct

constraint. Now let’s look at a slightly more complex example that shows two constraint clauses:

using System;

using System.Collections.Generic;

public interface IValue

{

// IValue methods.

}

public class MyDictionary<TKey, TValue>

where TKey: struct, IComparable<TKey>

where TValue: IValue, new()

{

public void Add( TKey key, TValue val ) {

imp.Add( key, val );

}

private Dictionary<TKey, TValue> imp

= new Dictionary<TKey, TValue>();

}

I declared MyDictionary<TKey, TValue> so that the key value is constrained to value types. I also

want those key values to be comparable, so I’ve required the TKey type to implement IComparable<TKey>.

This example shows two constraint clauses, one for each type parameter. In this case, I’m allowing the

TValue type to be either a struct or a class, but I do require that it support the defined IValue interface as

well as a default constructor.

Overall, the constraint mechanism built into C# generics is simple and straightforward. The

complexity of constraints is easy to manage and decipher with few if any surprises. As the language and

the CLR evolve, I suspect that this area will see some additions as more and more applications for

generics are explored. For example, the ability to use the class and struct constraints within a

constraint clause was a relatively late addition to the standard.

CHAPTER 11 ■ GENERICS

332

Finally, the format for constraints on generic interfaces is identical to that of generic classes and

structs.

Constraints on Nonclass Types

So far, I’ve discussed constraints within the context of classes, structs, and interfaces. In reality, any

entity that you can declare generically is capable of having an optional constraints clause. For generic

method and delegate declarations, the constraints clauses follow the formal parameter list to the

method or delegate. Using constraint clauses with method and delegate declarations does provide for

some odd-looking syntax, as shown in the following example:

using System;

public delegate R Operation<T1, T2, R>( T1 val1,

T2 val2 )

where T1: struct

where T2: struct

where R: struct;

public class EntryPoint

{

public static double Add( int val1, float val2 ) {

return val1 + val2;

}

static void Main() {

var op =

new Operation<int, float, double>( EntryPoint.Add );

Console.WriteLine( "{0} + {1} = {2}",

1, 3.2, op(1, 3.2f) );

}

}

I declared a generic delegate for an operator method that accepts two parameters and has a return

value. My constraint is that the parameters and the return value all must be value types. Similarly, for

generic methods, the constraints clauses follow the method declaration but precede the method body.

Co- and Contravariance

Variance is all about convertibility and being able to do what makes type-sense. For example, consider

the following code, which demonstrates array covariance that has been possible in C# since the 1.0 days:

using System;

static class EntryPoint

{

static void Main() {

string[] strings = new string[] {

"One",

CHAPTER 11 ■ GENERICS

333

"Two",

"Three"

};

DisplayStrings( strings );

// Array covariance rules allow the following

// assignment

object[] objects = strings;

// But what happens now?

objects[1] = new object();

DisplayStrings( strings );

}

static void DisplayStrings( string[] strings ) {

Console.WriteLine( "----- Printing strings -----" );

foreach( var s in strings ) {

Console.WriteLine( s );

}

}

}

At the beginning of the Main method, I create an array of strings and then immediately pass it to

DisplayStrings to print them to the console. Then, I assign a variable of type objects[] from the variable

strings. After all, because strings and objects are reference type variables, at first glance it makes

logical sense to be able to assign strings to objects because a string is implicitly convertible to an

object. However, notice right after doing so, I modify slot one and replace it with an object instance.

What happens when I call DisplayStrings the second time passing the strings array? As you might

expect, the runtime throws an exception of type ArrayTypeMismatchException shown as follows:

Unhandled Exception: System.ArrayTypeMismatchException: Attempted to access an

element as a type incompatible with the array.

Array covariance in C# has been in the language since the beginning for Java compatibility. But

because it is flawed, and some say broken, then how can we fix this problem? There are a few ways

indeed. Those of you familiar with functional programming will naturally suggest invariance as the

solution. That is, if an array is invariant similar to System.String, a copy is made typically in a lazy

fashion at the point where one is assigned into another variable. However, let’s see how we might fix this

problem using generics:

using System;

using System.Collections.Generic;

static class EntryPoint

{

static void Main() {

List<string> strings = new List<string> {

"One",

"Two",

"Three"

CHAPTER 11 ■ GENERICS

334

};

// THIS WILL NOT COMPILE!!!

List<object> objects = strings;

}

}

The spirit of the preceding code is identical to the array covariance example, but it will not compile.

If you attempt to compile this, you will get the following compiler error:

error CS0029: Cannot implicitly convert type 

'System.Collections.Generic.List<string>' to 

'System.Collections.Generic.List<object>'

The ultimate problem is that each constructed type is an individual type, and even though they

might originate from the same generic type, they have no implicit type relation between them. For

example, there is no implicit relationship between List<string> and List<object>, and just because

they both are constructed types of List<T> and string is implicitly convertible to object does not imply

that they are convertible from one to the other.

Don’t lose hope, though. There is a syntax added in C# 4.0 that allows you to achieve the desired

result. Using this new syntax, you can notate a generic interface or delegate indicating whether it

supports covariance or contravariance. Additionally, the new variance rules apply only to constructed

types in which reference types are passed for the type arguments to the generic type.

Covariance

Within strongly typed programming languages such as C#, an operation is covariant if it reflects and

preserves the ordering of types so they are ordered from more specific types to more generic types. To

illustrate, I’ll borrow from the example in the previous section to show how array assignment rules in C#

are covariant:

string s = "Hello";

object o = s;

string[] strings = new string[3];

object[] objects = strings;

The first two lines make perfect sense; after all, variables of type string are implicitly convertible to

type object because string derives from object. The second set of lines shows that variables of type

string[] are implicitly convertible to variables of type object[]. And because the ordering of types

between the two implicit assignments is identical that is, from a more specialized type (string) to a

more generic type (object) the array assignment operation is said to be covariant.

Now, to translate this concept to generic interface assignment, an interface of type IOperation<T> is

covariance-convertible to IOperation<R> if there exists an implicit reference conversion from T to R and

IOperation<T> to IOperation<R>. Simply put, if for the two conversion operations just mentioned, T and R

Tải ngay đi em, còn do dự, trời tối mất!