# C# Study - Collections ###### tags: `C#` 'Collections` # Reference --- # [``IList<T>`` Interface](https://docs.microsoft.com/en-us/dotnet/api/system.collections.generic.ilist-1?view=netcore-3.1) Namespace: ``System.Collections.Generic`` Represents a collection of objects that can be individually accessed by index. ``` public interface IList<T> : System.Collections.Generic.ICollection<T>, System.Collections.Generic.IEnumerable<T> ``` --- # [C# - List<T>](https://www.tutorialsteacher.com/csharp/csharp-list) The ``List<T>`` is a collection of strongly typed objects that can be accessed by index and having methods for sorting, searching, and modifying list. It is the generic version of the ``ArrayList`` that comes under ``System.Collection.Generic`` namespace. ## ``List<T>`` Characteristics * ``List<T>`` equivalent of the ``ArrayList``, which implements ``IList<T>``. * It comes under System.Collection.Generic namespace. * ``List<T>`` can contain elements of the specified type. It __provides compile-time type checking and doesn't perform boxing-unboxing because it is generic__. * Elements can be added using the ``Add()``, ``AddRange()`` methods or collection-initializer syntax. * Elements can be accessed by passing an index e.g. ``myList[0]``. __Indexes start from zero__. * ``List<T>`` performs faster and less error-prone than the ArrayList. boxing / unboxing ## Creating a List The ``List<T>`` is a generic collection, so you __need to specify a type parameter__ for the type of data it can store. The following example shows how to create list and add elements. ### Example 1 - Adding elements in List: ``` List<int> primeNumbers = new List<int>(); primeNumbers.Add(1); // adding elements using add() method primeNumbers.Add(3); primeNumbers.Add(5); primeNumbers.Add(7); var cities = new List<string>(); cities.Add("New York"); cities.Add("London"); cities.Add("Mumbai"); cities.Add("Chicago"); cities.Add(null);// nulls are allowed for reference type list //adding elements using collection-initializer syntax var bigCities = new List<string>() { "New York", "London", "Mumbai", "Chicago" }; ``` In the above example, ``List<int> primeNumbers = new List<int>();`` creates a list of int type. In the same way, cities and bigCities are string type list. You can then add elements in a list using the ``Add()`` method or the __collection-initializer syntax__. You can also add elements of the custom classes using the collection-initializer syntax. The following adds objects of the ``Student`` class in the ``List<Student>``. ### Example 2 - Add Custom Class Objects in List: ``` var students = new List<Student>() { new Student(){ Id = 1, Name="Bill"}, new Student(){ Id = 2, Name="Steve"}, new Student(){ Id = 3, Name="Ram"}, new Student(){ Id = 4, Name="Abdul"} }; ``` ## Adding an Array in a List Use the ``AddRange()`` method to add all the elements from an array or another collection to ``List``. ``AddRange()`` signature: ``` void AddRange(IEnumerable<T> collection) ``` ### Example 3 - Add Arrays in List: ``` string[] cities = new string[3]{ "Mumbai", "London", "New York" }; var popularCities = new List<string>(); // adding an array in a List popularCities.AddRange(cities); var favouriteCities = new List<string>(); // adding a List favouriteCities.AddRange(popularCities); ``` ## Accessing a List A list can be accessed by an index, a for/foreach loop, and using LINQ queries. Indexes of a list start from zero. Pass an index in the square brackets to access individual list items, same as array. Use a foreach or for loop to iterate a ``List<T>`` collection. ### Example 4 - Accessing List: ``` List<int> numbers = new List<int>() { 1, 2, 5, 7, 8, 10 }; Console.WriteLine(numbers[0]); // prints 1 Console.WriteLine(numbers[1]); // prints 2 Console.WriteLine(numbers[2]); // prints 5 Console.WriteLine(numbers[3]); // prints 7 // using foreach LINQ method numbers.ForEach(num => Console.WriteLine(num + ", "));//prints 1, 2, 5, 7, 8, 10, // using for loop for(int i = 0; i < numbers.Count; i++) { Console.WriteLine(numbers[i]); } ``` ## Accessing a List using LINQ The ``List<T>`` implements the ``IEnumerable`` interface. So, we can query a list using __LINQ query syntax or method syntax__, as shown below. ### Example 5 - LINQ Query on List: ``` var students = new List<Student>() { new Student(){ Id = 1, Name="Bill"}, new Student(){ Id = 2, Name="Steve"}, new Student(){ Id = 3, Name="Ram"}, new Student(){ Id = 4, Name="Abdul"} }; //get all students whose name is Bill var result = from s in students where s.Name == "Bill" select s; foreach(var student in result) { Console.WriteLine(student.Id + ", " + student.Name); } ``` ## Insert Elements in List Use the Insert() method inserts an element into the List<T> collection at the specified index. Insert() signature:void Insert(int index, T item); ### Example 6 - Insert elements into List: ``` var numbers = new List<int>(){ 10, 20, 30, 40 }; numbers.Insert(1, 11);// inserts 11 at 1st index: after 10. foreach(var num in numbers) { Console.Write(num); } ``` ## Remove Elements from List Use the ``Remove()`` method to remove the first occurrence of the specified element in the ``List<T>`` collection. Use the ``RemoveAt()`` method to remove an element from the specified index. If no element at the specified index, then the ``ArgumentOutOfRangeException`` will be thrown. ``Remove()`` signature: ``bool Remove(T item)`` ``RemoveAt()`` signature: ``void RemoveAt(int index)`` ### Example 7 - Remove elements from List: ``` var numbers = new List<int>(){ 10, 20, 30, 40, 10 }; numbers.Remove(10); // removes the first 10 from a list numbers.RemoveAt(2); //removes the 3rd element (index starts from 0) //numbers.RemoveAt(10); //throws ArgumentOutOfRangeException foreach(var el in intList) { Console.Write(el); //prints 20 30 } ``` ## Check Elements in List Use the ``Contains()`` method to determine whether an element is in the List<T> or not. ### Example 8 - Contains(): ``` var numbers = new List<int>(){ 10, 20, 30, 40 }; numbers.Contains(10); // returns true numbers.Contains(11); // returns false numbers.Contains(20); // returns true ``` ## ``List<T>`` Class Properties and Methods The following table lists the important properties and methods of ``List<T>`` class: * ``Items``: Gets or sets the element at the specified index * ``Count``: Returns the total number of elements exists in the ``List<T>`` --- # [What is the difference between List and IList in C#?](https://www.tutorialspoint.com/what-is-the-difference-between-list-and-ilist-in-chash) The main difference between ``List`` and ``IList`` in C# is that __``List`` is a class__ that represents __a list of objects—— which can be accessed by index while __``IList`` is an interface__ that represents a __collection of objects__ which can be accessed by index. The ``IList`` interface implemented from two interfaces and they are ``ICollection`` and ``IEnumerable``. ``List`` and ``IList`` are used to denote a set of objects. They can store objects of integers, strings, etc. There are methods to insert, remove elements, search and sort elements of a ``List`` or ``IList``. The major difference between ``List`` and ``IList`` is that __``List`` is a concrete class and ``IList`` is an interface__. Overall, ``List`` is a concrete type that __implements the ``IList`` interface__. ### Example 1: ``` using System; using System.Collections.Generic; namespace DemoApplication{ class Demo{ static void Main(string[] args) { IList<string> ilist = new IList<string>(); //This will throw error as we cannot create instance for an IList as it is an interface. ilist.Add("Mark"); ilist.Add("John"); foreach(string list in ilist) { Console.WriteLine(list); } } } } ``` ### Example 2: ``` using System; using System.Collections.Generic; namespace DemoApplication{ class Demo{ static void Main(string[] args) { IList<string> ilist = new List<string>(); ilist.Add("Mark"); ilist.Add("John"); List<string> list = new List<string>(); ilist.Add("Mark"); ilist.Add("John"); foreach(string lst in ilist) { Console.WriteLine(lst); } foreach(string lst in list) { Console.WriteLine(lst); } Console.ReadLine(); } } } ``` --- # [``IDictionary<TKey,TValue>`` Interface](https://docs.microsoft.com/en-us/dotnet/api/system.collections.generic.idictionary-2?view=netcore-3.1) Namespace: ``System.Collections.Generic`` Represents a generic collection of key/value pairs. ``` public interface IDictionary<TKey,TValue> : System.Collections.Generic.ICollection<System.Collections.Generic.KeyValuePair<TKey,TValue>>, System.Collections.Generic.IEnumerable<System.Collections.Generic.KeyValuePair<TKey,TValue>> ``` ## Examples The following code example creates an empty ``Dictionary<TKey,TValue>`` of strings, with ``string`` keys, and accesses it through the ``IDictionary<TKey,TValue>`` interface. The code example uses the ``Add`` method to add some elements. The example demonstrates that the ``Add`` method __throws ``ArgumentException`` when attempting to add a duplicate key__. The example uses the __``Item[]`` property__ (the indexer in C#) to retrieve values, demonstrating that __a ``KeyNotFoundException`` is thrown when a requested key is not present__, and showing that __the value associated with a key can be replaced__. 加值時,要先測試 key 存不存在; 另外,取值時, ``Item[]`` property - indexer 可以取值或更新值,但 key 不存在會? The example shows __how to use the ``TryGetValue`` method as a more efficient way to retrieve values if a program often must try key values that are not in the dictionary__, and __how to use the ``ContainsKey`` method to test whether a ``key`` exists prior to calling the ``Add`` method__. Finally, the example shows __how to enumerate the keys and values__ in the dictionary, and __how to enumerate the values alone using the ``Values`` property__. ``` using System; using System.Collections.Generic; public class Example { public static void Main() { // Create a new dictionary of strings, with string keys, // and access it through the IDictionary generic interface. IDictionary<string, string> openWith = new Dictionary<string, string>(); // Add some elements to the dictionary. // There are no duplicate keys, but some of the values are duplicates. openWith.Add("txt", "notepad.exe"); openWith.Add("bmp", "paint.exe"); openWith.Add("dib", "paint.exe"); openWith.Add("rtf", "wordpad.exe"); // The Add method throws an exception // if the new key is already in the dictionary. try { openWith.Add("txt", "winword.exe"); } catch (ArgumentException) { Console.WriteLine("An element with Key = \"txt\" already exists."); } // The Item property is another name for the indexer, // so you can omit its name when accessing elements. Console.WriteLine("For key = \"rtf\", value = {0}.", openWith["rtf"]); // The indexer can be used to change the value associated with a key. openWith["rtf"] = "winword.exe"; Console.WriteLine("For key = \"rtf\", value = {0}.", openWith["rtf"]); // If a key does not exist, // setting the indexer for that key adds a new key/value pair. openWith["doc"] = "winword.exe"; // The indexer throws an exception if the requested key is not in the dictionary. try { Console.WriteLine("For key = \"tif\", value = {0}.", openWith["tif"]); } catch (KeyNotFoundException) { Console.WriteLine("Key = \"tif\" is not found."); } // When a program often has to try keys that // turn out not to be in the dictionary, // TryGetValue can be a more efficient way to retrieve values. string value = ""; if (openWith.TryGetValue("tif", out value)) { Console.WriteLine("For key = \"tif\", value = {0}.", value); } else { Console.WriteLine("Key = \"tif\" is not found."); } // ContainsKey can be used to test keys before inserting them. if (!openWith.ContainsKey("ht")) { openWith.Add("ht", "hypertrm.exe"); Console.WriteLine("Value added for key = \"ht\": {0}", openWith["ht"]); } // When you use foreach to enumerate dictionary elements, // the elements are retrieved as KeyValuePair objects. Console.WriteLine(); foreach(KeyValuePair<string, string> kvp in openWith) { Console.WriteLine("Key = {0}, Value = {1}", kvp.Key, kvp.Value); } // To get the values alone, use the Values property. ICollection<string> icoll = openWith.Values; // The elements of the ValueCollection are strongly typed // with the type that was specified for dictionary values. Console.WriteLine(); foreach(string s in icoll) { Console.WriteLine("Value = {0}", s); } // To get the keys alone, use the Keys property. icoll = openWith.Keys; // The elements of the ValueCollection are strongly typed // with the type that was specified for dictionary values. Console.WriteLine(); foreach(string s in icoll) { Console.WriteLine("Key = {0}", s); } // Use the Remove method to remove a key/value pair. Console.WriteLine("\nRemove(\"doc\")"); openWith.Remove("doc"); if (!openWith.ContainsKey("doc")) { Console.WriteLine("Key \"doc\" is not found."); } } } ``` This code example produces the following output: ``` An element with Key = "txt" already exists. For key = "rtf", value = wordpad.exe. For key = "rtf", value = winword.exe. Key = "tif" is not found. Key = "tif" is not found. Value added for key = "ht": hypertrm.exe Key = txt, Value = notepad.exe Key = bmp, Value = paint.exe Key = dib, Value = paint.exe Key = rtf, Value = winword.exe Key = doc, Value = winword.exe Key = ht, Value = hypertrm.exe Value = notepad.exe Value = paint.exe Value = paint.exe Value = winword.exe Value = winword.exe Value = hypertrm.exe Key = txt Key = bmp Key = dib Key = rtf Key = doc Key = ht Remove("doc") Key "doc" is not found. ``` ## Remarks The ``IDictionary<TKey,TValue>`` interface is the base interface for generic collections of key/value pairs. __Each element is a key/value pair stored in a ``KeyValuePair<TKey,TValue>`` object__. __Each pair must have a unique key__. Implementations can vary in __whether they allow key to be ``null``__. __The value can be ``null`` and does not have to be unique__. The ``IDictionary<TKey,TValue>`` interface allows the contained keys and values to be enumerated, but it __does not imply any particular sort order__. The ``foreach`` statement of the C# language (For Each in Visual Basic, for each in C++) returns an object of the type of the elements in the collection. Since each element of the ``IDictionary<TKey,TValue>`` is a __key/value pair__, the element type is not the type of the key or the type of the value. Instead, __the element type is KeyValuePair<TKey,TValue>__. For example: ``` foreach (KeyValuePair<int, string> kvp in myDictionary) { Console.WriteLine("Key = {0}, Value = {1}", kvp.Key, kvp.Value); } ``` The ``foreach`` statement is a __wrapper around the enumerator__, which __only allows reading from, not writing to__, the collection. 只能讀,不能寫!! --- [C# - Dictionary<TKey, TValue>](https://www.tutorialsteacher.com/csharp/csharp-dictionary) The ``Dictionary<TKey, TValue>`` is a generic collection that stores key-value pairs __in no particular order__. ## Dictionary Characteristics * ``Dictionary<TKey, TValue>`` stores key-value pairs. * Comes under ``System.Collection.Generic`` namespace. * __Implements ``IDictionary<TKey, TValue>`` interface__. * __Keys must be unique and cannot be ``null``__. * __Values can be null or duplicate__. * Values can be accessed by passing associated key in the indexer e.g. ``myDictionary[key]`` * Elements are stored as ``KeyValuePair<TKey, TValue>`` objects. ## Creating a Dictionary You can create the ``Dictionary<TKey, TValue>`` object by passing the type of keys and values it can store. The following example shows how to create a dictionary and add key-value pairs. ### Example 1 - Create Dictionary and Add Elements: ``` IDictionary<int, string> numberNames = new Dictionary<int, string>(); numberNames.Add(1,"One"); //adding a key/value using the Add() method numberNames.Add(2,"Two"); numberNames.Add(3,"Three"); //The following throws run-time exception: key already added. //numberNames.Add(3, "Three"); foreach(KeyValuePair<int, string> kvp in numberNames) { Console.WriteLine("Key: {0}, Value: {1}", kvp.Key, kvp.Value); } //creating a dictionary using collection-initializer syntax var cities = new Dictionary<string, string>(){ {"UK", "London, Manchester, Birmingham"}, {"USA", "Chicago, New York, Washington"}, {"India", "Mumbai, New Delhi, Pune"} }; foreach(var kvp in cities) { Console.WriteLine("Key: {0}, Value: {1}", kvp.Key, kvp.Value); } ``` In the above example, numberNames is a Dictionary<int, string> type dictionary, so it can store int keys and string values. In the same way, cities is a Dictionary<string, string> type dictionary, so it can store string keys and string values. Dictionary cannot include duplicate or null keys, whereas values can be duplicated or null. Keys must be unique otherwise, it will throw a runtime exception. ## Access Dictionary Elements The Dictionary can be accessed using __indexer__. Specify a key to get the associated value. You can also use the ``ElementAt()`` method to __get a ``KeyValuePair`` from the specified index__. ### Example 2 - Access Dictionary Elements: ``` var cities = new Dictionary<string, string>(){ {"UK", "London, Manchester, Birmingham"}, {"USA", "Chicago, New York, Washington"}, {"India", "Mumbai, New Delhi, Pune"} }; Console.WriteLine(cities["UK"]); //prints value of UK key Console.WriteLine(cities["USA"]);//prints value of USA key //Console.WriteLine(cities["France"]); // run-time exception: Key does not exist //use ContainsKey() to check for an unknown key if(cities.ContainsKey("France")){ Console.WriteLine(cities["France"]); } //use TryGetValue() to get a value of unknown key string result; if(cities.TryGetValue("France", out result)) { Console.WriteLine(result); } //use ElementAt() to retrieve key-value pair using index for(int i = 0; i < cities.Count; i++) { Console.WriteLine("Key: {0}, Value: {1}", cities.ElementAt(i).Key, cities.ElementAt(i).Value); } ``` ## Update Dictionary Update the value of a key by specifying a key in the indexer. It will throw the KeyNotFoundException if a key does not exist in the dictionary, therefore use the ContainsKey() method before accessing unknown keys. ### Example 3 - Update Dictionary Elements: ``` var cities = new Dictionary<string, string>(){ {"UK", "London, Manchester, Birmingham"}, {"USA", "Chicago, New York, Washington"}, {"India", "Mumbai, New Delhi, Pune"} }; cities["UK"] = "Liverpool, Bristol"; // update value of UK key cities["USA"] = "Los Angeles, Boston"; // update value of USA key //cities["France"] = "Paris"; //throws run-time exception: KeyNotFoundException if(cities.ContainsKey("France")) { cities["France"] = "Paris"; } ``` ## Remove Elements in Dictionary The ``Remove()`` method __deletes an existing key-value pair__ from a dictionary. The ``Clear()`` method __deletes all the elements__ of the dictionary. ### Example 4 - Remove Dictionary Elements: ``` var cities = new Dictionary<string, string>(){ {"UK", "London, Manchester, Birmingham"}, {"USA", "Chicago, New York, Washington"}, {"India", "Mumbai, New Delhi, Pune"} }; cities.Remove("UK"); // removes UK //cities.Remove("France"); //throws run-time exception: KeyNotFoundException if(cities.ContainsKey("France")) { // check key before removing it cities.Remove("France"); } cities.Clear(); //removes all elements ``` --- ## Next: Sort a List * [Comparisons and sorts within collections](https://docs.microsoft.com/en-us/dotnet/standard/collections/comparisons-and-sorts-within-collections) * [List<T>.Sort Method](https://docs.microsoft.com/en-us/dotnet/api/system.collections.generic.list-1.sort?view=netcore-3.1) * [Comparison<T> Delegate](https://docs.microsoft.com/en-us/dotnet/api/system.comparison-1?view=netcore-3.1) * [IComparer<T> Interface](https://docs.microsoft.com/en-us/dotnet/api/system.collections.generic.icomparer-1?view=netcore-3.1) --- * [How to sort a list in C# | List.Sort() Method Set -1](https://www.geeksforgeeks.org/how-to-sort-list-in-c-sharp-set-1/) * [How to sort a list in C# | List.Sort() Method Set -2](https://www.geeksforgeeks.org/how-to-sort-a-list-in-c-sharp-list-sort-method-set-2/?ref=lbp) --- # [Using indexers (C# Programming Guide)](https://docs.microsoft.com/en-us/dotnet/csharp/programming-guide/indexers/using-indexers) __Indexers__ are a __syntactic convenience__ that enable you to create a ``class``, ``struct``, or interface that client applications can access as an array. The compiler will generate an ``Item`` property (or an alternatively named property if ``IndexerNameAttribute`` is present), and the appropriate accessor methods. Indexers are most frequently implemented in types whose __primary purpose is to encapsulate an internal collection or array__. For example, suppose you have a class ``TempRecord`` that represents the temperature in ``Fahrenheit`` as recorded at 10 different times during a 24-hour period. The class contains a temps array of type ``float[]`` to store the temperature values. By implementing an indexer in this class, clients can access the temperatures in a ``TempRecord`` instance as ``float temp = tempRecord[4]`` instead of as ``float temp = tempRecord.temps[4]``. The indexer notation not only simplifies the syntax for client applications; it also makes the class, and its purpose more intuitive for other developers to understand. To declare an indexer on a class or struct, use the ``this`` keyword, as the following example shows: ``` // Indexer declaration public int this[int index] { // get and set accessors } ``` __Important__ Declaring an indexer will automatically generate a property named ``Item`` on the object. The ``Item`` property is not directly accessible from the instance __member access expression__. Additionally, if you add your own ``Item`` property to an object with an indexer, you'll get a CS0102 compiler error. To avoid this error, use the IndexerNameAttribute rename the indexer as detailed below. --- ## Remarks __The type of an indexer and the type of its parameters must be at least as accessible as the indexer itself__. For more information about accessibility levels, see [Access Modifiers](https://docs.microsoft.com/en-us/dotnet/csharp/programming-guide/indexers/indexers-in-interfaces). For more information about how to use indexers with an interface, see [Interface Indexers](https://docs.microsoft.com/en-us/dotnet/csharp/programming-guide/indexers/indexers-in-interfaces). The signature of an indexer consists of the number and types of its formal parameters. It doesn't include the indexer type or the names of the formal parameters. If you declare more than one indexer in the same class, they must have different signatures. An indexer value is not classified as a variable; therefore, you cannot pass an indexer value as a ``ref`` or ``out`` parameter. To provide the indexer with a name that other languages can use, use [``System.Runtime.CompilerServices.IndexerNameAttribute``](https://docs.microsoft.com/en-us/dotnet/api/system.runtime.compilerservices.indexernameattribute), as the following example shows: ``` // Indexer declaration [System.Runtime.CompilerServices.IndexerName("TheItem")] public int this[int index] { // get and set accessors } ``` This indexer will have the name ``TheItem``, as it is overridden by the indexer name attribute. By default, the indexer name is ``Item``. ### Example: The following example shows how to declare a ``private`` array field, ``temps``, and an indexer. The indexer enables direct access to the instance ``tempRecord[i]``. __The alternative to using the indexer__ is to __declare the array as a ``public`` member and access its members__, ``tempRecord.temps[i]``, directly. --- ``` public class TempRecord { // Array of temperature values float[] temps = new float[10] { 56.2F, 56.7F, 56.5F, 56.9F, 58.8F, 61.3F, 65.9F, 62.1F, 59.2F, 57.5F }; // To enable client code to validate input // when accessing your indexer. public int Length => temps.Length; // Indexer declaration. // If index is out of range, the temps array will throw the exception. public float this[int index] { get => temps[index]; set => temps[index] = value; } } ``` ``` static void Main() { var tempRecord = new TempRecord(); // Use the indexer's set accessor tempRecord[3] = 58.3F; tempRecord[5] = 60.1F; // Use the indexer's get accessor for (int i = 0; i < 10; i++) { Console.WriteLine($"Element #{i} = {tempRecord[i]}"); } } ``` ---