1. Trang chủ
  2. » Công Nghệ Thông Tin

Programming C# 4.0 phần 4 pdf

86 455 0

Đang tải... (xem toàn văn)

Tài liệu hạn chế xem trước, để xem đầy đủ mời bạn chọn Tải xuống

THÔNG TIN TÀI LIỆU

Thông tin cơ bản

Định dạng
Số trang 86
Dung lượng 10,41 MB

Nội dung

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 CalendarEvent[] combinedEvents = new CalendarEvent[events1.Length + events2.Length]; events1.CopyTo(combinedEvents, 0); events2.CopyTo(combinedEvents, events1.Length); return combinedEvents; } This example uses the CopyTo method, which makes a complete copy of all the elements of the source array into the target passed as the first argument. The second argument says where to start copying elements into the target—Example 7-18 puts the first array’s elements at the start (offset zero), and then copies the second array’s elements directly after that. (So the ordering won’t be very useful—you’d probably want to sort the results after doing this.) You might sometimes want to be a bit more selective—you might want to copy only certain elements from the source into the target. For example, suppose you want to remove the first event. Arrays cannot be resized in .NET, but you could create a new array that’s one element shorter, and which contains all but the first element of the original array. The CopyTo method can’t help here as it copies the whole array, but you can use the more flexible Array.Copy method instead, as Example 7-19 shows. Example 7-19. Copying less than the whole array static CalendarEvent[] RemoveFirstEvent(CalendarEvent[] events) { CalendarEvent[] croppedEvents = new CalendarEvent[events.Length - 1]; Array.Copy( events, // Array from which to copy 1, // Starting point in source array croppedEvents, // Array into which to copy 0, // Starting point in destination array events.Length - 1 // Number of elements to copy ); return croppedEvents; } The key here is that we get to specify the index from which we want to start copying— 1 in this case, skipping over the first element, which has an index of 0. In practice, you would rarely do this—if you need to be able to add or remove items from a collection, you would normally use the List<T> type that we’ll be looking at later in this chapter, rather than a plain array. And even if you are working with arrays, there’s an Array.Resize helper function that you would typically use in reality— it calls Array.Copy for you. However, you often have to copy data be- tween arrays, even if it might not be strictly necessary in this simple example. A more complex example would have obscured the essential simplicity of Array.Copy. Arrays | 235 The topic of array sizes is a little more complex than it first appears, so let’s look at that in more detail. Array Size Arrays know how many elements they contain—several of the previous examples have used the Length property to discover the size of an existing array. This read-only prop- erty is defined by the base Array class, so it’s always present. * That may sound like enough to cover the simple task of knowing an array’s size, but arrays don’t have to be simple sequential lists. You may need to work with multidimensional data, and .NET supports two different styles of arrays for that: jagged and rectangular arrays. Arrays of arrays (or jagged arrays) As we said earlier, you can make an array using any type as the element type. And since arrays themselves have types, it follows that you can have an array of arrays. For ex- ample, suppose we wanted to create a list of forthcoming events over the next five days, grouped by day. We could represent this as an array with one entry per day, and since each day may have multiple events, each entry needs to be an array. Example 7-20 creates just such an array. Example 7-20. Building an array of arrays static CalendarEvent[][] GetEventsByDay(CalendarEvent[] allEvents, DateTime firstDay, int numberOfDays) { CalendarEvent[][] eventsByDay = new CalendarEvent[numberOfDays][]; for (int day = 0; day < numberOfDays; ++day) { DateTime dateOfInterest = (firstDay + TimeSpan.FromDays(day)).Date; CalendarEvent[] itemsOnDateOfInterest = Array.FindAll(allEvents, e => e.StartTime.Date == dateOfInterest); eventsByDay[day] = itemsOnDateOfInterest; } return eventsByDay; } * There’s also a LongLength, which is a 64-bit version of the property, which theoretically allows for larger arrays than the 32-bit Length property. However, .NET currently imposes an upper limit on the size of any single array: it cannot use more than 2 GB of memory, even in a 64-bit process. So in practice, LongLength isn’t very useful in the current version of .NET (4). (You can use a lot more than 2 GB of memory in total in a 64-bit process—the 2 GB limit applies only to individual arrays.) 236 | Chapter 7: Arrays and Lists [...]... return walls[newY, x] == 0; If you pass in the wrong number of indexes, the C# compiler will complain The number of dimensions (or rank, to use the official term) is considered to be part of the type: int[,] is a different type than int[,,], and C# checks that the number of indexes you supply matches the array type’s rank 242 | Chapter 7: Arrays and Lists Example 7-22 performs two checks: before it... element access work 246 | Chapter 7: Arrays and Lists Custom Indexers Arrays are an integral part of the NET type system, so C# knows exactly what to do when you access an array element using the square bracket syntax However, as List demonstrates, it’s also possible to use this same syntax with some objects that are not arrays For this to work, the object’s type needs to help C# out by defining the... Using a custom indexer Indexable ix = new Indexable(); Console.WriteLine(ix[10]); ix [42 ] = "Xyzzy"; After constructing the object, the next line uses the same element access syntax you’d use to read an element from an array But this is not an array, so the C# compiler will look for a property of the kind shown in Example 7- 24 If you try this on a type that doesn’t provide an indexer, you’ll get a compiler... work: aai[20].Number = 45 6; If you try this, you’ll find that the C# compiler reports the following error: error CS1612: Cannot modify the return value of 'ArrayAndIndexer.this[int]' because it is not a variable That’s a slightly cryptic message But the problem becomes clear when we think about what we just asked the compiler to do The intent of this code: aai[20].Number = 45 6; seems clear—we... new value for the element, as in: aai[20] = new CanChange { Number = 45 6 }; That compiles, but we end up losing the Name property that the element in that location previously had, because we overwrote the entire value of the element Since set doesn’t work, that leaves get The C# compiler could interpret this code: aai[20].Number = 45 6; as being equivalent to the code in Example 7-27 Example 7-27 What... TraineeFirefighter derives from FirefighterBase, an IEnumera ble will return a sequence of objects that are all of type Firefight erBase (as well as being of type TraineeFirefighter) In C# 4. 0, this works as you’d expect But it didn’t in previous versions In general, it’s not safe to assume that types are necessarily compatible just because their type arguments happen to be compatible... IComparable is capable of being compared to any FirefighterBase, and is therefore able to be compared with a TraineeFirefighter By default, generic arguments are neither covariant nor contravariant C# 4. 0 introduced support for variance because the absence of variance with collection interfaces just seemed wrong—IEnumerable now works like most developers would expect Collections and Polymorphism... array, but C# does not extend that courtesy to other collection types While we can initialize the list in much the same way as we would an array, the difference is that we are free to add and remove elements later To add a new element, we can use the Add method: CalendarEvent newEvent = new CalendarEvent { Title = "Dean Collins Shim Sham Lesson", StartTime = new DateTimeOffset (2009, 7, 14, 19, 15,... much simpler structure than the two-dimensional jagged array in Figure 7 -4 While Figure 7-6 is accurate in the sense that just one object holds all the values here, the grid-like layout of the numbers is not a literal representation of how the numbers are really stored, any more than the position of the various objects in Figure 7 -4 is a literal representation of what you’d see if you peered into your... are not arrays For this to work, the object’s type needs to help C# out by defining the behavior for this syntax This takes the form of a slightly unusual-looking property, as shown in Example 7- 24 Example 7- 24 A custom indexer class Indexable { public string this[int index] { get { return "Item " + index; } set { Console.WriteLine("You set item " + index + " to " + value); } } } This has the get and . 1, 1, 1, 1 }, { 1, 0, 1, 0, 0, 0, 1, 0, 0, 0, 0, 1 }, { 1, 0, 1, 0, 1, 0, 1, 0, 0, 1, 0, 1 }, { 1, 0, 1, 0, 1, 0, 1, 0, 1, 1, 1, 1 }, { 1, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 1 }, { 1, 1, 1, 1,. 1 }, { 1, 0, 1, 1, 1, 1, 1, 1, 1, 1, 0, 1 }, { 1, 0, 0, 0, 0, 0, 0, 1, 0, 0, 0, 1 }, { 1, 0, 1, 1, 1, 1, 0, 1, 0, 1, 0, 1 }, { 1, 0, 1, 0, 0, 0, 0, 0, 0, 1, 0, 0 }, { 1, 0, 1, 0, 1, 1, 1,. other classics.) 2 40 | Chapter 7: Arrays and Lists Example 7-21. A multidimensional rectangular array int[,] walls = new int[,] { { 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1 }, { 0, 0, 1, 0, 0, 0, 0, 0, 0, 0, 0,

Ngày đăng: 06/08/2014, 09:20

TỪ KHÓA LIÊN QUAN