public static void Main(string[] args)
        {
            // ---------------------------------
            // 71) Code snippets in visual studio
            // ---------------------------------

            // Code Snippets are ready-made snippets of code you can quickly insert into your code.
            // 1. Keyboard shortcut: Ctrl K + X
            // 2. Right click and select "Insert snippet...", from the context menu
            // 3. Click on Edit - Intellisense - Insert snippet
            // 4. Use code snippet short cut.
            // Ex: use "for loop" code snippet, type "for" and press Tab key twice

            // Once a code snippet is inserted, the editable fields are highlighted in yellow, and
            // the first editable field is selected automatically.
            // Upon changing the first editable field, press TAB to move to the
            // next editable field.
            // To come to the previous editable field use SHIFT + TAB.
            // Press ENTER or ESC keys to cancel field editing and return the Code Editor to normal.

            // Code Snippet Types:
            // Expansion: These snippets allow the code snippet to be inserted at the cursor.

            // SurroundsWith: These snippets allow the code snippet to be placed around a selected
            // piece of code.

            // Refactoring: These snippets are used during code refactoring.

            // Surround-with snippets surround the selected code, with the code snippets code.
            // 1. Select the code to surround, and use keyboard shortcut. CTRL K + S
            // 2. Select the code to surround, right click and select "Surround with..." option
            // with the context menu.
            // 3. Select the code to surround, then click on Edit menu, select "IntelliSense"
            // and then select the "Surround With" command

            // Code Snippets can be used with any type of applications that you create with Visual Studio.
            // Console Apps
            // ASP.NET
            // ASP.NET MVC

            // Code Snippet Manager
            // can be used to Add or remove code snippet. You can also find the following information
            // about a code snippet.
            // 1. Description
            // 2. Shortcut
            // 3. Snippet Type
            // 4. Author

            // To access the code snippet manager, click on "Tools" and then select "Code Snippet Manager"
            // Code snippets are XML files and have .snippet extension

            // ---------------------------------
            // 72) What is a dictionary in C#?
            // ---------------------------------

            // 1. A dictionary is a collection of (key, value) pairs.
            // 2. Dictionary class is present in System.Collections.Generic namespace.
            // 3. When creating a dictionary, we need to specify the type for key and value.
            // 4. Dictionary provides fast lookups for values using keys.
            // 5. Keys in the dictionary MUST be unique.

            NewCustomer customer1 = new NewCustomer()
            {
                ID     = 101,
                Name   = "Steven",
                Salary = 60000
            };

            var customer2 = new NewCustomer()
            {
                ID     = 110,
                Name   = "Mark",
                Salary = 90000
            };

            var customer3 = new NewCustomer()
            {
                ID     = 112,
                Name   = "Pam",
                Salary = 70000
            };

            Dictionary <int, NewCustomer> dictionaryCustomers = new Dictionary <int, NewCustomer>();

            dictionaryCustomers.Add(customer1.ID, customer1);
            dictionaryCustomers.Add(customer2.ID, customer2);
            dictionaryCustomers.Add(customer3.ID, customer3);

            NewCustomer customer110 = dictionaryCustomers[110];

            Console.WriteLine("ID = {0}, Name = {1}, Salary = {2}", customer110.ID, customer110.Name, customer110.Salary);

            // To traverse through the dictionary, you can utilize a foreach loop.
            // For the datatype you could just use var, and it will work just the same.
            foreach (KeyValuePair <int, NewCustomer> customerKeyValuePair in dictionaryCustomers)
            {
                Console.WriteLine("ID = {0}", customerKeyValuePair.Key);
                var customer = customerKeyValuePair.Value;
                Console.WriteLine("Name = {0}, Salary = {1}", customer.Name, customer.Salary);
            }

            // So to loop through just the keys.
            foreach (int key in dictionaryCustomers.Keys)
            {
                Console.WriteLine(key);
            }

            // To loop through only the values.
            foreach (var customer in dictionaryCustomers.Values)
            {
                Console.WriteLine("Name = {0}, Salary = {1}", customer.Name, customer.Salary);
            }

            // If you have duplicate keys, then you will get a run-time exception.
            // To check that a key is not already found use .ContainsKey()
            // This method should also be used before you try to retrieve a value
            // just to ensure that it actually exists and it is not going to throw an exception.
            if (!dictionaryCustomers.ContainsKey(110))
            {
                dictionaryCustomers[110] = customer2;
            }

            // ---------------------------------
            // 73) What is a dictionary in C# continued
            // ---------------------------------
            // Cover the methods in the Dictionary class.
            // 1. TryGetValue()
            // 2. Count()
            // 3. Remove()
            // 4. Clear()
            // 5. Using LINQ extension methods with Dictionary
            // 6. Different ways to convert an array into a dictionary

            // If you try to access a key that you are not sure if it exists.
            // You can use TryGetValue()

            NewCustomer customer101;

            if (dictionaryCustomers.TryGetValue(101, out customer101))
            {
                Console.WriteLine("ID = {0}, Name = {1}, Salary = {2}", customer101.ID, customer101.Name, customer101.Salary);
            }
            else
            {
                Console.WriteLine("The key is not found");
            }

            Console.WriteLine("Total Items = {0}", dictionaryCustomers.Count);

            // could also use the Count() function which is in a different namespace
            // It's actually an extension method.

            Console.WriteLine("Total Items = {0}", dictionaryCustomers.Count(kvp => kvp.Value.Salary > 40000));
            // So here we are using LINQ and passing in a lamba expression as the predicate.

            //dictionaryCustomers.Remove(110);

            //dictionaryCustomers.Clear();

            NewCustomer[] customers = new NewCustomer[3];
            customers[0] = customer1;
            customers[1] = customer2;
            customers[2] = customer3;

            // For the arg, you first specify what the key should be
            // then specify what the value will be.
            Dictionary <int, NewCustomer> dict = customers.ToDictionary(cust => cust.ID, cust => cust);

            foreach (KeyValuePair <int, NewCustomer> kvp in dict)
            {
                Console.WriteLine("Key = {0}", kvp.Key);
                NewCustomer cust = kvp.Value;
                Console.WriteLine("ID = {0}, Name = {1}, Salary = {2}", cust.ID, cust.Name, cust.Salary);
            }

            // ---------------------------------
            // 74) List collection class in C#
            // ---------------------------------

            // List is one of the generic collection classes present in System.Collection.Generic namespace.
            // There are several generic collection classes in System.Collections.Generic namespace.
            // 1. Dictionary
            // 2. List
            // 3. Stack
            // 4. Queue

            // A List class can be used to create a collection of any type.

            // For example, we can create a list of Intgers, Strings and even complex types.

            // The objects stored in the list can be accessed by index.

            // Unlike arrays, list can grow in size automatically.

            // This class also provides methods to search, sort, and manipulate lists.

            // DEMO

            NewCustomer[] myCustomers = new NewCustomer[2];
            myCustomers[0] = customer1;
            myCustomers[1] = customer2;

            // So build would succeed, BUT would get an exception at runtime.
            customers[2] = customer3;

            // Arrays don't grow in size automatically.
            // But Lists do grow automatically.

            List <NewCustomer> customersList = new List <NewCustomer>(2);

            customersList.Add(customer1);
            customersList.Add(customer2);
            customersList.Add(customer3);

            // No error as Lists can grow in size automatically.

            NewCustomer c = customersList[0];

            Console.WriteLine("ID = {0}, Name = {1}, Salary = {2}", c.ID, c.Name, c.Salary);

            // To iterate through the entire List, you can use a for loop or a foreach loop.
            foreach (NewCustomer cu in customersList)
            {
                Console.WriteLine("ID = {0}, Name = {1}, Salary = {2}", cu.ID, cu.Name, cu.Salary);
            }

            // So prefer to use a foreach loop when iterating through a collection.

            // The List class is strongly typed, meaning that you specify the type
            // that the collection can contain.

            // To insert an object at a specific location,
            // as the .Add() will just append to the end.
            customersList.Insert(0, customer3);
            // So when you insert it, it will push the rest of the elements
            // down in the collection from that point.

            // To retrieve the index of a specific object in the list.
            int index = customersList.IndexOf(customer3);

            // The .IndexOf() method is overloaded so it can accept an second argument
            // which will specify from what index to start searching from.
            int index2 = customersList.IndexOf(customer3, 1);

            // You could also specify the number of items to look.
            // For how many elements to you want to look from the start point.
            int index3 = customersList.IndexOf(customer3, 1, 2);

            // NOTE: That it will throw an exception is the range to look through
            // is more than the actual count.

            // ---------------------------------
            // 75) List collection class in C# contiued
            // ---------------------------------

            // Contains() function
            // Checks if an item exists in the list. This method returns
            // true if the items exist, else false.

            // Exists() function
            // Checks if an item exists in the list based on a condition. This
            // method returns true if the items exists, else false.

            // Find()
            // Searches for an element that matches the conditions defined
            // by the specified lambda expression and returns the first matching
            // item from the list.

            // FindLast()
            // Searches fro an element that matches the conditions defined
            // by the specified lambda expression and returns the Last matching
            // item from the list.

            // FindAll()
            // Returns all the items from the list that match the conditions
            // specified by the lambda expression.

            // FindIndex()
            // Returns the index of the first item, that matches the condition specified
            // by the lambda expression.
            // There are 2 other overloads of this method which allow us to specify
            // the range of elements to search within the list.

            // FindLastIndex()
            // Returns the last index of the last item, that matches the condition specified
            // by the lambda expression. There are 2 other overloads of
            // this method which allows us to specify the range of elements to search
            // within the list.

            // Convert an array to a list
            // Use ToList() method

            // Convert a list to an array
            // Use ToArray() method

            // Convert a list to a dictionary
            // use ToDictionary() method

            if (customersList.Contains(customer1))
            {
                Console.WriteLine("Yes it is found");
            }
            else
            {
                Console.WriteLine("Customer was not found in the list.");
            }

            // Check if an item exists in a list depending on a given condition.
            if (customersList.Exists(cust => cust.Name.StartsWith("S")))
            {
                Console.WriteLine("Yes, customer exists in the list.");
            }

            // So Find() is quite similar, except it returns the first occurence,
            // rather than a boolean as shown previous.
            NewCustomer cust1334 = customersList.Find(cust => cust.Salary > 50000);

            // So FindLast()
            // This returns the Last matching item from the list rather than the first
            // like the one shown above.

            NewCustomer lastMatchingCustomer = customersList.FindLast(cust => cust.Salary > 50000);

            // FindAll()
            List <NewCustomer> matchingCustomers = customersList.FindAll(cust => cust.Salary > 50000);

            // FindIndex()
            // Based on the matching condition.
            int matchingIndex1 = customersList.FindIndex(cust => cust.Salary > 50000);

            // Could also supply the first argument to be an integer to state
            // where to start searching from. (could supply another arg for a range)
            int matchingIndexFromRange = customersList.FindIndex(2, cust => cust.Salary > 50000);


            // FindLastIndex()
            // This also has three overloads with use cases with the same as above.
            int matchingLastIndex = customersList.FindLastIndex(1, cust => cust.Salary > 50000);

            // Convert an array to a list
            List <NewCustomer> myNewList = myCustomers.ToList();

            // Convert a list to an array.
            NewCustomer[] myConvertedArray = customersList.ToArray();

            // Convert a list to a dictionary.
            Dictionary <int, NewCustomer> convertedDictionary = customersList.ToDictionary(x => x.ID);

            foreach (KeyValuePair <int, NewCustomer> currentCust in convertedDictionary)
            {
                Console.WriteLine("ID = {0}", currentCust.Key);
                NewCustomer cust = currentCust.Value;
                Console.WriteLine("Name = {0}, Salary = {1}", cust.Name, cust.Salary);
            }


            // ---------------------------------
            // 76) Working with generic list class and ranges in C#
            // ---------------------------------

            // AddRange()
            // Add() method allows you to add one item at a time to the end of the list,
            // where AddRange() allows you to add another list of items,
            // to the end of the list.

            // GetRange()
            // Using an item index, we can retrieve only one item at a time from the list,
            // if you want to get a list of items from the list, then use GetRange()
            // This function expects 2 parameters, the start index of the list and
            // the number of elements to return.

            // InsertRange()
            // Insert() method allows you to insert a single item into the list at a specified
            // index where InsertRange() allows you to insert another list of items to your list
            // at the specified index.

            // RemoveRange()
            // Remove() removes only the first matching item from the list.
            // RemoveAt() function removes the item at the specified index in the list.
            // RemoveAll() removes all the items that match the specified condition.
            // RemoveRange() removes a range of elements from the list.
            // This function expects 2 parameters the start index in the list and the number of
            // elements to remove.
            // If you want to remove all the elements from the list without specifying any condition,
            // then use Clear() function.

            // --

            // so from index 0 get 2 customers
            // List<Customer> customers = listCustomers.GetRange(0, 2);



            // ---------------------------------
            // 77) Sort a list of simple types in C#
            // ---------------------------------

            // Sorting a list of simple types like int, string, char is straight forward.
            // just invoke the sort() method on the list instance and the data will
            // be automatically sorted in ascending order
            List <int> numbers = new List <int> {
                1, 8, 7, 5, 2, 3, 4, 9, 6
            };

            numbers.Sort();
            foreach (int num in numbers)
            {
                Console.WriteLine(num);
            }

            // If you want the data to be retrieved in desceding order, use Reverse() method
            // on the list instance.
            numbers.Reverse();


            // However when you do the same thing with a complex type like NewCustomer, we get
            // a runtime invalid operation expception - failed to compare 2 elements in the array.
            // This is because .NET runtime does not know how to sort complex types.
            // We have to tell the way we want data to be sorted in the list by
            // implementing IComparable interface.

            // How is the sort functionality working for simple types like int, string, char.
            // That is because these types (int, string, decimal, char, etc.) have
            // implemented IComparable interface already.

            // To sort a list of strings.
            List <string> alphabets = new List <string>()
            {
                "B", "F", "D", "E", "A", "C"
            };

            Console.WriteLine("Alphabets before sorting.");
            foreach (string alphabet in alphabets)
            {
                Console.WriteLine(alphabet);
            }

            alphabets.Sort();
            Console.WriteLine("Alphabets after sorting.");
            foreach (string alphabet in alphabets)
            {
                Console.WriteLine(alphabet);
            }

            alphabets.Reverse();
            Console.WriteLine("Alphabets sorted in descending order.");
            foreach (string alphabet in alphabets)
            {
                Console.WriteLine(alphabet);
            }

            // Now for sorting a complex type like NewCustomer.
            // You have to tell .NET runtime how you want these complex types
            // to be sorted by implementing the interface IComparable

            // ---------------------------------
            // 78) Sort a list of complex types in C#
            // ---------------------------------

            // To sort a list of complex types without using LINQ, the complex type has to implement IComparable
            // interface and provide implementation for CompareTo() method.
            // CompareTo() method returns an integer and the meaning of the return value is shown below.

            // RETURN VALUE is
            // Greater than zero =>
            // the current instance is greater than the object being compared with

            // Less than ZERO =>
            // the current instance is less than the object being compared with

            // is ZERO =>
            // the current instance is equal to the object being compared with

            // Alternatively you can also invoke CompareTo() method
            // you can invoke its value on one of its primitive properties
            // as to return its value

            customersList.Sort();

            // Using LINQ
            customersList.OrderByDescending(cust => cust.Salary);

            // There may also be the case when you want to sort a data structure
            // (a class) but you don't have access to the implementation.
            // Well you could use LINQ,

            // Or can do another way.
            // You can provide your own by implementing ICompararer interface.

            // shown below in its own class SortByName()
            // Step 1: Implement IComparer interface
            // Step 2: Pass in instance of the class that implements IComparer interace,
            // as an argument to the Sort() method
            SortByName sortByName = new SortByName();

            customersList.Sort(sortByName);

            // There are several overloaded methods for the .Sort() method
            // on the List<> data structure.

            // ---------------------------------
            // 79) Sort a list of complex types using Comparison delegate
            // ---------------------------------

            // One of the overloads of the Sort() method in List class expects Comparison delegate
            // to be passed as an argument
            // public void Sort(Comparison<T> comparison);

            // Approach 1:
            // Step 1: Create a function whose signature matches the signature of
            // System.Comparison delegate.
            // This is the method where we need to write the logic to compare 2 customer objects.
            // private static int CompareCustomers(NewCustomer c1, NewCustomer c2)
            // {
            //      return c1.ID.CompareTo(c2.ID);
            // }

            // Step 2: Create an instance of System.Comparison delegate and then pass the name
            // of the function created in Step 1 as the argument.
            // So at this point "Comparison" delegate is pointing to our function
            // that contains the logic to compare 2 customer objects.
            // Comparison<Customer> customerComparer = new Comparison<NewCustomer>(CompareCustomers);

            // Step 3: Pass the delegate instance as an argument to Sort() function.
            // customersList.Sort(customComparer);

            // NOTE: a delegate is a function pointer
            // when you invoke the delegate, it will automatically
            // invoke the function that it points to

            Comparison <NewCustomer> customerComparer = new Comparison <NewCustomer>(CompareCustomer);

            customersList.Sort(customerComparer);

            foreach (NewCustomer customer in customersList)
            {
                Console.WriteLine($"{ customer.ID }");
            }

            // APPROACH 2:
            // We don't actually have to go through all those steps as done in the previous
            // approach.
            // Rather we can achieve the same thing utilizing the delegate keyword.
            // In approach 1 we did the following:
            // 1. Created a private function that contains the logic to compare customers
            // 2. Created an instance of Comparison delegate, and then passed the name of
            // the private function to the delegate.
            // 3. Finally passed the delegate instance to the Sort() method.

            // Now a simplified way.
            // Use the delegate keyword
            customersList.Sort(delegate(NewCustomer c1, NewCustomer c2)
            {
                return(c1.ID.CompareTo(c2.ID));
            });

            // Approach 3:
            // Can be further simplified by using lamda expressions
            customersList.Sort((c1, c2) => c1.ID.CompareTo(c2.ID));
        }
        public static void Main(string[] args)
        {
            /* ---------------------
             * 80) Some useful methods of List collection class
             * ---------------------- */

            // TrueForAll()
            // Returns true or false depending on whether every element in the list matches the
            // conditions defined by the specified predicate

            // AsReadOnly()
            // Returns a read-only wrapper for the current collection.
            // Use this method if you don't want the client code to modify the collection
            // so no adding or removing from the collection
            // The ReadOnlyCollection will not have methods to add or remove items from the collection
            // you can only read items from this collection

            // TrimExcess()
            // Sets the capacity to the actual number of elements in the list,
            // if that number is less than a threshold value
            // NOTE:
            // This method can be used to minimize a collection's memory overhead if no new elements
            // will be added to the collection. The cost of reallocating and copying a large List<T>
            // can be considerable. So the TrimExcess method does nothing if the list is at more than 90
            // percent of capacity. This avoids incurring a large reallocation cost for a relatively
            // small gain.  The current threshold is 90 percent, but this could change in the future.

            List <NewCustomer> listCustomers = new List <NewCustomer>()
            {
                new NewCustomer()
                {
                    ID = 1, Name = "Steven", Salary = 52000
                },
                new NewCustomer()
                {
                    ID = 1, Name = "Joe", Salary = 65000
                },
                new NewCustomer()
                {
                    ID = 1, Name = "Tony", Salary = 88000
                }
            };

            Console.WriteLine(listCustomers.TrueForAll(x => x.Salary > 5000));

            ReadOnlyCollection <NewCustomer> readOnlyListCustomers = listCustomers.AsReadOnly();

            foreach (NewCustomer customer in readOnlyListCustomers)
            {
                Console.WriteLine($"{customer.Name}");
            }
            Console.WriteLine("First employee in the collection is {0}", readOnlyListCustomers[0].Name);

            Console.WriteLine("Capacity before trimming = " + listCustomers.Capacity);
            listCustomers.TrimExcess(); // So now this instance of List<> has a fixed size
            Console.WriteLine("Capacity after trimming = " + listCustomers.Capacity);

            // So the .TrimExcess() method is beneficial for saving memory if you know that you are not going
            // to be adding any new elements to the specific List<> instance

            /* ---------------------
             * 81) When to use a dictionary over list in C#
             * ---------------------- */

            // Find() method of the List class loops through each object in the list until a match is found.
            // So if you want to lookup a value using a key dictionary is better for performance over list.
            // So use dictionary when you know the collection will be primarily used for lookups.

            Country country1 = new Country()
            {
                Code = "USA", Name = "UNITED STATES", Capital = "Washington D.C."
            };
            Country country2 = new Country()
            {
                Code = "IND", Name = "INDIA", Capital = "New Delhi"
            };
            Country country3 = new Country()
            {
                Code = "CAN", Name = "CANADA", Capital = "Ottawa"
            };

            List <Country> listCountries = new List <Country>()
            {
                country1,
                country2,
                country3
            };

            string strUserChoice = string.Empty;

            do
            {
                Console.WriteLine("Please enter country code");
                string strCountryCode = Console.ReadLine().ToUpper();

                Country resultCountry = listCountries.Find(country => country.Code == strCountryCode);

                if (resultCountry == null)
                {
                    Console.WriteLine("Country code not valid");
                }
                else
                {
                    Console.WriteLine("Name = {0}, Capital = {1}", resultCountry.Name, resultCountry.Capital);
                }

                do
                {
                    Console.WriteLine("Do you want to continue? (Y or N)");
                    strUserChoice = Console.ReadLine().ToUpper();
                } while (strUserChoice != "Y" || strUserChoice != "N");
            } while (strUserChoice == "Y");

            // So for the above code, the data structure of List is not most efficient
            // as we are utilizing that data structure primarily for the use of retrieval.
            // Therefore since we would already know the 'key' using the dictionary would allow us
            // to retrieve the value we want in O(1) time. (lookups)
            // As compared to O(n) time for the List.

            Dictionary <string, Country> countriesTable = new Dictionary <string, Country>();

            countriesTable.Add(country1.Code, country1);
            countriesTable.Add(country2.Code, country2);
            countriesTable.Add(country3.Code, country3);

            do
            {
                Console.WriteLine("Please enter country code");
                string countryCodeInput = Console.ReadLine().ToUpper();

                // Need to check if a key is within a dictionary otherwise
                // .NET will throw an exception.
                if (countriesTable.ContainsKey(countryCodeInput))
                {
                    var resultCountry = countriesTable[countryCodeInput];
                    Console.WriteLine("Name = {0}, Capital = {1}", resultCountry.Name, resultCountry.Capital);
                }
                else
                {
                    Console.WriteLine("Country code not valid");
                }

                do
                {
                    Console.WriteLine("Do you want to continue? (Y or N)");
                    strUserChoice = Console.ReadLine().ToUpper();
                } while (strUserChoice != "Y" || strUserChoice != "N");
            } while (strUserChoice == "Y");

            /* ---------------------
             * 82) Generic queue collection class
             * ---------------------- */

            // Queue is a generic FIFO (first in, first out) collection class
            // that is found in the System.Collections.Generic namespace.
            // The first one to be added (enqueued) to the queue,
            // will also be the first item to be removed (dequeued).

            // To add items to the end, use Enqueue()

            // To remove at item that is at the beginning use Dequeue()

            // A foreach loop iterates through the queue, but will not remove it
            // from the queue.

            // To check if an item exists, use Contains()

            // Peek() can be used to return the item at the beginning of the queue,
            // without removing it.

            NewCustomer customer3 = new NewCustomer()
            {
                ID = 3, Name = "Dan", Salary = 72000
            };

            Queue <NewCustomer> queueCustomers = new Queue <NewCustomer>();

            queueCustomers.Enqueue(new NewCustomer()
            {
                ID = 1, Name = "Steven", Salary = 52000
            });
            queueCustomers.Enqueue(new NewCustomer()
            {
                ID = 2, Name = "Joe", Salary = 90000
            });
            queueCustomers.Enqueue(customer3);
            queueCustomers.Enqueue(new NewCustomer()
            {
                ID = 4, Name = "Tony", Salary = 65000
            });

            NewCustomer firstCustomer = queueCustomers.Dequeue();

            Console.WriteLine($"The first customer is {firstCustomer.Name}");
            Console.WriteLine($"Total items in the Queue = {queueCustomers.Count}");

            foreach (NewCustomer c in queueCustomers)
            {
                Console.WriteLine($"ID = {c.ID}, Name = {c.Name}");
            }

            // To get the item at the beginning of the queue without removing it.

            firstCustomer = queueCustomers.Peek();
            Console.WriteLine($"ID = {firstCustomer.ID}, Name = {firstCustomer.Name}");

            if (queueCustomers.Contains(customer3))
            {
                Console.WriteLine("Customer 3 is in the queue.");
            }
            else
            {
                Console.WriteLine("The customer does not exist in the queue.");
            }



            /* ---------------------
             * 83) Generic stack collection class
             * ---------------------- */

            // Stack is a generic LIFO (last in, first out) collection class that is present
            // in System.Colllections.Generic namespace.
            // Think of it as a stack of plates.
            // The last item is added (pushed) to the stack,
            // and will be the first item to be removed (popped) from the stack.

            // To insert an item at the top of the stack, use Push().

            // To remove and return the item at the top of the stack, use Pop()

            // A foreach loop iterates through the items in the stack, but it will not remove
            // them from the stack.
            // The last element added to the stack is the first one to be returned.

            // To check if an item exists in the stack use Contains()

            // Peek() returns the item at the top of the stack without removing it.

            Stack <NewCustomer> stackCustomers = new Stack <NewCustomer>();

            stackCustomers.Push(new NewCustomer()
            {
                ID = 1, Name = "Steven", Salary = 52000
            });
            stackCustomers.Push(new NewCustomer()
            {
                ID = 2, Name = "Joe", Salary = 90000
            });
            stackCustomers.Push(customer3);
            stackCustomers.Push(new NewCustomer()
            {
                ID = 4, Name = "Tony", Salary = 65000
            });

            NewCustomer customerOnStack = stackCustomers.Pop();

            Console.WriteLine($"First on stack is {customerOnStack.Name}");

            Console.WriteLine($"The amount of customers on the stack is {stackCustomers.Count}");

            foreach (NewCustomer current in stackCustomers)
            {
                Console.WriteLine($"The current customer on the stack is {current.Name}.");
            }

            Console.WriteLine($"Retrieve item on top without removing it: {stackCustomers.Peek()}");

            if (stackCustomers.Contains(customer3))
            {
                Console.WriteLine("Yes customer 3 is in the stack.");
            }
            else
            {
                Console.WriteLine("No customer 3 is not in the stack.");
            }

            /* ---------------------
             * 84) Real time example of queue collection class in C#
             * ---------------------- */



            /* ---------------------
             * 85) Real time example of stack collection class in C#
             * ---------------------- */

            /* ---------------------
             * 86) Multithreading in C#
             * ---------------------- */

            // What is a process?
            // A process is what the operating system uses to facilitate the execution of a program
            // by providing the resources required. Each process has a unique process id
            // associated with it. You can view the process within which a program is being
            // executing using windows task manager.

            // What is a thread?
            // A thread is a light weight process. A process has at least one thread which is
            // commonly called as main thread which actually executes the application code.
            // A single process can have multiple threads.

            // NOTE: All the threading related classes are present in System.Threading namespace.

            // So pass in a function name
            //Thread workerThread = new Thread(DoTimeConsumingWork);
            // So here we create a new thread such that the UI of the application remains responsive.
            // workerThread will start execution will begin with
            // workerThread.Start();

            // So the job is offloaded to the thread.
            // So the benefit of multithreading is that it can make your application more responsive,
            // as you can offload the work of time consuming functions.

            /* ---------------------
             * 87) Advantages and disadvantages of multithreading
             * ---------------------- */

            // Advantages of multithreading:
            // 1. To maintain a responsive user interface.
            // 2. To make efficient use of processor time while waiting for I/O operations to complete.
            // 3. To split large, CPU-bound tasks to be processed simultaneously on a machine that has
            // multiple processors/cores.
            // Multithreaded applications can improve the performance by taking advantages of multiple processors/cores
            // ability to have multiple threads.

            // Disadvantages of multithreading:
            // 1. On a single processor/core machine threading can affect performance negatively as there is overhead involved
            // with context-switching.
            // As the processor must allocate its time equally amongst its threads. Also time involved to switch between threads.
            // 2. Have to write more lines of code to accomplish the same task.
            // 3. Multitheading applications are difficult to write, understand, debug, and maintain.

            // NOTE: Only use multithreading when the advantages of doing so outweigh the disadvantages.

            /* ---------------------
             * 88) ThreadStart delegate
             * ---------------------- */

            // To create a THREAD, create an instance of Thread class and to it's constructor,
            // pass the name of the function that we want the thread to execute.

            Thread T1 = new Thread(Number.PrintNumbers);

            T1.Start();

            // Now rewrite it using ThreadStart delegate

            // There are 4 overloaded version of the Thread constructor,
            // One option is a ThreadStart delegate

            // A delegate is a type-safe function pointer.
            // The signature of the function which the delegate point to should match
            // the signature of the delegate otherwise you will get a compile error.

            Thread T2 = new Thread(new ThreadStart(Number.PrintNumbers));

            // Why does a delegate need to be passed as a parameter to the Thread class constructor?
            // The purpose of creating a Thread is to execute a function. A delegate is a type safe function pointer,
            // meaning that it points to a function that the thread has to execute.
            // In short, all threads require an entry point to start execution. Any thread you create will
            // need an explicitly defined entry point i.e. a pointer to the function where they should
            // begin execution.
            // So threads always require a delegate.

            // In the code showed above in the example where we just do
            // new Thread(Number.PrintNumbers)
            // It's working in spite of not creating the ThreadStart delegate explicitly because the
            // framework is doing it automatically for us.

            // You could also rewrite the same line using delegate() keyword
            Thread t3 = new Thread(delegate() { Number.PrintNumbers(); });

            // The same line written using lambda expression
            Thread t4 = new Thread(() => Number.PrintNumbers());

            // Thread functions do not need to be static functions always.
            // It can also be a non-static function. In the case of a non-static
            // function we have to create an instance of the class.

            Number number = new Number();
            Thread t5     = new Thread(number.DisplayNumbers);

            /* ---------------------
             * 89) ParameterizedThreadStart delegate
             * ---------------------- */

            // Use ParameterizedThreadStart delegate to pass data to the thread function.

            Console.WriteLine("Plesae enter the target number");
            object target = Console.ReadLine();

            ParameterizedThreadStart parameterizedThreadStart = new ParameterizedThreadStart(Number.OutputNumbers);
            Thread t6 = new Thread(parameterizedThreadStart);

            t6.Start(target);

            // Could also be written as...
            Console.WriteLine("Please enter the target number");
            object setTarget = Console.ReadLine();

            Thread t7 = new Thread(Number.OutputNumbers);

            t7.Start(setTarget);

            // Here we are not explicitly creating an instance of ParameterizedThreadStart
            // delegate. Then how is it working?

            // It's working because the compiler implicitly converts
            // new Thread(Number.PrintNumbers);
            // to
            // new Thread(new ParameterizedThreadStart(Number.PrintNumbers));

            // When to use ParameterizedThreadStart over ThreadStart delegate?
            // Use parameterizedThreadStart delegate if you have some data to pass to the
            // Thread function. Otherwise just use ThreadStart delegate.

            // NOTE: Using ParameterizedThreadStart delegate and Thread.Start(Object)
            // method to pass data to the thread function is not type safe as they operate
            // on object datatype and any type of data can be passed.

            // If you try to change the data type of the target parameter of OutputNumbers() function
            // from object to int, a compiler error will be raised as the signature of OutputNumbers()
            // function does not match with the signature of
            // ParameterizedThreadStart delegate.


            /* ---------------------
             * 90) Passing data to the Thread function in a type safe manner
             * ---------------------- */

            // To pass data to the Thread function in a type safe manner,
            // encapsulate the thread function and the data it needs in a helper class
            // and use the ThreadStart delegate to execute the thread functino.

            Console.WriteLine("Please enter the target number");
            int target8 = Convert.ToInt32(Console.ReadLine());

            MyNumber myNumber = new MyNumber(target8);
            Thread   t8       = new Thread(new ThreadStart(myNumber.PrintNumbers));

            t8.Start();
        }
 private static int CompareCustomer(NewCustomer x, NewCustomer y)
 {
     return(x.ID.CompareTo(y.ID));
 }