The prepackaged data-structure classes provided by the .NET Framework are known as collection classes—they store collections of data.
With collection classes, instead of creating data structures to store these sets of items, you simply use existing data structures, without concern for how they’re implemented.
The .NET Framework collections provide high-performance, high-quality implementations of common data structures and enable effective software reuse.
In earlier versions of C#, the .NET Framework primarily provided the collection classes in the System.Collections
namespace to store and manipulate object references.
The .NET Framework’s System.Collections.Generic
namespace contains collection classes that take advantage of .NET’s generics capabilities.
Array
and EnumeratorsAll arrays implicitly inherit from abstract base class Array
(namespace System
).
using static
DirectiveC# 6’s using static
directive allows access to a class’s static
members without fully qualifying their names.
Array
Method Sort
The static Array
method Sort
sorts an array.
Array
Method Copy
The static Array
method Copy
copies elements from one array to another.
Array
Method BinarySearch
The static Array
method BinarySearch
performs binary searches on an array. This method assumes that it receives a sorted array.
Array
Method GetEnumerator
and Interface IEnumerator
A collection’s GetEnumerator
method returns an enumerator that can iterate over the collection.
All enumerators have methods MoveNext
and Reset
and property Current
.
MoveNext
moves the enumerator to the next element in the collection. MoveNext
returns true
if there’s at least one more element in the collection; otherwise, the method returns false
.
Read-only property Current
returns the object at the current location in the collection.
foreach
The foreach
statement implicitly obtains an enumerator via the GetEnumerator
method and uses the enumerator’s MoveNext
method and Current
property to traverse the collection. This can be done with any collection that implements the IEnumerable
interface—not just arrays.
Array
Methods Clear
, IndexOf
, LastIndexOf
and Reverse
The static Array
method Clear
sets a range of elements to 0
, false
or null
, as appropriate.
The static Array
method IndexOf
locates the first occurrence of an object in an array or portion of an array.
The static Array
method LastIndexOf
locate the last occurrence of an object in an array or portion of an array.
The static Array
method Reverse
reverses the contents of an array or portion of an array.
Dictionary
CollectionsA dictionary is a collection of key–value pairs. A hash table is one way to implement a dictionary.
Dictionary
FundamentalsHashing is a high-speed scheme for converting keys to unique array indices.
SortedDictionary
CollectionGeneric class SortedDictionary
does not use a hash table, but instead stores its key–value pairs in a binary search tree.
Generic class SortedDictionary
takes two type arguments—the first specifies the type of key and the second the type of value.
Method ContainsKey
determines whether a key is in a dictionary.
SortedDictionary
method Add
creates a new entry in the dictionary, with the first argument as the key and the second as the value.
SortedDictionary
’s indexer can be used to get or set a key’s corresponding value.
SortedDictionary
property Keys
gets an ICollection<T>
that contains all the keys.
If you use a foreach
statement with a SortedDictionary
, the iteration variable is of type KeyValuePair
, which has properties Key
and Value
for retrieving the current pair’s key and value.
Invoking the get
accessor of a SortedDictionary
indexer with a key that does not exist in the collection causes a KeyNotFoundException
.
LinkedList
CollectionThe LinkedList
class is a doubly linked list—we can navigate the list both backwards and forwards with nodes of generic class LinkedListNode
.
Each node contains property Value
and read-only properties Previous
and Next
.
The LinkedList
class’s enumerator loops over the values of the nodes, not the nodes themselves.
One LinkedListNode
cannot be a member of more than one LinkedList
. Any attempt to add a node from one LinkedList
to another generates an InvalidOperationException
.
Method Find
performs a linear search on the list and returns the first node that contains a value equal to the passed argument.
Method Remove
deletes a node from a LinkedList
.
?[]
C# 6 also provides a null-conditional operator, ?[]
, for arrays and for collections that support the []
indexing operator.
The ?[]
operator determines whether the array or collection reference is null
before accessing an element.
Dictionary
Initializers and Collection InitializersC# 6’s index initializer syntax enables you to initialize a Dictionary
’s key–value pairs as in
var variableName = new Dictionary<TypeOfKey, TypeOfValue>{
[key1] = value1,
[key2] = value2,
[key3] = value3
};
As of C# 6, the compiler also supports collection initializers for any collection that has an Add
extension method.
A delegate is an object that holds a reference to a method.
Via delegates, you can assign methods to variables, and pass methods to and from other methods. You also can call methods through variables of delegate types.
The compiler converts LINQ queries into calls to extension methods—many of which have delegate parameters.
A delegate type is declared by preceding a method header with keyword delegate
(placed after any access specifiers, such as public
or private
) and following the method header with a semicolon.
A delegate type declaration includes the method header only—the header describes a set of methods with specific parameters and a specific return type.
A delegate variable can hold a reference to any method with a matching signature.
The real power of delegates is in passing method references as arguments to methods.
A method name can be passed directly to a delegate parameter, rather than first assigning it to a delegate variable.
Lambda expressions allow you to define simple, anonymous methods—that is, methods that do not have names and that are defined where they are assigned to a delegate or passed to a delegate parameter.
Working with lambda expressions can reduce the size of your code and the complexity of working with delegates.
A lambda expression begins with a parameter list and is followed by the =>
lambda operator and an expression that represents the lambda’s body.
The value produced by the expression is implicitly returned by the lambda expression.
An expression lambda has a single expression to the right of the lambda operator.
A lambda’s return type is inferred from the return value or, in some cases, from a delegate’s return type.
A delegate can hold a reference to a lambda expression that has a signature that’s compatible with the delegate type.
A lambda expression is called via a variable that references it.
Lambda expressions often are used as arguments to methods with delegate-type parameters, rather than defining and referencing a separate method or defining a delegate variable that references a lambda.
You can explicitly type a lambda expression’s parameters.
When specifying a lambda parameter’s type and/or when a lambda has more than one parameter, you must enclose the parameter list in parentheses.
A statement lambda contains a statement block—one or more statements enclosed in braces ({}
)—to the right of the lambda operator.
With external iteration, you specify how to iterate, not the library. Also, you must access the elements sequentially from beginning to end in a single thread of execution.
External iteration requires variables that are mutated repeatedly as the iteration is performed.
External iteration is error prone.
In functional programming, you specify what you want to accomplish in a task, but not how to accomplish it.
You do not need to specify how to iterate through the elements or declare and use any mutable (that is, modifiable) variables. This is known as internal iteration, because the library code (behind the scenes) iterates through all the elements to perform the task.
A key aspect of functional programming is immutability—not modifying the data source being processed or any other program state, such as counter-control variables in loops. This eliminates common errors that are caused by modifying data incorrectly.
Three common functional-programming operations that you’ll perform on collections of data are filter, map and reduce.
A filter operation results in a new collection containing only the elements that satisfy a condition.
A map operation results in a new collection in which each element of the original collection is mapped to a new value (possibly of a different type). The new collection has the same number of elements as the collection that was mapped.
A reduce operation combines the elements of a collection into a single new value, typically using a lambda that specifies how to combine the elements.
Though C# was not originally designed as a functional-programming language, C#’s LINQ query syntax and LINQ extension methods support functional-programming techniques, such as internal iteration and immutability.
The same tasks you can perform with LINQ query syntax can also be performed with various LINQ extension methods and lambdas.
The compiler translates LINQ query syntax into calls to LINQ extension methods that receive lambdas as arguments.
Min
, Max
, Sum
and Average
Class Enumerable
(namespace System.Linq
) defines the LINQ to Objects extension methods.
Min
returns the smallest value in the collection.
Max
returns the largest value in the collection.
Sum
returns the sum of all the values in the collection.
Average
returns the average of all the values in the collection.
Aggregate
Extension Method for Reduction OperationsYou can define your own reductions with the Aggregate
LINQ extension method.
The version of Aggregate
demonstrated in the chapter receives two arguments—a value that helps you begin the reduction operation and a Func
delegate (namespace System
) representing a method that receives two arguments and returns a value and specifies how to reduce the arguments. Once for each element in the collection, Aggregate
calls this lambda expression.
On the first call to the lambda, Aggregate
’s first argument and the first element of the collection are passed. Each subsequent call to the lambda uses the result of the previous call as the lambda’s first argument and the next element of the collection as the second. This continues until all elements of the collection have been processed to reduce the collection to a single value.
Where
Extension Method for Filtering OperationsYou filter elements to produce a collection of results that match a condition—known as a predicate.
LINQ extension method Where
receives as its argument a Func
delegate for a method that receives one argument and returns a bool
indicating whether a given element should be included in the collection returned by Where
.
The OrderBy
extension method receives as its argument a Func
delegate representing a method that receives one parameter (an element in the collection) and returns a value that’s used to order the results.
Calls to Where
and OrderBy
use deferred execution—they aren’t evaluated until you iterate over the results.
Select
Extension Method for Mapping OperationsMapping transforms a collection’s elements to new values, which sometimes are of different types from the original elements.
LINQ extension method Select
receives as its argument a Func
delegate for a method that receives one argument (an element in the collection) and maps it to a new value (possibly of another type) that’s included in the collection returned by Select
.
A concept called threads enables the operating system to run parts of an app concurrently.
Though all of these tasks can make progress concurrently, they may do so by sharing one processor core.
With multicore processors, apps can operate truly in parallel on separate cores.
The operating system can allow one app’s threads to operate truly in parallel on separate cores, possibly increasing the app’s performance substantially.
Parallelizing apps and algorithms to take advantage of multiple cores is difficult and highly error prone, especially if those tasks share data that can be modified by one or more of the tasks.
Another benefit of functional programming is that you can easily ask the library to perform a task with parallel processing to take advantage of a processor’s multiple cores. This is the purpose of PLINQ (Parallel LINQ)—an implementation of the LINQ to Objects extension methods that parallelizes the operations for increased performance.
Class Enumerable
provides static
method Range
to produce an IEnumerable<int>
containing integer values in sequence. The first argument specifies the starting value in the range and the second specifies the number of values to produce.
Extension method ToArray
returns an array representation of an IEnumerable<T>
’s contents. Class Enumerable
also provides method ToList
to obtain a List<T>
rather than an array.
DateTime
’s Now
property gets the current time.
DateTime
method Subtract
compute the difference between two DateTime
s, which is returned as a TimeSpan
.
TimeSpan
’s TotalMilliseconds
returns the number of milliseconds represented by the TimeSpan
.
To initiate parallel processing, invoke IEnumerable<T>
extension method AsParallel
(from class ParallelEnumerable
), which returns a ParallelQuery<T>
.
An object that implements ParallelQuery<T>
can be used with PLINQ’s parallelized versions of the LINQ extension methods.
Class ParallelEnumerable
defines the ParallelQuery<T>
(PLINQ) parallelized versions of LINQ to Objects extension methods as well as several PLINQ-specific extension methods.
C# supports covariance and contravariance of generic interface and delegate types.
Assigning an array of a derived-class type to an array variable of a base-class type is an example of covariance.
Covariance now works with several generic interface types, including IEnumerable<T>
.
Covariance in generic collections works only with reference types in the same class hierarchy.
Using a cast operator to assign an array variable of a base-class type to an array variable of a derived-class type is an example of contravariance.
Class SortedSet
maintains a set of objects in sorted order—no duplicates are allowed.
The objects placed in a SortedSet
must be comparable to determine their sorting order. Objects are comparable if their classes implement the IComparable<T>
interface.
For classes that do not implement IComparable<T>
, you can compare the objects using an object that implements the IComparer<T>
interface. This interface’s Compare
method compares its two arguments and returns 0 if they are equal, a negative integer if the first object is less than the second, or a positive integer if the first object is greater than the second.
Providing an IComparer
for a base-class type where an IComparer
for a derived-class type is expected is allowed because interface IComparer<T>
supports contravariance.