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

Programming C# 4.0 phần 4 pdf
PREMIUM
Số trang
86
Kích thước
10.4 MB
Định dạng
PDF
Lượt xem
889

Programming C# 4.0 phần 4 pdf

Nội dung xem thử

Mô tả chi tiết

object of type CalendarEvent[] (shown on the left) where each element in the array

refers to one of the event objects.

Figure 7-1. An array with reference type elements

As you saw in Chapter 3, with reference types multiple different variables can all refer

to the same object. Since elements in an array behave in a similar way to local variables

of the element type, we could create an array where all the elements refer to the same

object, as shown in Example 7-11.

Example 7-11. Multiple elements referring to the same object

CalendarEvent theOnlyEvent = new CalendarEvent

{

Title = "Swing Dancing at the South Bank",

StartTime = new DateTimeOffset (2009, 7, 11, 15, 00, 00, TimeSpan.Zero),

Duration = TimeSpan.FromHours(4)

};

CalendarEvent[] events =

{

theOnlyEvent,

theOnlyEvent,

theOnlyEvent,

theOnlyEvent,

theOnlyEvent

};

Figure 7-2 illustrates the result. While this particular example is not brilliantly useful,

in some situations it’s helpful for multiple elements to refer to one object. For example,

imagine a feature for booking meeting rooms or other shared facilities—this could

be a useful addition to a calendar program. An array might describe how the room will

be used today, where each element represents a one-hour slot for a particular room. If

Arrays | 227

the same individual had booked the same room for two different slots, the two corre￾sponding array elements would both refer to the same person.

Figure 7-2. An array where all of the elements refer to the same object

Another feature that reference type array elements have in common with reference type

variables and arguments is support for polymorphism. As you saw in Chapter 4, a

variable declared as some particular reference type can refer to any object of that type,

or of any type derived from the variable’s declared type. This works for arrays too—

using the examples from Chapter 4, if an array’s type is FirefighterBase[], each ele￾ment could refer to a Firefighter, or TraineeFirefighter, or anything else that derives

from FirefighterBase. (And each element is allowed to refer to an object of a different

type, as long as the objects are all compatible with the element type.) Likewise, you can

declare an array of any interface type—for example, INamedPerson[], in which case each

element can refer to any object of any type that implements that interface. Taking this

to extremes, an array of type object[] has elements that can refer to any object of any

reference type, or any boxed value.

As you will remember from Chapter 3, the alternative to a reference type is a value

type. With value types, each variable holds its own copy of the value, rather than a

reference to some potentially shared object. As you would expect, this behavior carries

over to arrays when the element type is a value type. Consider the array shown in

Example 7-12.

Example 7-12. An array of integer values

int[] numbers = { 2, 3, 5, 7, 11 };

Like all the numeric types, int is a value type, so we end up with a rather different

structure. As Figure 7-3 shows, the array elements are the values themselves, rather

than references to values.

Why would you need to care about where exactly the value lives? Well, there’s a sig￾nificant difference in behavior. Given the numbers array in Example 7-12, consider this

code:

228 | Chapter 7: Arrays and Lists

int thirdElementInArray = numbers[2];

thirdElementInArray += 1;

Console.WriteLine("Variable: " + thirdElementInArray);

Console.WriteLine("Array element: " + numbers[2]);

which would print out the following:

Variable: 6

Array element: 5

Figure 7-3. An array with value type elements

Because we are dealing with a value type, the thirdElementInArray local variable gets

a copy of the value in the array. This means that the code can change the local variable

without altering the element in the array. Compare that with similar code working on

the array from Example 7-10:

CalendarEvent thirdElementInArray = events[2];

thirdElementInArray.Title = "Modified title";

Console.WriteLine("Variable: " + thirdElementInArray.Title);

Console.WriteLine("Array element: " + events[2].Title);

This would print out the following:

Variable: Modified title

Array element: Modified title

This shows that we’ve modified the event’s title both from the point of view of the local

variable and from the point of view of the array element. That’s because both refer to

the same CalendarEvent object—with a reference type, when the first line gets an ele￾ment from the array we don’t get a copy of the object, we get a copy of the reference

to that object. The object itself is not copied.

The distinction between the reference and the object being referred to means that

there’s sometimes scope for ambiguity—what exactly does it mean to change an ele￾ment in an array? For value types, there’s no ambiguity, because the element is the

value. The only way to change an entry in the numbers array in Example 7-12 is to assign

a new value into an element:

numbers[2] = 42;

Arrays | 229

But as you’ve seen, with reference types the array element is just a reference, and we

may be able to modify the object it refers to without changing the array element itself.

Of course, we can also change the element, it just means something slightly different—

we’re asking to change which object that particular element refers to. For example, this:

events[2] = events[0];

causes the third element to refer to the same object as the first. This doesn’t modify the

object that element previously referenced. (It might cause the object to become inac￾cessible, though—if nothing else has a reference to that object, overwriting the array

element that referred to it means the program no longer has any way of getting hold of

that object, and so the .NET Framework can reclaim the memory it occupies during

the next garbage collection cycle.)

It’s often tempting to talk in terms of “the fourth object in the array,” and in a lot of

cases, that’s a perfectly reasonable approximation in practice. As long as you’re aware

that with reference types, array elements contain references, not objects, and that what

you really mean is “the object referred to by the fourth element in the array” you won’t

get any nasty surprises.

Regardless of what element type you choose for an array, all arrays provide various

useful methods and properties.

Array Members

An array is an object in its own right; distinct from any objects its elements may refer

to. And like any object, it has a type—as you’ve already seen, we write an array type as

SomeType[]. Whatever type SomeType may be, its corresponding array type, Some

Type[], will derive from a standard built-in type called Array, defined in the System

namespace.

The Array base class provides a variety of services for working with arrays. It can help

you find interesting items in an array. It can reorder the elements, or move information

between arrays. And there are methods for working with the array’s size.

Finding elements

Suppose we want to find out if an array of calendar items contains any events that start

on a particular date. An obvious way to do this would be to write a loop that iterates

through all of the elements in the array, looking at each date in turn (see Example 7-13).

Example 7-13. Finding elements with a loop

DateTime dateOfInterest = new DateTime (2009, 7, 12);

foreach (CalendarEvent item in events)

{

if (item.StartTime.Date == dateOfInterest)

{

Console.WriteLine(item.Title + ": " + item.StartTime);

230 | Chapter 7: Arrays and Lists

}

}

Example 7-13 relies on a useful feature of the DateTimeOffset type that

makes it easy to work out whether two DateTimeOffset values fall on the

same day, regardless of the exact time. The Date property returns a

DateTime in which the year, month, and day are copied over, but the

time of day is set to the default time of midnight.

Although Example 7-13 works just fine, the Array class provides an alternative: its

FindAll method builds a new array containing only those elements in the original array

that match whatever criteria you specify. Example 7-14 uses this method to do the same

job as Example 7-13.

Example 7-14. Finding elements with FindAll

DateTime dateOfInterest = new DateTime (2009, 7, 12);

CalendarEvent[] itemsOnDateOfInterest = Array.FindAll(events,

e => e.StartTime.Date == dateOfInterest);

foreach (CalendarEvent item in itemsOnDateOfInterest)

{

Console.WriteLine(item.Title + ": " + item.StartTime);

}

Notice that we’re using a lambda expression to tell FindAll which items match. That’s

not mandatory—FindAll requires a delegate here, so you can use any of the alternatives

discussed in Chapter 5, including lambda expressions, anonymous methods, method

names, or any expression that returns a suitable delegate. The delegate type here is

Predicate<T>, where T is the array element type (Predicate<CalendarEvent> in this case).

We also discussed predicate delegates in Chapter 5, but in case your memory needs

refreshing, we just need to supply a function that takes a CalendarEvent and returns

true if it matches, and false if it does not. Example 7-14 uses the same expression as

the if statement in Example 7-13.

This may not seem like an improvement on Example 7-13. We’ve not written any less

code, and we’ve ended up using a somewhat more advanced language feature—lambda

expressions—to get the job done. However, notice that in Example 7-14, we’ve already

done all the work of finding the items of interest before we get to the loop. Whereas

the loop in Example 7-13 is a mixture of code that works out what items we need and

code that does something with those items, Example 7-14 keeps those tasks neatly

separated. And if we were doing more complex work with the matching items, that

separation could become a bigger advantage—code tends to be easier to understand

and maintain when it’s not trying to do too many things at once.

The FindAll method becomes even more useful if you want to pass the set of matching

items on to some other piece of code, because you can just pass the array of matches

Arrays | 231

it returns as an argument to some method in your code. But how would you do that

with the approach in Example 7-13, where the match-finding code is intermingled with

the processing code? While the simple foreach loop in Example 7-13 is fine for trivial

examples, FindAll and similar techniques (such as LINQ, which we’ll get to in the next

chapter) are better at managing the more complicated scenarios likely to arise in real

code.

This is an important principle that is not limited to arrays or collections.

In general, you should try to construct your programs by combining

small pieces, each of which does one well-defined job. Code written this

way tends to be easier to maintain and to contain fewer bugs than code

written as one big, sprawling mass of complexity. Separating code that

selects information from code that processes information is just one

example of this idea.

The Array class offers a few variations on the FindAll theme. If you happen to be in￾terested only in finding the first matching item, you can just call Find. Conversely,

FindLast returns the very last matching item.

Sometimes it can be useful to know where in the array a matching item was found. So

as an alternative to Find and FindLast, Array also offers FindIndex and FindLastIndex,

which work in the same way except they return a number indicating the position of the

first or last match, rather than returning the matching item itself.

Finally, one special case for finding the index of an item turns out to crop up fairly

often: the case where you know exactly which object you’re interested in, and just need

to know where it is in the array. You could do this with a suitable predicate, for example:

int index = Array.FindIndex(events, e => e == someParticularEvent);

But Array offers the more specialized IndexOf and LastIndexOf, so you only have to

write this:

int index = Array.IndexOf(events, someParticularEvent);

Ordering elements

Sometimes it’s useful to modify the order in which entries appear in an array. For

example, with a calendar, some events will be planned long in advance while others

may be last-minute additions. Any calendar application will need to be able to ensure

that events are displayed in chronological order, regardless of how they were added, so

we need some way of getting items into the right order.

The Array class makes this easy with its Sort method. We just need to tell it how we

want the events ordered—it can’t really guess, because it doesn’t have any way of

knowing whether we consider our events to be ordered by the Title, StartTime, or

Duration property. This is a perfect job for a delegate: we can provide a tiny bit of code

232 | Chapter 7: Arrays and Lists

that looks at two CalendarEvent objects and says whether one should appear before the

other, and pass that code into the Sort method (see Example 7-15).

Example 7-15. Sorting an array

Array.Sort(events,

(event1, event2) => event1.StartTime.CompareTo(event2.StartTime));

The Sort method’s first argument, events, is just the array we’d like to reorder. (We

defined that back in Example 7-10.) The second argument is a delegate, and for con￾venience we again used the lambda syntax introduced in Chapter 5. The Sort method

wants to be able to know, for any two events, whether one should appear before the

other, It requires a delegate of type Comparison<T>, a function which takes two argu￾ments—we called them event1 and event2 here—and which returns a number. If

event1 is before event2, the number must be negative, and if it’s after, the number must

be positive. We return zero to indicate that the two are equal. Example 7-15 just defers

to the StartTime property—that’s a DateTimeOffset, which provides a handy

CompareTo method that does exactly what we need.

It turns out that Example 7-15 isn’t changing anything here, because the events array

created in Example 7-10 happens to be in ascending order of date and time already. So

just to illustrate that we can sort on any criteria, let’s order them by duration instead:

Array.Sort(events,

(event1, event2) => event1.Duration.CompareTo(event2.Duration));

This illustrates how the use of delegates enables us to plug in any number of different

ordering criteria, leaving the Array class to get on with the tedious job of shuffling the

array contents around to match the specified order.

Some data types such as dates or numbers have an intrinsic ordering. It would be irri￾tating to have to tell Array.Sort how to work out whether one number comes before

or after another. And in fact we don’t have to—we can pass an array of numbers to a

simpler overload of the Sort method, as shown in Example 7-16.

Example 7-16. Sorting intrinsically ordered data

int[] numbers = { 4, 1, 2, 5, 3 };

Array.Sort(numbers);

As you would expect, this arranges the numbers into ascending order. We would pro￾vide a comparison delegate here only if we wanted to sort the numbers into some other

order. You might be wondering what would happen if we tried this simpler method

with an array of CalendarEvent objects:

Array.Sort(events); // Blam!

Arrays | 233

If you try this, you’ll find that the method throws an InvalidOperationException, be￾cause Array.Sort has no way of working out what order we need. It works only for

types that have an intrinsic order. And should we want to, we could make Calen

darEvent self-ordering. We just have to implement an interface called IComparable<Cal

endarEvent>, which provides a single method, CompareTo. Example 7-17 implements

this, and defers to the DateTimeOffset value in StartTime—the DateTimeOffset type

implements IComparable<DateTimeOffset>. So all we’re really doing here is passing the

responsibility on to the property we want to use for ordering, just like we did in Ex￾ample 7-15. The one extra bit of work we do is to check for comparison with null—

the IComparable<T> interface documentation states that a non-null object should always

compare as greater than null, so we return a positive number in that case. Without this

check, our code would crash with a NullReferenceException if null were passed to

CompareTo.

Example 7-17. Making a type comparable

class CalendarEvent : IComparable<CalendarEvent>

{

public string Title { get; set; }

public DateTimeOffset StartTime { get; set; }

public TimeSpan Duration { get; set; }

public int CompareTo(CalendarEvent other)

{

if (other == null) { return 1; }

return StartTime.CompareTo(other.StartTime);

}

}

Now that our CalendarEvent class has declared an intrinsic ordering for itself, we are

free to use the simplest Sort overload:

Array.Sort(events); // Works, now that CalendarEvent is IComparable<T>

Getting your array contents in order isn’t the only reason for relocating elements, so

Array offers some slightly less specialized methods for moving data around.

Moving or copying elements

Suppose you want to build a calendar application that works with multiple sources of

information—maybe you use several different websites with calendar features and

would like to aggregate all the events into a single list. Example 7-18 shows a method

that takes two arrays of CalendarEvent objects, and returns one array containing all the

elements from both.

Example 7-18. Copying elements from two arrays into one big one

static CalendarEvent[] CombineEvents(CalendarEvent[] events1,

CalendarEvent[] events2)

{

234 | Chapter 7: Arrays and Lists

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