{"id":310,"date":"2023-10-20T13:24:22","date_gmt":"2023-10-20T05:24:22","guid":{"rendered":"https:\/\/miie.net\/?p=310"},"modified":"2023-10-20T13:24:22","modified_gmt":"2023-10-20T05:24:22","slug":"pro-c10-chapter-10-collections-and-generics","status":"publish","type":"post","link":"https:\/\/diji.net\/?p=310","title":{"rendered":"Pro C#10 CHAPTER 10 Collections and Generics"},"content":{"rendered":"<p>CHAPTER 10<\/p>\n<p>Collections and Generics<\/p>\n<p>Any application you create with the .NET Core platform will need to contend with the issue of maintaining and manipulating a set of data points in memory. These data points can come from any variety of locations including a relational database, a local text file, an XML document, a web service call, or perhaps user- provided input.<br \/>\nWhen the .NET platform was first released, programmers frequently used the classes of the System. Collections namespace to store and interact with bits of data used within an application. In .NET 2.0, the C# programming language was enhanced to support a feature termed generics; with this change, a new namespace was introduced in the base class libraries: System.Collections.Generic.<br \/>\nThis chapter will provide you with an overview of the various collection (generic and nongeneric) namespaces and types found within the .NET Core base class libraries. As you will see, generic containers are often favored over their nongeneric counterparts because they typically provide greater type safety and performance benefits. After you have learned how to create and manipulate the generic items found in the framework, the remainder of this chapter will examine how to build your own generic methods and generic types. As you do this, you will learn about the role of constraints (and the corresponding C# where keyword), which allow you to build extremely type-safe classes.<\/p>\n<p>The Motivation for Collection Classes<br \/>\nThe most primitive container you could use to hold application data is undoubtedly the array. As you saw in Chapter 4, C# arrays allow you to define a set of identically typed items (including an array of System. Objects, which essentially represents an array of any type of data) of a fixed upper limit. Also recall from Chapter 4 that all C# array variables gather a good deal of functionality from the System.Array class. By way of a quick review, consider the following code, which creates an array of textual data and manipulates its contents in various ways:<\/p>\n<p>\/\/ Make an array of string data.<br \/>\nstring[] strArray = {&quot;First&quot;, &quot;Second&quot;, &quot;Third&quot; };<\/p>\n<p>\/\/ Show number of items in array using Length property. Console.WriteLine(&quot;This array has {0} items.&quot;, strArray.Length); Console.WriteLine();<\/p>\n<p>\/\/ Display contents using enumerator. foreach (string s in strArray)<br \/>\n{<br \/>\nConsole.WriteLine(&quot;Array Entry: {0}&quot;, s);<br \/>\n}<\/p>\n<p>\u00a9 Andrew Troelsen, Phil Japikse 2022<br \/>\nA. Troelsen and P. Japikse, Pro C# 10 with .NET 6, <a href=\"https:\/\/doi.org\/10.1007\/978-1-4842-7869-7_10\"><a href=\"https:\/\/doi.org\/10.1007\/978-1-4842-7869-7_10\"><a href=\"https:\/\/doi.org\/10.1007\/978-1-4842-7869-7_10\">https:\/\/doi.org\/10.1007\/978-1-4842-7869-7_10<\/a><\/a><\/a><\/p>\n<p>387<\/p>\n<p>Console.WriteLine();<\/p>\n<p>\/\/ Reverse the array and print again. Array.Reverse(strArray);<br \/>\nforeach (string s in strArray)<br \/>\n{<br \/>\nConsole.WriteLine(&quot;Array Entry: {0}&quot;, s);<br \/>\n}<\/p>\n<p>Console.ReadLine();<\/p>\n<p>While basic arrays can be useful to manage small amounts of fixed-size data, there are many other times where you require a more flexible data structure, such as a dynamically growing and shrinking container or a container that can hold objects that meet only a specific criterion (e.g., only objects deriving from a specific base class or only objects implementing a particular interface). When you make use of a simple array, always remember they are created with a \u201cfixed size.\u201d If you make an array of three items, you get only three items; therefore, the following code will result in a runtime exception (an IndexOutOfRangeException, to be exact):<\/p>\n<p>\/\/ Make an array of string data.<br \/>\nstring[] strArray = { &quot;First&quot;, &quot;Second&quot;, &quot;Third&quot; };<\/p>\n<p>\/\/ Try to add a new item at the end?? Runtime error! strArray[3] = &quot;new item?&quot;;<br \/>\n...<\/p>\n<p>\u25a0 Note  It is actually possible to change the size of an array using the generic Resize()<T> method. However, this will result in a copy of the data into a new array object and could be inefficient.<\/p>\n<p>To help overcome the limitations of a simple array, the .NET Core base class libraries ship with a number of namespaces containing collection classes. Unlike a simple C# array, collection classes are built to dynamically resize themselves on the fly as you insert or remove items. Moreover, many of the collection classes offer increased type safety and are highly optimized to process the contained data in a memory-<br \/>\nefficient manner. As you read this chapter, you will quickly notice that a collection class can belong to one of two broad categories.<br \/>\n\u2022Nongeneric collections (primarily found in the System.Collections namespace)<br \/>\n\u2022Generic collections (primarily found in the System.Collections.Generic<br \/>\nnamespace)<br \/>\nNongeneric collections are typically designed to operate on System.Object types and are, therefore, loosely typed containers (however, some nongeneric collections do operate on only a specific type of data, such as string objects). In contrast, generic collections are much more type-safe, given that you must specify the \u201ctype of type\u201d they contain upon creation. As you will see, the telltale sign of any generic item is the \u201ctype parameter\u201d marked with angled brackets (e.g., List<T>). You will examine the details of generics (including the many benefits they provide) a bit later in this chapter. For now, let\u2019s examine some<br \/>\nof the key nongeneric collection types in the System.Collections and System.Collections.Specialized<br \/>\nnamespaces.<\/p>\n<p>The System.Collections Namespace<br \/>\nWhen the .NET platform was first released, programmers frequently used the nongeneric collection classes found within the System.Collections namespace, which contains a set of classes used to manage and organize large amounts of in-memory data. Table 10-1 documents some of the more commonly used collection classes of this namespace and the core interfaces they implement.<\/p>\n<p>Table 10-1. Useful Types of System.Collections<\/p>\n<p>System.Collections<br \/>\nClass   Meaning in Life Key Implemented Interfaces<br \/>\nArrayList   Represents a dynamically sized collection of objects listed in sequential order IList, ICollection, IEnumerable, and ICloneable<br \/>\nBitArray    Manages a compact array of bit values, which are represented as Booleans, where true indicates that the bit is on (1) and false indicates the bit is off (0)    ICollection, IEnumerable, and ICloneable<br \/>\nHashtable   Represents a collection of key-value pairs that are organized based on the hash code of the key IDictionary, ICollection, IEnumerable, and ICloneable<br \/>\nQueue   Represents a standard first-in, first-out (FIFO) collection of objects  ICollection, IEnumerable, and ICloneable<br \/>\nSortedList  Represents a collection of key-value pairs that are sorted by the keys and are accessible by key and by index   IDictionary, ICollection, IEnumerable, and ICloneable<br \/>\nStack   A last-in, first-out (LIFO) stack providing push and pop (and peek) functionality   ICollection, IEnumerable, and ICloneable<\/p>\n<p>The interfaces implemented by these collection classes provide huge insights into their overall functionality. Table 10-2 documents the overall nature of these key interfaces, some of which you worked with firsthand in Chapter 8.<\/p>\n<p>Table 10-2. Key Interfaces Supported by Classes of System.Collections<\/p>\n<p>System.Collections<br \/>\nInterface   Meaning in Life<br \/>\nICollection Defines general characteristics (e.g., size, enumeration, and thread safety) for all nongeneric collection types<br \/>\nICloneable  Allows the implementing object to return a copy of itself to the caller<br \/>\nIDictionary Allows a nongeneric collection object to represent its contents using key-value pairs<br \/>\nIEnumerable Returns an object implementing the IEnumerator interface (see next table entry)<br \/>\nIEnumerator Enables foreach-style iteration of collection items<br \/>\nIList   Provides behavior to add, remove, and index items in a sequential list of objects<\/p>\n<p>An Illustrative Example: Working with the ArrayList<br \/>\nBased on your experience, you might have some firsthand experience using (or implementing) some of these classic data structures such as stacks, queues, and lists. If this is not the case, I will provide some further details on their differences when you examine their generic counterparts a bit later in this chapter. Until then, here is example code using an ArrayList object:<\/p>\n<p>\/\/ You must import System.Collections to access the ArrayList. using System.Collections;<br \/>\nArrayList strArray = new ArrayList();<br \/>\nstrArray.AddRange(new string[] { &quot;First&quot;, &quot;Second&quot;, &quot;Third&quot; });<\/p>\n<p>\/\/ Show number of items in ArrayList.<br \/>\nSystem.Console.WriteLine(&quot;This collection has {0} items.&quot;, strArray.Count); System.Console.WriteLine();<\/p>\n<p>\/\/ Add a new item and display current count. strArray.Add(&quot;Fourth!&quot;);<br \/>\nSystem.Console.WriteLine(&quot;This collection has {0} items.&quot;, strArray.Count);<\/p>\n<p>\/\/ Display contents.<br \/>\nforeach (string s in strArray)<br \/>\n{<br \/>\nSystem.Console.WriteLine(&quot;Entry: {0}&quot;, s);<br \/>\n}<br \/>\nSystem.Console.WriteLine();<\/p>\n<p>Notice that you can add (or remove) items on the fly and the container automatically resizes itself accordingly.<br \/>\nAs you would guess, the ArrayList class has many useful members beyond the Count property and AddRange() and Add() methods, so be sure you consult the .NET Core documentation for full details. On a related note, the other classes of System.Collections (Stack, Queue, etc.) are also fully documented in the<br \/>\n.NET Core help system.<br \/>\nHowever, it is important to point out that a majority of your .NET Core projects will most likely not make use of the collection classes in the System.Collections namespace! To be sure, these days it is far more common to make use of the generic counterpart classes found in the System.Collections.Generic namespace. Given this point, I won\u2019t comment on (or provide code examples for) the remaining nongeneric classes found in System.Collections.<\/p>\n<p>A Survey of System.Collections.Specialized Namespace<br \/>\nSystem.Collections is not the only .NET Core namespace that contains nongeneric collection classes. The System.Collections.Specialized namespace defines a number of (pardon the redundancy) specialized collection types. Table 10-3 documents some of the more useful types in this particular collection-centric namespace, all of which are nongeneric.<\/p>\n<p>Table 10-3. Useful Classes of System.Collections.Specialized<\/p>\n<p>System.Collections.<br \/>\nSpecialized Type    Meaning in Life<br \/>\nHybridDictionary    This class implements IDictionary by using a ListDictionary while the collection is small and then switching to a Hashtable when the collection gets large.<br \/>\nListDictionary  This class is useful when you need to manage a small number of items (ten or so) that can change over time. This class makes use of a singly linked list to maintain its data.<br \/>\nStringCollection    This class provides an optimal way to manage large collections of string data.<br \/>\nBitVector32 This class provides a simple structure that stores Boolean values and small integers in 32 bits of memory.<\/p>\n<p>Beyond these concrete class types, this namespace also contains many additional interfaces and abstract base classes that you can use as a starting point for creating custom collection classes. While these \u201cspecialized\u201d types might be just what your projects require in some situations, I won\u2019t comment on their usage here. Again, in many cases, you will likely find that the System.Collections.Generic namespace provides classes with similar functionality and additional benefits.<\/p>\n<p>\u25a0 Note there are two additional collection-centric namespaces (System.Collections.ObjectModel and System.Collections.Concurrent) in the .net Core base class libraries. You will examine the former namespace later in this chapter, after you are comfortable with the topic of generics. System.Collections. Concurrent provides collection classes well suited to a multithreaded environment (see Chapter 15 for information on multithreading).<\/p>\n<p>The Problems of Nongeneric Collections<br \/>\nWhile it is true that many successful .NET and .NET Core applications have been built over the years using these nongeneric collection classes (and interfaces), history has shown that using these types can result in a number of issues.<br \/>\nThe first issue is that using the System.Collections and System.Collections.Specialized classes can result in some poorly performing code, especially when you are manipulating numerical data (e.g., value types). As you\u2019ll see momentarily, the CoreCLR must perform a number of memory transfer operations when you store structures in any nongeneric collection class prototyped to operate on System.Objects, which can hurt runtime execution speed.<br \/>\nThe second issue is that most of the nongeneric collection classes are not type-safe because (again) they were developed to operate on System.Objects, and they could therefore contain anything at all.<br \/>\nIf a developer needed to create a highly type-safe collection (e.g., a container that can hold objects implementing only a certain interface), the only real choice was to create a new collection class by hand. Doing so was not too labor intensive, but it was a tad on the tedious side.<\/p>\n<p>Before you look at how to use generics in your programs, you\u2019ll find it helpful to examine the issues of nongeneric collection classes a bit closer; this will help you better understand the problems generics intended to solve in the first place. If you want to follow along, create a new Console Application project<br \/>\nnamed IssuesWithNonGenericCollections. Next, make sure you import the System.Collections namespace to the top of the Program.cs file and clear out the rest of the code.<\/p>\n<p>using System.Collections;<\/p>\n<p>The Issue of Performance<br \/>\nAs you might recall from Chapter 4, the .NET Core platform supports two broad categories of data: value types and reference types. Given that .NET Core defines two major categories of types, you might occasionally need to represent a variable of one category as a variable of the other category. To do so, C#<br \/>\nprovides a simple mechanism, termed boxing, to store the data in a value type within a reference variable. Assume that you have created a local variable of type int in a method called SimpleBoxUnboxOperation. If, during the course of your application, you were to represent this value type as a reference type, you would box the value, as follows:<\/p>\n<p>static void SimpleBoxUnboxOperation()<br \/>\n{<br \/>\n\/\/ Make a ValueType (int) variable. int myInt = 25;<\/p>\n<p>\/\/ Box the int into an object reference. object boxedInt = myInt;<br \/>\n}<\/p>\n<p>Boxing can be formally defined as the process of explicitly assigning a value type to a System.Object variable. When you box a value, the CoreCLR allocates a new object on the heap and copies the value type\u2019s value (25, in this case) into that instance. What is returned to you is a reference to the newly allocated heap- based object.<br \/>\nThe opposite operation is also permitted through unboxing. Unboxing is the process of converting the value held in the object reference back into a corresponding value type on the stack. Syntactically speaking, an unboxing operation looks like a normal casting operation. However, the semantics are quite different.<br \/>\nThe CoreCLR begins by verifying that the receiving data type is equivalent to the boxed type, and if so, it copies the value back into a local stack-based variable. For example, the following unboxing operations work successfully, given that the underlying type of the boxedInt is indeed an int:<\/p>\n<p>static void SimpleBoxUnboxOperation()<br \/>\n{<br \/>\n\/\/ Make a ValueType (int) variable. int myInt = 25;<\/p>\n<p>\/\/ Box the int into an object reference. object boxedInt = myInt;<\/p>\n<p>\/\/ Unbox the reference back into a corresponding int. int unboxedInt = (int)boxedInt;<br \/>\n}<\/p>\n<p>When the C# compiler encounters boxing\/unboxing syntax, it emits CIL code that contains the box\/unbox op codes. If you were to examine your compiled assembly using ildasm.exe, you would find the following:<\/p>\n<p>.method assembly hidebysig static<br \/>\nvoid '&lt;<Main>$&gt;g SimpleBoxUnboxOperation|0_0'() cil managed<br \/>\n{<br \/>\n.maxstack 1<br \/>\n.locals init (int32 V_0, object V_1, int32 V_2) IL_0000: nop<br \/>\nIL_0001: ldc.i4.s   25 IL_0003: stloc.0 IL_0004: ldloc.0<br \/>\nIL_0005: box    [System.Runtime]System.Int32 IL_000a: stloc.1<br \/>\nIL_000b: ldloc.1<br \/>\nIL_000c: unbox.any [System.Runtime]System.Int32 IL_0011: stloc.2<br \/>\nIL_0012: ret<br \/>\n} \/\/ end of method '<Program>$'::'&lt;<Main>$&gt;g SimpleBoxUnboxOperation|0_0'<\/p>\n<p>Remember that unlike when performing a typical cast, you must unbox into an appropriate data type. If you attempt to unbox a piece of data into the incorrect data type, an InvalidCastException exception will be thrown. To be perfectly safe, you should wrap each unboxing operation in try\/catch logic; however, this would be quite labor intensive to do for every unboxing operation. Consider the following code update, which will throw an error because you\u2019re attempting to unbox the boxed int into a long:<\/p>\n<p>static void SimpleBoxUnboxOperation()<br \/>\n{<br \/>\n\/\/ Make a ValueType (int) variable. int myInt = 25;<\/p>\n<p>\/\/ Box the int into an object reference. object boxedInt = myInt;<\/p>\n<p>\/\/ Unbox in the wrong data type to trigger<br \/>\n\/\/ runtime exception.<br \/>\ntry<br \/>\n{<br \/>\nlong unboxedLong = (long)boxedInt;<br \/>\n}<br \/>\ncatch (InvalidCastException ex)<br \/>\n{<br \/>\nConsole.WriteLine(ex.Message);<br \/>\n}<br \/>\n}<\/p>\n<p>At first glance, boxing\/unboxing might seem like a rather uneventful language feature that is more academic than practical. After all, you will seldom need to store a local value type in a local object variable, as shown here. However, it turns out that the boxing\/unboxing process is quite helpful because it allows you to assume everything can be treated as a System.Object, while the CoreCLR takes care of the memory- related details on your behalf.<\/p>\n<p>Let\u2019s look at a practical use of these techniques. We will examine the System.Collections.ArrayList class and use it to hold onto a batch of numeric (stack-allocated) data. The relevant members of the ArrayList class are listed as follows. Notice that they are prototyped to operate on System.Object data. Now consider the Add(), Insert(), and Remove() methods, as well as the class indexer.<\/p>\n<p>public class ArrayList : IList, ICloneable<br \/>\n{<br \/>\n...<br \/>\npublic virtual int Add(object? value);<br \/>\npublic virtual void Insert(int index, object? value); public virtual void Remove(object? obj);<br \/>\npublic virtual object? this[int index] {get; set; }<br \/>\n}<\/p>\n<p>ArrayList has been built to operate on objects, which represent data allocated on the heap, so it might seem strange that the following code compiles and executes without throwing an error:<\/p>\n<p>static void WorkWithArrayList()<br \/>\n{<br \/>\n\/\/ Value types are automatically boxed when<br \/>\n\/\/ passed to a method requesting an object. ArrayList myInts = new ArrayList(); myInts.Add(10);<br \/>\nmyInts.Add(20); myInts.Add(35);<br \/>\n}<\/p>\n<p>Although you pass in numerical data directly into methods requiring an object, the runtime automatically boxes the stack-based data on your behalf. Later, if you want to retrieve an item from the ArrayList using the type indexer, you must unbox the heap-allocated object into a stack-allocated integer using a casting operation. Remember that the indexer of the ArrayList is returning System.Objects, not System.Int32s.<\/p>\n<p>static void WorkWithArrayList()<br \/>\n{<br \/>\n\/\/ Value types are automatically boxed when<br \/>\n\/\/ passed to a member requesting an object. ArrayList myInts = new ArrayList(); myInts.Add(10);<br \/>\nmyInts.Add(20); myInts.Add(35);<\/p>\n<p>\/\/ Unboxing occurs when an object is converted back to<br \/>\n\/\/ stack-based data.<br \/>\nint i = (int)myInts[0];<\/p>\n<p>\/\/ Now it is reboxed, as WriteLine() requires object types!<br \/>\nConsole.WriteLine(&quot;Value of your int: {0}&quot;, i);<br \/>\n}<\/p>\n<p>Again, note that the stack-allocated System.Int32 is boxed prior to the call to ArrayList.Add(), so it can be passed in the required System.Object. Also note that the System.Object is unboxed back into a<br \/>\nSystem.Int32 once it is retrieved from the ArrayList via the casting operation, only to be boxed again when it is passed to the Console.WriteLine() method, as this method is operating on System.Object variables.<br \/>\nBoxing and unboxing are convenient from a programmer\u2019s viewpoint, but this simplified approach to stack\/heap memory transfer comes with the baggage of performance issues (in both speed of execution and code size) and a lack of type safety. To understand the performance issues, ponder these steps that must occur to box and unbox a simple integer:<br \/>\n1.A new object must be allocated on the managed heap.<br \/>\n2.The value of the stack-based data must be transferred into that memory location.<br \/>\n3.When unboxed, the value stored on the heap-based object must be transferred back to the stack.<br \/>\n4.The now unused object on the heap will (eventually) be garbage collected.<br \/>\nAlthough this particular WorkWithArrayList() method won\u2019t cause a major bottleneck in terms of performance, you could certainly feel the impact if an ArrayList contained thousands of integers that your program manipulates on a somewhat regular basis. In an ideal world, you could manipulate stack-based data in a container without any performance issues. Ideally, it would be nice if you did not have to bother plucking data from this container using try\/catch scopes (this is exactly what generics let you achieve).<\/p>\n<p>The Issue of Type Safety<br \/>\nI touched on the issue of type safety when covering unboxing operations. Recall that you must unbox your data into the same data type it was declared as before boxing. However, there is another aspect of type safety you must keep in mind in a generic-free world: the fact that a majority of the classes of System.Collections can typically hold anything whatsoever because their members are prototyped to operate on System.<br \/>\nObjects. For example, this method builds an ArrayList of random bits of unrelated data:<\/p>\n<p>static void ArrayListOfRandomObjects()<br \/>\n{<br \/>\n\/\/ The ArrayList can hold anything at all. ArrayList allMyObjects = new ArrayList(); allMyObjects.Add(true);<br \/>\nallMyObjects.Add(new OperatingSystem(PlatformID.MacOSX, new Version(10, 0))); allMyObjects.Add(66);<br \/>\nallMyObjects.Add(3.14);<br \/>\n}<\/p>\n<p>In some cases, you will require an extremely flexible container that can hold literally anything (as shown here). However, most of the time you desire a type-safe container that can operate only on a particular type of data point. For example, you might need a container that can hold only database connections, bitmaps, or IPointy-compatible objects.<br \/>\nPrior to generics, the only way you could address this issue of type safety was to create a custom (strongly typed) collection class manually. Assume you want to create a custom collection that can contain only objects of type Person.<\/p>\n<p>namespace IssuesWithNonGenericCollections; public class Person<br \/>\n{<br \/>\npublic int Age {get; set;}<br \/>\npublic string FirstName {get; set;} public string LastName {get; set;}<\/p>\n<p>public Person(){}<br \/>\npublic Person(string firstName, string lastName, int age)<br \/>\n{<br \/>\nAge = age;<br \/>\nFirstName = firstName;<br \/>\nLastName = lastName;<br \/>\n}<\/p>\n<p>public override string ToString()<br \/>\n{<br \/>\nreturn $&quot;Name: {FirstName} {LastName}, Age: {Age}&quot;;<br \/>\n}<br \/>\n}<\/p>\n<p>To build a collection that can hold only Person objects, you could define a System.Collections. ArrayList member variable within a class named PersonCollection and configure all members to operate on strongly typed Person objects, rather than on System.Object types. Here is a simple example (a production-level custom collection could support many additional members and might extend an abstract base class from the System.Collections or System.Collections.Specialized namespace):<\/p>\n<p>using System.Collections;<br \/>\nnamespace IssuesWithNonGenericCollections; public class PersonCollection : IEnumerable<br \/>\n{<br \/>\nprivate ArrayList arPeople = new ArrayList();<\/p>\n<p>\/\/ Cast for caller.<br \/>\npublic Person GetPerson(int pos) =&gt; (Person)arPeople[pos];<\/p>\n<p>\/\/ Insert only Person objects.<br \/>\npublic void AddPerson(Person p)<br \/>\n{<br \/>\narPeople.Add(p);<br \/>\n}<\/p>\n<p>public void ClearPeople()<br \/>\n{<br \/>\narPeople.Clear();<br \/>\n}<\/p>\n<p>public int Count =&gt; arPeople.Count;<\/p>\n<p>\/\/ Foreach enumeration support.<br \/>\nIEnumerator IEnumerable.GetEnumerator() =&gt; arPeople.GetEnumerator();<br \/>\n}<\/p>\n<p>Notice that the PersonCollection class implements the IEnumerable interface, which allows a foreach- like iteration over each contained item. Also notice that your GetPerson() and AddPerson() methods have been prototyped to operate only on Person objects, not bitmaps, strings, database connections, nor other items. With these types defined, you are now assured of type safety, given that the C# compiler will be able<br \/>\nto determine any attempt to insert an incompatible data type. Update the using statements in Program.cs to the following and add the UserPersonCollection() method to the end of your current code:<\/p>\n<p>using System.Collections;<br \/>\nusing IssuesWithNonGenericCollections;<br \/>\n\/\/Top level statements in Program.cs static void UsePersonCollection()<br \/>\n{<br \/>\nConsole.WriteLine(&quot;<strong><strong><em> Custom Person Collection <\/em><\/strong><\/strong>\\n&quot;); PersonCollection myPeople = new PersonCollection(); myPeople.AddPerson(new Person(&quot;Homer&quot;, &quot;Simpson&quot;, 40)); myPeople.AddPerson(new Person(&quot;Marge&quot;, &quot;Simpson&quot;, 38)); myPeople.AddPerson(new Person(&quot;Lisa&quot;, &quot;Simpson&quot;, 9)); myPeople.AddPerson(new Person(&quot;Bart&quot;, &quot;Simpson&quot;, 7)); myPeople.AddPerson(new Person(&quot;Maggie&quot;, &quot;Simpson&quot;, 2));<\/p>\n<p>\/\/ This would be a compile-time error!<br \/>\n\/\/ myPeople.AddPerson(new Car());<\/p>\n<p>foreach (Person p in myPeople)<br \/>\n{<br \/>\nConsole.WriteLine(p);<br \/>\n}<br \/>\n}<\/p>\n<p>While custom collections do ensure type safety, this approach leaves you in a position where you must create an (almost identical) custom collection for each unique data type you want to contain. Thus, if you need a custom collection that can operate only on classes deriving from the Car base class, you need to build a highly similar collection class.<\/p>\n<p>using System.Collections;<br \/>\npublic class CarCollection : IEnumerable<br \/>\n{<br \/>\nprivate ArrayList arCars = new ArrayList();<\/p>\n<p>\/\/ Cast for caller.<br \/>\npublic Car GetCar(int pos) =&gt; (Car) arCars[pos];<\/p>\n<p>\/\/ Insert only Car objects. public void AddCar(Car c)<br \/>\n{<br \/>\narCars.Add(c);<br \/>\n}<\/p>\n<p>public void ClearCars()<br \/>\n{<br \/>\narCars.Clear();<br \/>\n}<\/p>\n<p>public int Count =&gt; arCars.Count;<\/p>\n<p>\/\/ Foreach enumeration support.<br \/>\nIEnumerator IEnumerable.GetEnumerator() =&gt; arCars.GetEnumerator();<br \/>\n}<\/p>\n<p>However, a custom collection class does nothing to solve the issue of boxing\/unboxing penalties.<br \/>\nEven if you were to create a custom collection named IntCollection that you designed to operate only on System.Int32 items, you would have to allocate some type of object to hold the data (e.g., System.Array and ArrayList).<\/p>\n<p>using System.Collections;<br \/>\npublic class IntCollection : IEnumerable<br \/>\n{<br \/>\nprivate ArrayList arInts = new ArrayList();<\/p>\n<p>\/\/ Get an int (performs unboxing!).<br \/>\npublic int GetInt(int pos) =&gt; (int)arInts[pos];<\/p>\n<p>\/\/ Insert an int (performs boxing)!<br \/>\npublic void AddInt(int i)<br \/>\n{<br \/>\narInts.Add(i);<br \/>\n}<\/p>\n<p>public void ClearInts()<br \/>\n{<br \/>\narInts.Clear();<br \/>\n}<\/p>\n<p>public int Count =&gt; arInts.Count;<\/p>\n<p>IEnumerator IEnumerable.GetEnumerator() =&gt; arInts.GetEnumerator();<br \/>\n}<\/p>\n<p>Regardless of which type you might choose to hold the integers, you cannot escape the boxing dilemma using nongeneric containers.<\/p>\n<p>A First Look at Generic CollectionsT<br \/>\nWhen you use generic collection classes, you rectify all the previous issues, including boxing\/unboxing penalties and a lack of type safety. Also, the need to build a custom (generic) collection class becomes quite rare. Rather than having to build unique classes that can contain people, cars, and integers, you can use a generic collection class and specify the type of type.<br \/>\nConsider the following method (added to the bottom of Program.cs), which uses the generic List<T> class (in the System.Collections.Generic namespace) to contain various types of data in a strongly typed manner (don\u2019t fret the details of generic syntax at this time):<\/p>\n<p>static void UseGenericList()<br \/>\n{<br \/>\nConsole.WriteLine(&quot;<strong><strong><em> Fun with Generics <\/em><\/strong><\/strong>\\n&quot;);<\/p>\n<p>\/\/ This List&lt;&gt; can hold only Person objects. List<Person> morePeople = new List<Person>(); morePeople.Add(new Person (&quot;Frank&quot;, &quot;Black&quot;, 50)); Console.WriteLine(morePeople[0]);<\/p>\n<p>\/\/ This List&lt;&gt; can hold only integers. List<int> moreInts = new List<int>(); moreInts.Add(10);<br \/>\nmoreInts.Add(2);<br \/>\nint sum = moreInts[0] + moreInts[1];<\/p>\n<p>\/\/ Compile-time error! Can't add Person object<br \/>\n\/\/ to a list of ints!<br \/>\n\/\/ moreInts.Add(new Person());<br \/>\n}<\/p>\n<p>The first List<T> object can contain only Person objects. Therefore, you do not need to perform a cast when plucking the items from the container, which makes this approach more type-safe. The second<br \/>\nList<T> can contain only integers, all of which are allocated on the stack; in other words, there is no hidden boxing or unboxing as you found with the nongeneric ArrayList. Here is a short list of the benefits generic containers provide over their nongeneric counterparts:<br \/>\n\u2022   Generics provide better performance because they do not result in boxing or unboxing penalties when storing value types.<br \/>\n\u2022   Generics are type-safe because they can contain only the type of type you specify.<br \/>\n\u2022   Generics greatly reduce the need to build custom collection types because you specify the \u201ctype of type\u201d when creating the generic container.<\/p>\n<p>The Role of Generic Type Parameters<br \/>\nYou can find generic classes, interfaces, structures, and delegates throughout the .NET Core base class libraries, and these might be part of any .NET Core namespace. Also be aware that generics have far more uses than simply defining a collection class. To be sure, you will see many different generics used in the remainder of this book for various reasons.<\/p>\n<p>\u25a0 Note  only classes, structures, interfaces, and delegates can be written generically; enum types cannot.<\/p>\n<p>When you see a generic item listed in the .NET Core documentation or the Visual Studio Object Browser, you will notice a pair of angled brackets with a letter or other token sandwiched within. Figure 10-1 shows the Visual Studio Object Browser displaying a number of generic items located within the System.<br \/>\nCollections.Generic namespace, including the highlighted List<T> class.<\/p>\n<p>Figure 10-1. Generic items supporting type parameters<\/p>\n<p>Formally speaking, you call these tokens type parameters; however, in more user-friendly terms, you can simply call them placeholders. You can read the symbol <T> as \u201cof T.\u201d Thus, you can read IEnumerable<T> as \u201cIEnumerable of T\u201d or, to say it another way, \u201cIEnumerable of type T.\u201d<\/p>\n<p>\u25a0 Note the name of a type parameter (placeholder) is irrelevant, and it is up to the developer who created the generic item. However, typically T is used to represent types, TKey or K is used for keys, and TValue or V is used for values.<\/p>\n<p>When you create a generic object, implement a generic interface, or invoke a generic member, it is up to you to supply a value to the type parameter. You\u2019ll see many examples in this chapter and throughout the remainder of the text. However, to set the stage, let\u2019s see the basics of interacting with generic types and members.<\/p>\n<p>Specifying Type Parameters for Generic Classes\/Structures<br \/>\nWhen you create an instance of a generic class or structure, you specify the type parameter when you declare the variable and when you invoke the constructor. As you saw in the preceding code example, UseGenericList() defined two List<T> objects.<\/p>\n<p>\/\/ This List&lt;&gt; can hold only Person objects. List<Person> morePeople = new List<Person>();<br \/>\n\/\/ This List&lt;&gt; can hold only integers. List<int> moreInts = new List<int>();<\/p>\n<p>You can read the first line in the preceding snippet as \u201ca List&lt;&gt; of T, where T is of type Person.\u201d Or, more simply, you can read it as \u201ca list of person objects.\u201d After you specify the type parameter of a generic item, it cannot be changed (remember, generics are all about type safety). When you specify a type parameter for a generic class or structure, all occurrences of the placeholder(s) are now replaced with your supplied value.<br \/>\nIf you were to view the full declaration of the generic List<T> class using the Visual Studio Object Browser, you would see that the placeholder T is used throughout the definition of the List<T> type. Here is a partial listing:<\/p>\n<p>\/\/ A partial listing of the List<T> class. namespace System.Collections.Generic;<br \/>\npublic class List<T> : IList<T>, IList, IReadOnlyList<T><br \/>\n{<br \/>\n...<br \/>\npublic void Add(T item);<br \/>\npublic void AddRange(IEnumerable<T> collection); public ReadOnlyCollection<T> AsReadOnly(); public int BinarySearch(T item);<br \/>\npublic bool Contains(T item); public void CopyTo(T[] array);<br \/>\npublic int FindIndex(System.Predicate<T> match); public T FindLast(System.Predicate<T> match); public bool Remove(T item);<br \/>\npublic int RemoveAll(System.Predicate<T> match); public T[] ToArray();<br \/>\npublic bool TrueForAll(System.Predicate<T> match); public T this[int index] { get; set; }<br \/>\n}<\/p>\n<p>When you create a List<T> specifying Person objects, it is as if the List<T> type were defined as follows:<\/p>\n<p>namespace System.Collections.Generic; public class List<Person><br \/>\n: IList<Person>, IList, IReadOnlyList<Person><br \/>\n{<br \/>\n...<br \/>\npublic void Add(Person item);<br \/>\npublic void AddRange(IEnumerable<Person> collection); public ReadOnlyCollection<Person> AsReadOnly(); public int BinarySearch(Person item);<br \/>\npublic bool Contains(Person item); public void CopyTo(Person[] array);<br \/>\npublic int FindIndex(System.Predicate<Person> match); public Person FindLast(System.Predicate<Person> match); public bool Remove(Person item);<br \/>\npublic int RemoveAll(System.Predicate<Person> match); public Person[] ToArray();<\/p>\n<p>public bool TrueForAll(System.Predicate<Person> match); public Person this[int index] { get; set; }<br \/>\n}<\/p>\n<p>Of course, when you create a generic List<T> variable, the compiler does not literally create a new implementation of the List<T> class. Rather, it will address only the members of the generic type you actually invoke.<\/p>\n<p>Specifying Type Parameters for Generic Members<br \/>\nIt is fine for a nongeneric class or structure to support generic properties. In these cases, you would also need to specify the placeholder value at the time you invoke the method. For example, System.Array supports several generic methods. Specifically, the nongeneric static Sort() method now has a generic counterpart named Sort<T>(). Consider the following code snippet, where T is of type int:<\/p>\n<p>int[] myInts = { 10, 4, 2, 33, 93 };<\/p>\n<p>\/\/ Specify the placeholder to the generic<br \/>\n\/\/ Sort&lt;&gt;() method.<br \/>\nArray.Sort<int>(myInts);<\/p>\n<p>foreach (int i in myInts)<br \/>\n{<br \/>\nConsole.WriteLine(i);<br \/>\n}<\/p>\n<p>Specifying Type Parameters for Generic Interfaces<br \/>\nIt is common to implement generic interfaces when you build classes or structures that need to support various framework behaviors (e.g., cloning, sorting, and enumeration). In Chapter 8, you learned about a number of nongeneric interfaces, such as IComparable, IEnumerable, IEnumerator, and IComparer. Recall that the nongeneric IComparable interface was defined like this:<\/p>\n<p>public interface IComparable<br \/>\n{<br \/>\nint CompareTo(object obj);<br \/>\n}<\/p>\n<p>In Chapter 8, you also implemented this interface on your Car class to enable sorting in a standard array. However, the code required several runtime checks and casting operations because the parameter was a general System.Object.<\/p>\n<p>public class Car : IComparable<br \/>\n{<br \/>\n...<br \/>\n\/\/ IComparable implementation.<br \/>\nint IComparable.CompareTo(object obj)<br \/>\n{<br \/>\nif (obj is Car temp)<\/p>\n<p>{<br \/>\nreturn this.CarID.CompareTo(temp.CarID);<br \/>\n}<br \/>\nthrow new ArgumentException(&quot;Parameter is not a Car!&quot;);<br \/>\n}<br \/>\n}<\/p>\n<p>Now assume you use the generic counterpart of this interface.<\/p>\n<p>public interface IComparable<T><br \/>\n{<br \/>\nint CompareTo(T obj);<br \/>\n}<\/p>\n<p>In this case, your implementation code will be cleaned up considerably.<\/p>\n<p>public class Car : IComparable<Car><br \/>\n{<br \/>\n...<br \/>\n\/\/ IComparable<T> implementation.<br \/>\nint IComparable<Car>.CompareTo(Car obj)<br \/>\n{<br \/>\nif (this.CarID &gt; obj.CarID)<br \/>\n{<br \/>\nreturn 1;<br \/>\n}<br \/>\nif (this.CarID &lt; obj.CarID)<br \/>\n{<br \/>\nreturn -1;<br \/>\n}<br \/>\nreturn 0;<br \/>\n}<br \/>\n}<\/p>\n<p>Here, you do not need to check whether the incoming parameter is a Car because it can only be a Car! If someone were to pass in an incompatible data type, you would get a compile-time error. Now that you have a better handle on how to interact with generic items, as well as the role of type parameters (aka placeholders), you\u2019re ready to examine the classes and interfaces of the System.Collections.Generic namespace.<\/p>\n<p>The System.Collections.Generic Namespace<br \/>\nWhen you are building a .NET Core application and need a way to manage in-memory data, the classes of System.Collections.Generic will most likely fit the bill. At the opening of this chapter, I briefly mentioned some of the core nongeneric interfaces implemented by the nongeneric collection classes. Not too surprisingly, the System.Collections.Generic namespace defines generic replacements for many of them.<br \/>\nIn fact, you can find a number of the generic interfaces that extend their nongeneric counterparts. This might seem odd; however, by doing so, implementing classes will also support the legacy functionally found in their nongeneric siblings. For example, IEnumerable<T> extends IEnumerable. Table 10-4 documents the core generic interfaces you\u2019ll encounter when working with the generic collection classes.<\/p>\n<p>Table 10-4. Key Interfaces Supported by Classes of System.Collections.Generic<\/p>\n<p>System.Collections.Generic<br \/>\nInterface   Meaning in Life<br \/>\nICollection<T>  Defines general characteristics (e.g., size, enumeration, and thread safety) for all generic collection types.<br \/>\nIComparer<T>    Defines a way to compare to objects.<br \/>\nIDictionary&lt;TKey, TValue&gt;   Allows a generic collection object to represent its contents using key- value pairs.<br \/>\nIEnumerable<T>\/ IAsyncEnumerable<T> Returns the IEnumerator<T> interface for a given object.<br \/>\nIAsyncEnumerable (new in C# 8.0) is covered in Chapter 15.<br \/>\nIEnumerator<T>  Enables foreach-style iteration over a generic collection.<br \/>\nIList<T>    Provides behavior to add, remove, and index items in a sequential list of objects.<br \/>\nISet<T> Provides the base interface for the abstraction of sets.<\/p>\n<p>The System.Collections.Generic namespace also defines several classes that implement many of these key interfaces. Table 10-5 describes some commonly used classes of this namespace, the interfaces they implement, and their basic functionality.<\/p>\n<p>Table 10-5. Classes of System.Collections.Generic<\/p>\n<p>Generic Class   Supported Key Interfaces    Meaning in Life<br \/>\nDictionary&lt;TKey, TValue&gt;    ICollection<T>, IDictionary&lt;TKey, TValue&gt;, IEnumerable<T>   This represents a generic collection of keys and values.<br \/>\nLinkedList<T>   ICollection<T>, IEnumerable<T>  This represents a doubly linked list.<br \/>\nList<T> ICollection<T>, IEnumerable<T>, IList<T>    This is a dynamically resizable sequential list of items.<br \/>\nQueue<T>    ICollection (not a typo; this is the nongeneric collection interface), IEnumerable<T>   This is a generic implementation of a first-in, first-out list.<br \/>\nSortedDictionary&lt;TKey, TValue&gt;  ICollection<T>, IDictionary&lt;TKey, TValue&gt;, IEnumerable<T>   This is a generic implementation of a sorted set of key-value pairs.<br \/>\nSortedSet<T>    ICollection<T>, IEnumerable<T>, ISet<T> This represents a collection of objects that is maintained in sorted order with no duplication.<br \/>\nStack<T>    ICollection (not a typo; this is the nongeneric collection interface), IEnumerable<T>   This is a generic implementation of a last-in, first-out list.<\/p>\n<p>The System.Collections.Generic namespace also defines many auxiliary classes and structures that work in conjunction with a specific container. For example, the LinkedListNode<T> type represents a node within a generic LinkedList<T>, the KeyNotFoundException exception is raised when attempting to grab an<\/p>\n<p>item from a container using a nonexistent key, and so forth. Be sure to consult the .NET Core documentation for full details of the System.Collections.Generic namespace.<br \/>\nIn any case, your next task is to learn how to use some of these generic collection classes. Before you do, however, allow me to illustrate a C# language feature (first introduced in .NET 3.5) that simplifies the way you populate generic (and nongeneric) collection containers with data.<\/p>\n<p>Understanding Collection Initialization Syntax<br \/>\nIn Chapter 4, you learned about object initialization syntax, which allows you to set properties on a new variable at the time of construction. Closely related to this is collection initialization syntax. This C# language feature makes it possible to populate many containers (such as ArrayList or List<T>) with items by using syntax similar to what you use to populate a basic array. Create a new .NET Core Console application named FunWithCollectionInitialization. Clear out the generated code in Program.cs and add the following using statements:<\/p>\n<p>using System.Collections; using System.Drawing;<\/p>\n<p>\u25a0 Note You can apply collection initialization syntax only to classes that support an Add() method, which is formalized by the ICollection<T>\/ICollection interfaces.<\/p>\n<p>Consider the following examples:<\/p>\n<p>\/\/ Init a standard array.<br \/>\nint[] myArrayOfInts = { 0, 1, 2, 3, 4, 5, 6, 7, 8, 9 };<\/p>\n<p>\/\/ Init a generic List&lt;&gt; of ints.<br \/>\nList<int> myGenericList = new List<int> { 0, 1, 2, 3, 4, 5, 6, 7, 8, 9 };<\/p>\n<p>\/\/ Init an ArrayList with numerical data.<br \/>\nArrayList myList = new ArrayList { 0, 1, 2, 3, 4, 5, 6, 7, 8, 9 };<\/p>\n<p>If your container is managing a collection of classes or a structure, you can blend object initialization syntax with collection initialization syntax to yield some functional code. You might recall the Point class from Chapter 5, which defined two properties named X and Y. If you wanted to build a generic List<T> of Point objects, you could write the following:<\/p>\n<p>List<Point> myListOfPoints = new List<Point><br \/>\n{<br \/>\nnew Point { X = 2, Y = 2 }, new Point { X = 3, Y = 3 }, new Point { X = 4, Y = 4 }<br \/>\n};<\/p>\n<p>foreach (var pt in myListOfPoints)<br \/>\n{<br \/>\nConsole.WriteLine(pt);<br \/>\n}<\/p>\n<p>Again, the benefit of this syntax is that you save yourself numerous keystrokes. While the nested curly brackets can become difficult to read if you don\u2019t mind your formatting, imagine the amount of code that would be required to fill the following List<T> of Rectangles if you did not have collection initialization syntax.<\/p>\n<p>List<Rectangle> myListOfRects = new List<Rectangle><br \/>\n{<br \/>\nnew Rectangle {<br \/>\nHeight = 90, Width = 90,<br \/>\nLocation = new Point { X = 10, Y = 10 }}, new Rectangle {<br \/>\nHeight = 50,Width = 50,<br \/>\nLocation = new Point { X = 2, Y = 2 }},<br \/>\n};<br \/>\nforeach (var r in myListOfRects)<br \/>\n{<br \/>\nConsole.WriteLine(r);<br \/>\n}<\/p>\n<p>Working with the List<T> Class<br \/>\nCreate a new Console Application project named FunWithGenericCollections. Add a new file, named<br \/>\nPerson.cs, and add the following code (which is the same code as the previous Person class):<\/p>\n<p>namespace FunWithGenericCollections; public class Person<br \/>\n{<br \/>\npublic int Age {get; set;}<br \/>\npublic string FirstName {get; set;} public string LastName {get; set;}<\/p>\n<p>public Person(){}<br \/>\npublic Person(string firstName, string lastName, int age)<br \/>\n{<br \/>\nAge = age;<br \/>\nFirstName = firstName;<br \/>\nLastName = lastName;<br \/>\n}<\/p>\n<p>public override string ToString()<br \/>\n{<br \/>\nreturn $&quot;Name: {FirstName} {LastName}, Age: {Age}&quot;;<br \/>\n}<br \/>\n}<\/p>\n<p>Clear out the generated code in Program.cs and add the following using statement:<\/p>\n<p>using FunWithGenericCollections;<\/p>\n<p>The first generic class you will examine is List<T>, which you\u2019ve already seen once or twice in this chapter. The List<T> class is bound to be your most frequently used type in the System.Collections. Generic namespace because it allows you to resize the contents of the container dynamically. To illustrate the basics of this type, ponder the following method in your Program.cs file, which leverages List<T> to manipulate the set of Person objects shown earlier in this chapter; you might recall that these Person objects defined three properties (Age, FirstName, and LastName) and a custom ToString() implementation:<\/p>\n<p>static void UseGenericList()<br \/>\n{<br \/>\n\/\/ Make a List of Person objects, filled with<br \/>\n\/\/ collection\/object init syntax. List<Person> people = new List<Person>()<br \/>\n{<br \/>\nnew Person {FirstName= &quot;Homer&quot;, LastName=&quot;Simpson&quot;, Age=47}, new Person {FirstName= &quot;Marge&quot;, LastName=&quot;Simpson&quot;, Age=45}, new Person {FirstName= &quot;Lisa&quot;, LastName=&quot;Simpson&quot;, Age=9}, new Person {FirstName= &quot;Bart&quot;, LastName=&quot;Simpson&quot;, Age=8}<br \/>\n};<\/p>\n<p>\/\/ Print out # of items in List. Console.WriteLine(&quot;Items in list: {0}&quot;, people.Count);<\/p>\n<p>\/\/ Enumerate over list. foreach (Person p in people)<br \/>\n{<br \/>\nConsole.WriteLine(p);<br \/>\n}<\/p>\n<p>\/\/ Insert a new person.<br \/>\nConsole.WriteLine(&quot;\\n-&gt;Inserting new person.&quot;);<br \/>\npeople.Insert(2, new Person { FirstName = &quot;Maggie&quot;, LastName = &quot;Simpson&quot;, Age = 2 }); Console.WriteLine(&quot;Items in list: {0}&quot;, people.Count);<\/p>\n<p>\/\/ Copy data into a new array.<br \/>\nPerson[] arrayOfPeople = people.ToArray(); foreach (Person p in arrayOfPeople)<br \/>\n{<br \/>\nConsole.WriteLine(&quot;First Names: {0}&quot;, p.FirstName);<br \/>\n}<br \/>\n}<\/p>\n<p>Here, you use collection initialization syntax to populate your List<T> with objects, as a shorthand notation for calling Add() multiple times. After you print out the number of items in the collection (as well as enumerate over each item), you invoke Insert(). As you can see, Insert() allows you to plug a new item into the List<T> at a specified index.<br \/>\nFinally, notice the call to the ToArray() method, which returns an array of Person objects based on the contents of the original List<T>. From this array, you loop over the items again using the array\u2019s indexer syntax. If you call this method from your top-level statements, you get the following output:<\/p>\n<p><strong><strong><em> Fun with Generic Collections <\/em><\/strong><\/strong> Items in list: 4<br \/>\nName: Homer Simpson, Age: 47 Name: Marge Simpson, Age: 45 Name: Lisa Simpson, Age: 9 Name: Bart Simpson, Age: 8<\/p>\n<p>-&gt;Inserting new person.<br \/>\nItems in list: 5<br \/>\nFirst Names: Homer First Names: Marge First Names: Maggie First Names: Lisa First Names: Bart<\/p>\n<p>The List<T> class defines many additional members of interest, so be sure to consult the documentation for more information. Next, let\u2019s look at a few more generic collections, specifically Stack<T>, Queue<T>, and SortedSet<T>. This should get you in a great position to understand your basic choices regarding how to hold your custom application data.<\/p>\n<p>Working with the Stack<T> Class<br \/>\nThe Stack<T> class represents a collection that maintains items using a last-in, first-out manner. As you might expect, Stack<T> defines members named Push() and Pop() to place items onto or remove items from the stack. The following method creates a stack of Person objects:<\/p>\n<p>static void UseGenericStack()<br \/>\n{<br \/>\nStack<Person> stackOfPeople = new();<br \/>\nstackOfPeople.Push(new Person { FirstName = &quot;Homer&quot;, LastName = &quot;Simpson&quot;, Age = 47 }); stackOfPeople.Push(new Person { FirstName = &quot;Marge&quot;, LastName = &quot;Simpson&quot;, Age = 45 }); stackOfPeople.Push(new Person { FirstName = &quot;Lisa&quot;, LastName = &quot;Simpson&quot;, Age = 9 });<\/p>\n<p>\/\/ Now look at the top item, pop it, and look again. Console.WriteLine(&quot;First person is: {0}&quot;, stackOfPeople.Peek()); Console.WriteLine(&quot;Popped off {0}&quot;, stackOfPeople.Pop()); Console.WriteLine(&quot;\\nFirst person is: {0}&quot;, stackOfPeople.Peek()); Console.WriteLine(&quot;Popped off {0}&quot;, stackOfPeople.Pop()); Console.WriteLine(&quot;\\nFirst person item is: {0}&quot;, stackOfPeople.Peek()); Console.WriteLine(&quot;Popped off {0}&quot;, stackOfPeople.Pop());<\/p>\n<p>try<br \/>\n{<br \/>\nConsole.WriteLine(&quot;\\nnFirst person is: {0}&quot;, stackOfPeople.Peek()); Console.WriteLine(&quot;Popped off {0}&quot;, stackOfPeople.Pop());<br \/>\n}<br \/>\ncatch (InvalidOperationException ex)<\/p>\n<p>{<br \/>\nConsole.WriteLine(&quot;\\nError! {0}&quot;, ex.Message);<br \/>\n}<br \/>\n}<\/p>\n<p>Here, you build a stack that contains three people, added in the order of their first names: Homer, Marge, and Lisa. As you peek into the stack, you will always see the object at the top first; therefore, the first call to Peek() reveals the third Person object. After a series of Pop() and Peek() calls, the stack eventually empties, at which time additional Peek() and Pop() calls raise a system exception. You can see the output for this here:<\/p>\n<p><strong><strong><em> Fun with Generic Collections <\/em><\/strong><\/strong> First person is: Name: Lisa Simpson, Age: 9 Popped off Name: Lisa Simpson, Age: 9<\/p>\n<p>First person is: Name: Marge Simpson, Age: 45 Popped off Name: Marge Simpson, Age: 45<\/p>\n<p>First person item is: Name: Homer Simpson, Age: 47 Popped off Name: Homer Simpson, Age: 47<\/p>\n<p>Error! Stack empty.<\/p>\n<p>Working with the Queue<T> Class<br \/>\nQueues are containers that ensure items are accessed in a first-in, first-out manner. Sadly, we humans are subject to queues all day long: lines at the bank, lines at the movie theater, and lines at the morning coffeehouse. When you need to model a scenario in which items are handled on a first-come, first-served<br \/>\nbasis, you will find the Queue<T> class fits the bill. In addition to the functionality provided by the supported interfaces, Queue defines the key members shown in Table 10-6.<\/p>\n<p>Table 10-6. Members of the Queue<T> Type<\/p>\n<p>Select Member of Queue<T>   Meaning in Life<br \/>\nDequeue()   Removes and returns the object at the beginning of the Queue<T><br \/>\nEnqueue()   Adds an object to the end of the Queue<T><br \/>\nPeek()  Returns the object at the beginning of the Queue<T> without removing it<\/p>\n<p>Now let\u2019s put these methods to work. You can begin by leveraging your Person class again and building a Queue<T> object that simulates a line of people waiting to order coffee.<\/p>\n<p>static void UseGenericQueue()<br \/>\n{<br \/>\n\/\/ Make a Q with three people. Queue<Person> peopleQ = new();<br \/>\npeopleQ.Enqueue(new Person {FirstName= &quot;Homer&quot;, LastName=&quot;Simpson&quot;, Age=47});<\/p>\n<p>peopleQ.Enqueue(new Person {FirstName= &quot;Marge&quot;, LastName=&quot;Simpson&quot;, Age=45}); peopleQ.Enqueue(new Person {FirstName= &quot;Lisa&quot;, LastName=&quot;Simpson&quot;, Age=9});<\/p>\n<p>\/\/ Peek at first person in Q.<br \/>\nConsole.WriteLine(&quot;{0} is first in line!&quot;, peopleQ.Peek().FirstName);<\/p>\n<p>\/\/ Remove each person from Q. GetCoffee(peopleQ.Dequeue()); GetCoffee(peopleQ.Dequeue()); GetCoffee(peopleQ.Dequeue());<br \/>\n\/\/ Try to de-Q again? try<br \/>\n{<br \/>\nGetCoffee(peopleQ.Dequeue());<br \/>\n}<br \/>\ncatch(InvalidOperationException e)<br \/>\n{<br \/>\nConsole.WriteLine(&quot;Error! {0}&quot;, e.Message);<br \/>\n}<br \/>\n\/\/Local helper function<br \/>\nstatic void GetCoffee(Person p)<br \/>\n{<br \/>\nConsole.WriteLine(&quot;{0} got coffee!&quot;, p.FirstName);<br \/>\n}<br \/>\n}<\/p>\n<p>Here, you insert three items into the Queue<T> class using its Enqueue() method. The call to Peek() allows you to view (but not remove) the first item currently in the Queue. Finally, the call to Dequeue() removes the item from the line and sends it into the GetCoffee() helper function for processing. Note that if you attempt to remove items from an empty queue, a runtime exception is thrown. Here is the output you receive when calling this method:<\/p>\n<p><strong><strong><em> Fun with Generic Collections <\/em><\/strong><\/strong> Homer is first in line!<br \/>\nHomer got coffee!<br \/>\nMarge got coffee!<br \/>\nLisa got coffee!<br \/>\nError! Queue empty.<\/p>\n<p>Working with the PriorityQueue&lt;TElement, TPriority&gt; Class (New 10)<br \/>\nIntroduced in .NET 6\/C# 10, the PriorityQueue works just like the Queue<T> except that each queued item is given a priority. When items are dequeued, they are removed from lowest to highest priority. The following updates the previous Queue example to use a PriorityQueue:<\/p>\n<p>static void UsePriorityQueue()<br \/>\n{<br \/>\nConsole.WriteLine(&quot;<em> Fun with Generic Priority Queues <\/em>\\n&quot;);<\/p>\n<p>PriorityQueue&lt;Person, int&gt; peopleQ = new();<br \/>\npeopleQ.Enqueue(new Person { FirstName = &quot;Lisa&quot;, LastName = &quot;Simpson&quot;, Age = 9 }, 1); peopleQ.Enqueue(new Person { FirstName = &quot;Homer&quot;, LastName = &quot;Simpson&quot;, Age = 47 }, 3); peopleQ.Enqueue(new Person { FirstName = &quot;Marge&quot;, LastName = &quot;Simpson&quot;, Age = 45 }, 3); peopleQ.Enqueue(new Person { FirstName = &quot;Bart&quot;, LastName = &quot;Simpson&quot;, Age = 12 }, 2);<\/p>\n<p>while (peopleQ.Count &gt; 0)<br \/>\n{<br \/>\nConsole.WriteLine(peopleQ.Dequeue().FirstName); \/\/Displays Lisa Console.WriteLine(peopleQ.Dequeue().FirstName); \/\/Displays Bart Console.WriteLine(peopleQ.Dequeue().FirstName); \/\/Displays either Marge or Homer Console.WriteLine(peopleQ.Dequeue().FirstName); \/\/Displays the other priority 3 item<br \/>\n}<br \/>\n}<\/p>\n<p>If more than one item is set to the current lowest priority, the order of dequeuing is not guaranteed. As shown in code sample, the third call to Dequeue() will return either Homer or Marge, as they are both set to a priority of three. The fourth call will then return the other person. If exact order matters, you must ensure values for each priority are unique.<\/p>\n<p>Working with the SortedSet<T> Class<br \/>\nThe SortedSet<T> class is useful because it automatically ensures that the items in the set are sorted when you insert or remove items. However, you do need to inform the SortedSet<T> class exactly how you<br \/>\nwant it to sort the objects, by passing in as a constructor argument an object that implements the generic<br \/>\nIComparer<T> interface.<br \/>\nBegin by creating a new class named SortPeopleByAge, which implements IComparer<T>, where T is of type Person. Recall that this interface defines a single method named Compare(), where you can author whatever logic you require for the comparison. Here is a simple implementation of this class:<\/p>\n<p>namespace FunWithGenericCollections; class SortPeopleByAge : IComparer<Person><br \/>\n{<br \/>\npublic int Compare(Person firstPerson, Person secondPerson)<br \/>\n{<br \/>\nif (firstPerson?.Age &gt; secondPerson?.Age)<br \/>\n{<br \/>\nreturn 1;<br \/>\n}<br \/>\nif (firstPerson?.Age &lt; secondPerson?.Age)<br \/>\n{<br \/>\nreturn -1;<br \/>\n}<br \/>\nreturn 0;<br \/>\n}<br \/>\n}<\/p>\n<p>Now add the following new method that demonstrates using SortedSet<Person>:<\/p>\n<p>static void UseSortedSet()<br \/>\n{<br \/>\n\/\/ Make some people with different ages.<br \/>\nSortedSet<Person> setOfPeople = new SortedSet<Person>(new SortPeopleByAge())<br \/>\n{<br \/>\nnew Person {FirstName= &quot;Homer&quot;, LastName=&quot;Simpson&quot;, Age=47}, new Person {FirstName= &quot;Marge&quot;, LastName=&quot;Simpson&quot;, Age=45}, new Person {FirstName= &quot;Lisa&quot;, LastName=&quot;Simpson&quot;, Age=9}, new Person {FirstName= &quot;Bart&quot;, LastName=&quot;Simpson&quot;, Age=8}<br \/>\n};<\/p>\n<p>\/\/ Note the items are sorted by age!<br \/>\nforeach (Person p in setOfPeople)<br \/>\n{<br \/>\nConsole.WriteLine(p);<br \/>\n}<br \/>\nConsole.WriteLine();<\/p>\n<p>\/\/ Add a few new people, with various ages.<br \/>\nsetOfPeople.Add(new Person { FirstName = &quot;Saku&quot;, LastName = &quot;Jones&quot;, Age = 1 }); setOfPeople.Add(new Person { FirstName = &quot;Mikko&quot;, LastName = &quot;Jones&quot;, Age = 32 });<\/p>\n<p>\/\/ Still sorted by age!<br \/>\nforeach (Person p in setOfPeople)<br \/>\n{<br \/>\nConsole.WriteLine(p);<br \/>\n}<br \/>\n}<\/p>\n<p>When you run your application, the listing of objects is now always ordered based on the value of the<br \/>\nAge property, regardless of the order you inserted or removed objects.<\/p>\n<p><strong><strong><em> Fun with Generic Collections <\/em><\/strong><\/strong> Name: Bart Simpson, Age: 8<br \/>\nName: Lisa Simpson, Age: 9 Name: Marge Simpson, Age: 45 Name: Homer Simpson, Age: 47<\/p>\n<p>Name: Saku Jones, Age: 1 Name: Bart Simpson, Age: 8 Name: Lisa Simpson, Age: 9 Name: Mikko Jones, Age: 32 Name: Marge Simpson, Age: 45 Name: Homer Simpson, Age: 47<\/p>\n<p>Working with the Dictionary&lt;TKey, TValue&gt; Class<br \/>\nAnother handy generic collection is the Dictionary&lt;TKey,TValue&gt; type, which allows you to hold any number of objects that may be referred to via a unique key. Thus, rather than obtaining an item from a List<T> using a numerical identifier (e.g., \u201cGive me the second object\u201d), you could use the unique text key (e.g., \u201cGive me the object I keyed as Homer\u201d).<br \/>\nLike other collection objects, you can populate a Dictionary&lt;TKey,TValue&gt; by calling the generic Add() method manually. However, you can also fill a Dictionary&lt;TKey,TValue&gt; using collection initialization syntax. Do be aware that when you are populating this collection object, key names must be unique. If you mistakenly specify the same key multiple times, you will receive a runtime exception.<br \/>\nConsider the following method that fills a Dictionary&lt;K,V&gt; with various objects. Notice when you create the Dictionary&lt;TKey,TValue&gt; object, you specify the key type (TKey) and underlying object type (TValue) as constructor arguments. In this example, you are using a string data type as the key and<br \/>\na Person type as the value. Also note that you can combine object initialization syntax with collection initialization syntax.<\/p>\n<p>private static void UseDictionary()<br \/>\n{<br \/>\n\/\/ Populate using Add() method<br \/>\nDictionary&lt;string, Person&gt; peopleA = new Dictionary&lt;string, Person&gt;(); peopleA.Add(&quot;Homer&quot;, new Person { FirstName = &quot;Homer&quot;, LastName = &quot;Simpson&quot;, Age<br \/>\n= 47 });<br \/>\npeopleA.Add(&quot;Marge&quot;, new Person { FirstName = &quot;Marge&quot;, LastName = &quot;Simpson&quot;, Age<br \/>\n= 45 });<br \/>\npeopleA.Add(&quot;Lisa&quot;, new Person { FirstName = &quot;Lisa&quot;, LastName = &quot;Simpson&quot;, Age = 9 });<\/p>\n<p>\/\/ Get Homer.<br \/>\nPerson homer = peopleA[&quot;Homer&quot;]; Console.WriteLine(homer);<\/p>\n<p>\/\/ Populate with initialization syntax.<br \/>\nDictionary&lt;string, Person&gt; peopleB = new Dictionary&lt;string, Person&gt;()<br \/>\n{<br \/>\n{ &quot;Homer&quot;, new Person { FirstName = &quot;Homer&quot;, LastName = &quot;Simpson&quot;, Age = 47 } },<br \/>\n{ &quot;Marge&quot;, new Person { FirstName = &quot;Marge&quot;, LastName = &quot;Simpson&quot;, Age = 45 } },<br \/>\n{ &quot;Lisa&quot;, new Person { FirstName = &quot;Lisa&quot;, LastName = &quot;Simpson&quot;, Age = 9 } }<br \/>\n};<\/p>\n<p>\/\/ Get Lisa.<br \/>\nPerson lisa = peopleB[&quot;Lisa&quot;]; Console.WriteLine(lisa);<br \/>\n}<\/p>\n<p>It is also possible to populate a Dictionary&lt;TKey,TValue&gt; using a related initialization syntax that is specific to this type of container (not surprisingly termed dictionary initialization). Similar to the syntax used to populate the personB object in the previous code example, you still define an initialization scope for the collection object; however, you can use the indexer to specify the key and assign this to a new object as so:<\/p>\n<p>\/\/ Populate with dictionary initialization syntax.<br \/>\nDictionary&lt;string, Person&gt; peopleC = new Dictionary&lt;string, Person&gt;()<br \/>\n{<br \/>\n[&quot;Homer&quot;] = new Person { FirstName = &quot;Homer&quot;, LastName = &quot;Simpson&quot;, Age = 47 }, [&quot;Marge&quot;] = new Person { FirstName = &quot;Marge&quot;, LastName = &quot;Simpson&quot;, Age = 45 }, [&quot;Lisa&quot;] = new Person { FirstName = &quot;Lisa&quot;, LastName = &quot;Simpson&quot;, Age = 9 }<br \/>\n};<\/p>\n<p>The System.Collections.ObjectModel Namespace<br \/>\nNow that you understand how to work with the major generic classes, we will briefly examine an additional collection-centric namespace, System.Collections.ObjectModel. This is a relatively small namespace, which contains a handful of classes. Table 10-7 documents the two classes that you should most certainly be aware of.<\/p>\n<p>Table 10-7. Useful Members of System.Collections.ObjectModel<\/p>\n<p>System.Collections.<br \/>\nObjectModel Type    Meaning in Life<br \/>\nObservableCollection<T> Represents a dynamic data collection that provides notifications when items get added, when items get removed, or when the whole list is refreshed<br \/>\nReadOnlyObservable Collection<T>    Represents a read-only version of ObservableCollection<T><\/p>\n<p>The ObservableCollection<T> class is useful, in that it has the ability to inform external objects when its contents have changed in some way (as you might guess, working with ReadOnlyObservableCollection<\/p>\n<p><T> is similar but read-only in nature).<\/p>\n<p>Working with ObservableCollection<T><br \/>\nCreate a new Console Application project named FunWithObservableCollections and import the System.Collections.ObjectModel namespace into your initial C# code file. In many ways, working with ObservableCollection<T> is identical to working with List<T>, given that both of these classes implement the same core interfaces. What makes the ObservableCollection<T> class unique is that this class supports an event named CollectionChanged. This event will fire whenever a new item is inserted, a current item is removed (or relocated), or the entire collection is modified.<br \/>\nLike any event, CollectionChanged is defined in terms of a delegate, which in this case is NotifyCollectionChangedEventHandler. This delegate can call any method that takes an object as the first parameter and takes a NotifyCollectionChangedEventArgs as the second. Consider the following code, which populates an observable collection containing Person objects and wires up the CollectionChanged event:<\/p>\n<p>using System.Collections.ObjectModel; using System.Collections.Specialized; using FunWithObservableCollections;<\/p>\n<p>\/\/ Make a collection to observe<br \/>\n\/\/and add a few Person objects.<br \/>\nObservableCollection<Person> people = new ObservableCollection<Person>()<br \/>\n{<br \/>\nnew Person{ FirstName = \"Peter\", LastName = \"Murphy\", Age = 52 }, new Person{ FirstName = \"Kevin\", LastName = \"Key\", Age = 48 },<br \/>\n};<\/p>\n<p>\/\/ Wire up the CollectionChanged event. people.CollectionChanged += people_CollectionChanged;<\/p>\n<p>static void people_CollectionChanged(object sender, System.Collections.Specialized.NotifyCollectionChangedEventArgs e)<br \/>\n{<br \/>\nthrow new NotImplementedException();<br \/>\n}<\/p>\n<p>The incoming NotifyCollectionChangedEventArgs parameter defines two important properties, OldItems and NewItems, which will give you a list of items that were currently in the collection before the event fired and the new items that were involved in the change. However, you will want to examine these lists only under the correct circumstances. Recall that the CollectionChanged event can fire when items are added, removed, relocated, or reset. To discover which of these actions triggered the event, you can use the Action property of NotifyCollectionChangedEventArgs. The Action property can be tested against any of the following members of the NotifyCollectionChangedAction enumeration:<\/p>\n<p>public enum NotifyCollectionChangedAction<br \/>\n{<br \/>\nAdd = 0,<br \/>\nRemove = 1,<br \/>\nReplace = 2,<br \/>\nMove = 3,<br \/>\nReset = 4,<br \/>\n}<\/p>\n<p>Here is an implementation of the CollectionChanged event handler that will traverse the old and new sets when an item has been inserted into or removed from the collection at hand (notice the using for System.Collections.Specialized):<\/p>\n<p>using System.Collections.Specialized;<br \/>\n...<br \/>\nstatic void people_CollectionChanged(object sender, NotifyCollectionChangedEventArgs e)<br \/>\n{<br \/>\n\/\/ What was the action that caused the event? Console.WriteLine(\"Action for this event: {0}\", e.Action);<\/p>\n<p>\/\/ They removed something.<br \/>\nif (e.Action == NotifyCollectionChangedAction.Remove)<br \/>\n{<br \/>\nConsole.WriteLine(\"Here are the OLD items:\"); foreach (Person p in e.OldItems)<\/p>\n<p>{<br \/>\nConsole.WriteLine(p.ToString());<br \/>\n}<br \/>\nConsole.WriteLine();<br \/>\n}<\/p>\n<p>\/\/ They added something.<br \/>\nif (e.Action == NotifyCollectionChangedAction.Add)<br \/>\n{<br \/>\n\/\/ Now show the NEW items that were inserted. Console.WriteLine(\"Here are the NEW items:\"); foreach (Person p in e.NewItems)<br \/>\n{<br \/>\nConsole.WriteLine(p.ToString());<br \/>\n}<br \/>\n}<br \/>\n}<\/p>\n<p>Now, update your calling code to add and remove an item.<\/p>\n<p>\/\/ Now add a new item.<br \/>\npeople.Add(new Person(\"Fred\", \"Smith\", 32));<br \/>\n\/\/ Remove an item. people.RemoveAt(0);<\/p>\n<p>When you run the program, you will see output similar to the following:<\/p>\n<p>Action for this event: Add Here are the NEW items:<br \/>\nName: Fred Smith, Age: 32<\/p>\n<p>Action for this event: Remove Here are the OLD items:<br \/>\nName: Peter Murphy, Age: 52<\/p>\n<p>That wraps up the examination of the various collection-centric namespaces. To conclude the chapter, you will now examine how you can build your own custom generic methods and custom generic types.<\/p>\n<p>Creating Custom Generic Methods<br \/>\nWhile most developers typically use the existing generic types within the base class libraries, it is also possible to build your own generic members and custom generic types. Let\u2019s look at how to incorporate custom generics into your own projects. The first step is to build a generic swap method. Begin by creating a new console application named CustomGenericMethods.<br \/>\nWhen you build custom generic methods, you achieve a supercharged version of traditional method overloading. In Chapter 2, you learned that overloading is the act of defining multiple versions of a single method, which differ by the number of, or type of, parameters.<\/p>\n<p>While overloading is a useful feature in an object-oriented language, one problem is that you can easily end up with a ton of methods that essentially do the same thing. For example, assume you need to build some methods that can switch two pieces of data using a simple swap routine. You might begin by authoring a new static class with a method that can operate on integers, like this:<\/p>\n<p>namespace CustomGenericMethods; static class SwapFunctions<br \/>\n{<br \/>\n\/\/ Swap two integers.<br \/>\nstatic void Swap(ref int a, ref int b)<br \/>\n{<br \/>\nint temp = a; a = b;<br \/>\nb = temp;<br \/>\n}<br \/>\n}<\/p>\n<p>So far, so good. But now assume you also need to swap two Person objects; this would require authoring a new version of Swap().<\/p>\n<p>\/\/ Swap two Person objects.<br \/>\nstatic void Swap(ref Person a, ref Person b)<br \/>\n{<br \/>\nPerson temp = a; a = b;<br \/>\nb = temp;<br \/>\n}<\/p>\n<p>No doubt, you can see where this is going. If you also needed to swap floating-point numbers, bitmaps, cars, buttons, etc., you would have to build even more methods, which would become a maintenance nightmare. You could build a single (nongeneric) method that operated on object parameters, but then you face all the issues you examined earlier in this chapter, including boxing, unboxing, a lack of type safety, explicit casting, and so on.<br \/>\nWhenever you have a group of overloaded methods that differ only by incoming arguments, this is your clue that generics could make your life easier. Consider the following generic Swap<T>() method that can swap any two Ts:<\/p>\n<p>\/\/ This method will swap any two items.<br \/>\n\/\/ as specified by the type parameter <T>. static void Swap<T>(ref T a, ref T b)<br \/>\n{<br \/>\nConsole.WriteLine(\"You sent the Swap() method a {0}\", typeof(T)); T temp = a;<br \/>\na = b;<br \/>\nb = temp;<br \/>\n}<\/p>\n<p>Notice how a generic method is defined by specifying the type parameters after the method name but before the parameter list. Here, you state that the Swap<T>() method can operate on any two parameters of type <T>. To spice things up a bit, you also print out the type name of the supplied placeholder to the console using C#\u2019s typeof() operator. Now consider the following calling code, which swaps integers and strings:<\/p>\n<p>Console.WriteLine(\"***** Fun with Custom Generic Methods *****\\n\");<\/p>\n<p>\/\/ Swap 2 ints.<br \/>\nint a = 10, b = 90;<br \/>\nConsole.WriteLine(\"Before swap: {0}, {1}\", a, b); SwapFunctions.Swap<int>(ref a, ref b); Console.WriteLine(\"After swap: {0}, {1}\", a, b); Console.WriteLine();<\/p>\n<p>\/\/ Swap 2 strings.<br \/>\nstring s1 = \"Hello\", s2 = \"There\"; Console.WriteLine(\"Before swap: {0} {1}!\", s1, s2); SwapFunctions.Swap<string>(ref s1, ref s2); Console.WriteLine(\"After swap: {0} {1}!\", s1, s2);<\/p>\n<p>Console.ReadLine();<\/p>\n<p>The output looks like this:<\/p>\n<p>***** Fun with Custom Generic Methods ***** Before swap: 10, 90<br \/>\nYou sent the Swap() method a System.Int32 After swap: 90, 10<\/p>\n<p>Before swap: Hello There!<br \/>\nYou sent the Swap() method a System.String After swap: There Hello!<\/p>\n<p>The major benefit of this approach is that you have only one version of Swap<T>() to maintain, yet it can operate on any two items of a given type in a type-safe manner. Better yet, stack-based items stay on the stack, while heap-based items stay on the heap!<\/p>\n<p>Inference of Type Parameters<br \/>\nWhen you invoke generic methods such as Swap<T>, you can optionally omit the type parameter if (and only if ) the generic method requires arguments because the compiler can infer the type parameter based on the member parameters. For example, you could swap two System.Boolean values by adding the following code to your top-level statements:<\/p>\n<p>\/\/ Compiler will infer System.Boolean. bool b1 = true, b2 = false;<br \/>\nConsole.WriteLine(\"Before swap: {0}, {1}\", b1, b2); SwapFunctions.Swap(ref b1, ref b2); Console.WriteLine(\"After swap: {0}, {1}\", b1, b2);<\/p>\n<p>Even though the compiler can discover the correct type parameter based on the data type used to declare b1 and b2, you should get in the habit of always specifying the type parameter explicitly.<\/p>\n<p>SwapFunctions.Swap<bool>(ref b1, ref b2);<\/p>\n<p>This makes it clear to your fellow programmers that this method is indeed generic. Moreover, inference of type parameters works only if the generic method has at least one parameter. For example, assume you have the following generic method in your Program.cs file:<\/p>\n<p>static void DisplayBaseClass<T>()<br \/>\n{<br \/>\n\/\/ BaseType is a method used in reflection,<br \/>\n\/\/ which will be examined in Chapter 17<br \/>\nConsole.WriteLine(\"Base class of {0} is: {1}.\", typeof(T), typeof(T).BaseType);<br \/>\n}<\/p>\n<p>In this case, you must supply the type parameter upon invocation.<\/p>\n<p>...<br \/>\n\/\/ Must supply type parameter if<br \/>\n\/\/ the method does not take params. DisplayBaseClass<int>(); DisplayBaseClass<string>();<\/p>\n<p>\/\/ Compiler error! No params? Must supply placeholder!<br \/>\n\/\/ DisplayBaseClass();<br \/>\nConsole.ReadLine();<\/p>\n<p>Of course, generic methods do not need to be static as they are in these examples. All rules and options for nongeneric methods also apply.<\/p>\n<p>Creating Custom Generic Structures and Classes<br \/>\nNow that you understand how to define and invoke generic methods, it\u2019s time to turn your attention to the construction of a generic structure (the process of building a generic class is identical) within a new Console Application project named GenericPoint. Assume you have built a generic Point structure that supports a single type parameter that represents the underlying storage for the (x, y) coordinates. The caller can then create Point<T> types as follows:<\/p>\n<p>\/\/ Point using ints.<br \/>\nPoint<int> p = new Point<int>(10, 10);<\/p>\n<p>\/\/ Point using double.<br \/>\nPoint<double> p2 = new Point<double>(5.4, 3.3);<\/p>\n<p>\/\/ Point using strings.<br \/>\nPoint<string> p3 = new Point<string>(\"\"\",\"\"3\"\");<\/p>\n<p>Creating a point using strings might seem a bit odd at first, but consider the case of imaginary numbers. Then it might make sense to use strings for the values of X and Y of a point. Regardless, it demonstrates the power of generics. Here is the complete definition of Point<T>:<\/p>\n<p>namespace GenericPoint;<br \/>\n\/\/ A generic Point structure. public struct Point<T><\/p>\n<p>{<br \/>\n\/\/ Generic state data. private T _xPos; private T _yPos;<\/p>\n<p>\/\/ Generic constructor. public Point(T xVal, T yVal)<br \/>\n{<br \/>\n_xPos = xVal;<br \/>\n_yPos = yVal;<br \/>\n}<\/p>\n<p>\/\/ Generic properties. public T X<br \/>\n{<br \/>\nget => _xPos;<br \/>\nset => _xPos = value;<br \/>\n}<\/p>\n<p>public T Y<br \/>\n{<br \/>\nget => _yPos;<br \/>\nset => _yPos = value;<br \/>\n}<\/p>\n<p>public override string ToString() => $\"[{_xPos}, {_yPos}]\";<br \/>\n}<\/p>\n<p>As you can see, Point<T> leverages its type parameter in the definition of the field data, constructor arguments, and property definitions.<\/p>\n<p>Default Value Expressions with Generics<br \/>\nWith the introduction of generics, the C# default keyword has been given a dual identity. In addition to its use within a switch construct, it can be used to set a type parameter to its default value. This is helpful<br \/>\nbecause a generic type does not know the actual placeholders up front, which means it cannot safely assume what the default value will be. The defaults for a type parameter are as follows:<br \/>\n\u2022   Numeric values have a default value of 0.<br \/>\n\u2022   Reference types have a default value of null.<br \/>\n\u2022   Fields of a structure are set to 0 (for value types) or null (for reference types).<br \/>\nTo reset an instance of Point<T>, you could set the X and Y values to 0 directly. This assumes the caller will supply only numerical data. What about the string version? This is where the default(T) syntax comes in handy. The default keyword resets a variable to the default value for the variable\u2019s data type. Add a method called ResetPoint() as follows:<\/p>\n<p>\/\/ Reset fields to the default value of the type parameter.<br \/>\n\/\/ The \"default\" keyword is overloaded in C#.<br \/>\n\/\/ When used with generics, it represents the default<\/p>\n<p>\/\/ value of a type parameter. public void ResetPoint()<br \/>\n{<br \/>\n_xPos = default(T);<br \/>\n_yPos = default(T);<br \/>\n}<\/p>\n<p>Now that you have the ResetPoint() method in place, you can fully exercise the methods of<br \/>\nPoint<T> struct. using GenericPoint;<br \/>\nConsole.WriteLine(\"***** Fun with Generic Structures *****\\n\");<br \/>\n\/\/ Point using ints.<br \/>\nPoint<int> p = new Point<int>(10, 10); Console.WriteLine(\"p.ToString()={0}\", p.ToString()); p.ResetPoint(); Console.WriteLine(\"p.ToString()={0}\", p.ToString()); Console.WriteLine();<\/p>\n<p>\/\/ Point using double.<br \/>\nPoint<double> p2 = new Point<double>(5.4, 3.3); Console.WriteLine(\"p2.ToString()={0}\", p2.ToString()); p2.ResetPoint(); Console.WriteLine(\"p2.ToString()={0}\", p2.ToString()); Console.WriteLine();<\/p>\n<p>\/\/ Point using strings.<br \/>\nPoint<string> p3 = new Point<string>(\"i\", \"3i\"); Console.WriteLine(\"p3.ToString()={0}\", p3.ToString()); p3.ResetPoint(); Console.WriteLine(\"p3.ToString()={0}\", p3.ToString()); Console.ReadLine();<\/p>\n<p>Here is the output:<\/p>\n<p>***** Fun with Generic Structures ***** p.ToString()=[10, 10]<br \/>\np.ToString()=[0, 0]<\/p>\n<p>p2.ToString()=[5.4, 3.3]<br \/>\np2.ToString()=[0, 0]<\/p>\n<p>p3.ToString()=[i, 3i] p3.ToString()=[, ]<\/p>\n<p>Default Literal Expressions (New 7.1)<br \/>\nIn addition to setting the default value of a property, C# 7.1 introduced default literal expressions. This eliminates the need for specifying the type of the variable in the default statement. Update the ResetPoint() method to the following:<\/p>\n<p>public void ResetPoint()<br \/>\n{<br \/>\n_xPos = default;<br \/>\n_yPos = default;<br \/>\n}<\/p>\n<p>The default expression isn\u2019t limited to simple variables but can also be applied to complex types. For example, to create and initialize the Point structure, you can write the following:<\/p>\n<p>Point<string> p4 = default; Console.WriteLine(\"p4.ToString()={0}\", p4.ToString()); Console.WriteLine();<br \/>\nPoint<int> p5 = default; Console.WriteLine(\"p5.ToString()={0}\", p5.ToString());<\/p>\n<p>Pattern Matching with Generics (New 7.1)<br \/>\nAnother update in C# 7.1 is the ability to pattern match on generics. Take the following method, which checks the Point instance for the data type that it is based on (arguably incomplete, but enough to show the concept):<\/p>\n<p>static void PatternMatching<T>(Point<T> p)<br \/>\n{<br \/>\nswitch (p)<br \/>\n{<br \/>\ncase Point<string> pString: Console.WriteLine(\"Point is based on strings\"); return;<br \/>\ncase Point<int> pInt:<br \/>\nConsole.WriteLine(\"Point is based on ints\"); return;<br \/>\n}<br \/>\n}<\/p>\n<p>To exercise the pattern matching code, update the top-level statements to the following:<\/p>\n<p>Point<string> p4 = default; Point<int> p5 = default; PatternMatching(p4); PatternMatching(p5);<\/p>\n<p>Constraining Type Parameters<br \/>\nAs this chapter illustrates, any generic item has at least one type parameter that you need to specify at the time you interact with the generic type or member. This alone allows you to build some type-safe code; however, you can also use the where keyword to get extremely specific about what a given type parameter must look like.<br \/>\nUsing this keyword, you can add a set of constraints to a given type parameter, which the C# compiler will check at compile time. Specifically, you can constrain a type parameter as described in Table 10-8.<\/p>\n<p>Table 10-8. Possible Constraints for Generic Type Parameters<\/p>\n<p>Generic Constraint  Meaning in Life<br \/>\nwhere T : struct    The type parameter <T> must have System.ValueType in its chain of inheritance (i.e.,<br \/>\n<T> must be a structure).<br \/>\nwhere T : class The type parameter <T> must not have System.ValueType in its chain of inheritance (i.e., <T> must be a reference type).<br \/>\nwhere T : new() The type parameter <T> must have a default constructor. This is helpful if your generic type must create an instance of the type parameter because you cannot assume you know the format of custom constructors. Note that this constraint must be listed last on a multiconstrained type.<br \/>\nwhere T : NameOfBaseClass   The type parameter <T> must be derived from the class specified by<br \/>\nNameOfBaseClass.<br \/>\nwhere T : NameOfInterface   The type parameter <T> must implement the interface specified by<br \/>\nNameOfInterface. You can separate multiple interfaces as a comma-delimited list.<\/p>\n<p>Unless you need to build some extremely type-safe custom collections, you might never need to use the where keyword in your C# projects. Regardless, the following handful of (partial) code examples illustrate how to work with the where keyword.<\/p>\n<p>Examples of Using the where Keyword<br \/>\nBegin by assuming that you have created a custom generic class, and you want to ensure that the type parameter has a default constructor. This could be useful when the custom generic class needs to create instances of the T because the default constructor is the only constructor that is potentially common to all types. Also, constraining T in this way lets you get compile-time checking; if T is a reference type, the programmer remembered to redefine the default in the class definition (you might recall that the default constructor is removed in classes when you define your own).<\/p>\n<p>\/\/ MyGenericClass derives from object, while<br \/>\n\/\/ contained items must have a default ctor. public class MyGenericClass<T> where T : new()<br \/>\n{<br \/>\n...<br \/>\n}<\/p>\n<p>Notice that the where clause specifies which type parameter is being constrained, followed by a colon operator. After the colon operator, you list each possible constraint (in this case, a default constructor). Here is another example:<\/p>\n<p>\/\/ MyGenericClass derives from object, while<br \/>\n\/\/ contained items must be a class implementing IDrawable<br \/>\n\/\/ and must support a default ctor.<br \/>\npublic class MyGenericClass<T> where T : class, IDrawable, new()<br \/>\n{<br \/>\n...<br \/>\n}<\/p>\n<p>In this case, T has three requirements. It must be a reference type (not a structure), as marked with the class token. Second, T must implement the IDrawable interface. Third, it must also have a default<br \/>\nconstructor. Multiple constraints are listed in a comma-delimited list; however, you should be aware that the<br \/>\nnew() constraint must always be listed last! Thus, the following code will not compile:<\/p>\n<p>\/\/ Error! new() constraint must be listed last!<br \/>\npublic class MyGenericClass<T> where T : new(), class, IDrawable<br \/>\n{<br \/>\n...<br \/>\n}<\/p>\n<p>If you ever create a custom generic collection class that specifies multiple type parameters, you can specify a unique set of constraints for each, using separate where clauses.<\/p>\n<p>\/\/ <K> must extend SomeBaseClass and have a default ctor,<br \/>\n\/\/ while <T> must be a structure and implement the<br \/>\n\/\/ generic IComparable interface.<br \/>\npublic class MyGenericClass<K, T> where K : SomeBaseClass, new() where T : struct, IComparable<T><br \/>\n{<br \/>\n...<br \/>\n}<\/p>\n<p>You will rarely encounter cases where you need to build a complete custom generic collection class; however, you can use the where keyword on generic methods as well. For example, if you want to specify that your generic Swap<T>() method can operate only on structures, you will update the method like this:<\/p>\n<p>\/\/ This method will swap any structure, but not classes. static void Swap<T>(ref T a, ref T b) where T : struct<br \/>\n{<br \/>\n...<br \/>\n}<\/p>\n<p>Note that if you were to constrain the Swap() method in this manner, you would no longer be able to swap string objects (as is shown in the sample code) because string is a reference type.<\/p>\n<p>The Lack of Operator Constraints<br \/>\nI want to make one more comment about generic methods and constraints as this chapter ends. It might come as a surprise to you to find out that when creating generic methods, you will get a compiler error if you apply any C# operators (+, -, *, ==, etc.) on the type parameters. For example, imagine the usefulness of a class that can add, subtract, multiply, and divide generic types.<\/p>\n<p>\/\/ Compiler error! Cannot apply<br \/>\n\/\/ operators to type parameters! public class BasicMath<T><br \/>\n{<br \/>\npublic T Add(T arg1, T arg2)<br \/>\n{ return arg1 + arg2; }<br \/>\npublic T Subtract(T arg1, T arg2)<br \/>\n{ return arg1 - arg2; }<br \/>\npublic T Multiply(T arg1, T arg2)<br \/>\n{ return arg1 * arg2; }<br \/>\npublic T Divide(T arg1, T arg2)<br \/>\n{ return arg1 \/ arg2; }<br \/>\n}<\/p>\n<p>Unfortunately, the preceding BasicMath class will not compile. While this might seem like a major restriction, you need to remember that generics are generic. Of course, the numerical data can work with the binary operators of C#. However, for the sake of argument, if <T> were a custom class or structure type, the compiler could assume the class supports the +, -, *, and \/ operators. Ideally, C# would allow a generic type to be constrained by supported operators, as in this example:<\/p>\n<p>\/\/ Illustrative code only!<br \/>\npublic class BasicMath<T> where T : operator +, operator -, operator *, operator \/<br \/>\n{<br \/>\npublic T Add(T arg1, T arg2)<br \/>\n{ return arg1 + arg2; }<br \/>\npublic T Subtract(T arg1, T arg2)<br \/>\n{ return arg1 - arg2; }<br \/>\npublic T Multiply(T arg1, T arg2)<br \/>\n{ return arg1 * arg2; }<br \/>\npublic T Divide(T arg1, T arg2)<br \/>\n{ return arg1 \/ arg2; }<br \/>\n}<\/p>\n<p>Alas, operator constraints are not supported under the current version of C#. However, it is possible (albeit it requires a bit more work) to achieve the desired effect by defining an interface that supports these operators (C# interfaces can define operators!) and then specifying an interface constraint of the generic class. In any case, this wraps up this book\u2019s initial look at building custom generic types. In Chapter 12, I will pick up the topic of generics once again while examining the delegate type.<\/p>\n<p>Summary<br \/>\nThis chapter began by examining the nongeneric collection types of System.Collections and System. Collections.Specialized, including the various issues associated with many nongeneric containers, such as a lack of type safety and the runtime overhead of boxing and unboxing operations. As mentioned, for these very reasons, modern-day .NET programs will typically make use of the generic collection classes found in System.Collections.Generic and System.Collections.ObjectModel.<br \/>\nAs you have seen, a generic item allows you to specify placeholders (type parameters) at the time of object creation (or invocation, in the case of generic methods). While you will most often simply use the generic types provided in the .NET base class libraries, you will also be able to create your own generic types (and generic methods). When you do so, you have the option of specifying any number of constraints (using the where keyword) to increase the level of type safety and ensure that you perform operations on types of a known quantity that are guaranteed to exhibit certain basic capabilities.<br \/>\nAs a final note, remember that generics are found in numerous locations within the .NET base class libraries. Here, you focused specifically on generic collections. However, as you work through the remainder of this book (and when you dive into the platform on your own terms), you will certainly find generic classes, structures, and delegates located in a given namespace. As well, be on the lookout for generic members of a nongeneric class!<\/p>\n","protected":false},"excerpt":{"rendered":"<p>CHAPTER 10 Collections and Generics Any application you create with the .NET Core platform will need to contend with the issue of maintaining and manipulating a set of data points in memory. These data points can come from any variety of locations including a relational database, a local text file, an XML document, a web [&hellip;]<\/p>\n","protected":false},"author":1,"featured_media":0,"comment_status":"open","ping_status":"open","sticky":false,"template":"","format":"standard","meta":{"footnotes":""},"categories":[3],"tags":[22],"class_list":["post-310","post","type-post","status-publish","format-standard","hentry","category-csharp","tag-pro-csharp10-with-net6"],"_links":{"self":[{"href":"https:\/\/diji.net\/index.php?rest_route=\/wp\/v2\/posts\/310","targetHints":{"allow":["GET"]}}],"collection":[{"href":"https:\/\/diji.net\/index.php?rest_route=\/wp\/v2\/posts"}],"about":[{"href":"https:\/\/diji.net\/index.php?rest_route=\/wp\/v2\/types\/post"}],"author":[{"embeddable":true,"href":"https:\/\/diji.net\/index.php?rest_route=\/wp\/v2\/users\/1"}],"replies":[{"embeddable":true,"href":"https:\/\/diji.net\/index.php?rest_route=%2Fwp%2Fv2%2Fcomments&post=310"}],"version-history":[{"count":0,"href":"https:\/\/diji.net\/index.php?rest_route=\/wp\/v2\/posts\/310\/revisions"}],"wp:attachment":[{"href":"https:\/\/diji.net\/index.php?rest_route=%2Fwp%2Fv2%2Fmedia&parent=310"}],"wp:term":[{"taxonomy":"category","embeddable":true,"href":"https:\/\/diji.net\/index.php?rest_route=%2Fwp%2Fv2%2Fcategories&post=310"},{"taxonomy":"post_tag","embeddable":true,"href":"https:\/\/diji.net\/index.php?rest_route=%2Fwp%2Fv2%2Ftags&post=310"}],"curies":[{"name":"wp","href":"https:\/\/api.w.org\/{rel}","templated":true}]}}