Chapter 19 showed how to create and manipulate custom data structures. The discussion was “low level,” in the sense that we painstakingly created each element of each data structure dynamically with new
and modified the data structures by directly manipulating their elements and references to their elements. For the vast majority of apps, there’s no need to build custom data structures. Instead, you can use the prepackaged data-structure classes provided by the .NET Framework. These are known as collection classes—they store collections of data. Each instance of one of these classes is a collection of items. Some examples of collections are the cards you hold in a card game, the songs stored in your computer, the real-estate records in your local registry of deeds (which map book numbers and page numbers to property owners), and the players on your favorite sports team.
Collection classes enable you to store sets of items by using existing data structures, without concern for how they’re implemented. This is a nice example of code reuse. You can code faster and expect excellent performance, maximizing execution speed and minimizing memory consumption. In this chapter, we discuss
the collection interfaces that declare each collection type’s capabilities
the implementation classes
the enumerators that iterate through collections (these are like iterators in languages like C++ and Java).
The .NET Framework provides several namespaces dedicated to collections:
System.Collections
contains collections that store object
s—similar to the data structures we defined in Chapter 19. Such collections can store objects of many different types at the same time, because all C# types derive directly or indirectly from object
. You might encounter this namespace’s classes, such as ArrayList
, Stack
and Hashtable
, in C# legacy code prior to the introduction of generics in C# 2.0 (2005). Legacy code uses older programming techniques—possibly including language and library features that a programming language no longer supports or that have been superseded by newer capabilities.
System.Collections.Generic
contains generic collections, such as the List<T>
(Section 9.4) and Dictionary<K, V>
(Section 17.9) classes, that store objects of types you specify when you create the collection. You should use the generic collections—rather than the object
-based legacy collections—to take advantage of compile-time type checking of your collection-processing code.
System.Collections.Concurrent
contains so-called thread-safe generic collections for use in multithreaded applications.
System.Collections.Specialized
contains collections that are optimized for specific scenarios, such as manipulating collections of bits.
In Section 14.3.3, we introduced the concept of a delegate—an object that holds a reference to a method. Delegates enable apps to store methods as data and to pass a method as an argument to another method. In event handling, a delegate stores a reference to the event-handler method that will be called when a user interacts with a GUI control. In this chapter, we’ll discuss delegates in more detail and introduce lambda expressions, which allow you to define anonymous methods that can be used with delegates. Here we’ll focus on using lambdas to pass method references to methods that specify delegate parameters.
So far, we’ve demonstrated three programming paradigms:
structured programming (also known as procedural programming)
object-oriented programming
generic programming (which we’ll continue discussing in this chapter).
Sections 21.10–21.11 define and introduce functional programming, showing how to use it with LINQ to Objects to write code more concisely and with fewer bugs than programs written with other techniques. In Section 21.12, with one additional method call, we’ll demonstrate how PLINQ (Parallel LINQ) can improve LINQ to Objects performance substantially on multicore systems. In the exercises, we’ll also ask you to revisit earlier examples and implement them using functional-programming techniques.