#!markdown # Chapter 8 - Working with Common .NET Types Execute the following code cell to make `Console` methods available in every code cell in this notebook. #!csharp using static System.Console; #!markdown # Working with numbers One of the most common types of data is numbers. #!csharp using System.Numerics; #!markdown ## Working with big integers #!csharp WriteLine("Working with large integers:"); WriteLine("----------------------------------------"); ulong big = ulong.MaxValue; WriteLine($"{big,40:N0}"); BigInteger bigger = BigInteger.Parse("123456789012345678901234567890"); WriteLine($"{bigger,40:N0}"); #!markdown ## Working with complex numbers #!csharp WriteLine("Working with complex numbers:"); Complex c1 = new(real: 4, imaginary: 2); Complex c2 = new(real: 3, imaginary: 7); Complex c3 = c1 + c2; // output using default ToString implementation WriteLine($"{c1} added to {c2} is {c3}"); // output using custom format WriteLine("{0} + {1}i added to {2} + {3}i is {4} + {5}i", c1.Real, c1.Imaginary, c2.Real, c2.Imaginary, c3.Real, c3.Imaginary); #!markdown # Working with text One of the other most common types of data for variables is text. #!csharp string city = "London"; #!markdown ## Getting the length of a string #!csharp WriteLine($"{city} is {city.Length} characters long."); #!markdown ## Getting the characters of a string #!csharp WriteLine($"First char is {city[0]} and third is {city[2]}."); #!markdown ## Splitting a string #!csharp string cities = "Paris,Tehran,Chennai,Sydney,New York,MedellĂ­n"; string[] citiesArray = cities.Split(','); WriteLine($"There are {citiesArray.Length} items in the array."); foreach (string item in citiesArray) { WriteLine(item); } #!markdown ## Getting part of a string Sometimes, you need to get part of some text. The `IndexOf` method has nine overloads that return the index position of a specified `char` or `string` within a `string`. The `Substring` method has two overloads, as shown in the following list: - `Substring(startIndex, length)`: returns a substring starting at startIndex and containing the next length characters. - `Substring(startIndex)`: returns a substring starting at startIndex and containing all characters up to the end of the string. #!csharp string fullName = "Alan Jones"; int indexOfTheSpace = fullName.IndexOf(' '); string firstName = fullName.Substring( startIndex: 0, length: indexOfTheSpace); string lastName = fullName.Substring( startIndex: indexOfTheSpace + 1); WriteLine($"Original: {fullName}"); WriteLine($"Swapped: {lastName}, {firstName}"); #!markdown ## Checking a string for content Sometimes, you need to check whether a piece of text starts or ends with some characters or contains some characters. You can achieve this with methods named `StartsWith`, `EndsWith`, and `Contains`: #!csharp string company = "Microsoft"; bool startsWithM = company.StartsWith("M"); bool containsN = company.Contains("N"); WriteLine($"Text: {company}"); WriteLine($"Starts with M: {startsWithM}, contains an N: {containsN}"); #!markdown ## Joining, formatting, and other string members #!csharp string recombined = string.Join(" => ", citiesArray); WriteLine(recombined); #!csharp string fruit = "Apples"; decimal price = 0.39M; DateTime when = DateTime.Today; WriteLine($"Interpolated: {fruit} cost {price:C} on {when:dddd}."); WriteLine(string.Format("string.Format: {0} cost {1:C} on {2:dddd}.", arg0: fruit, arg1: price, arg2: when)); #!markdown # Working with dates and times After numbers and text, the next most popular types of data to work with are dates and times. #!markdown ## Specifying date and time values #!csharp WriteLine("Earliest date/time value is: {0}", arg0: DateTime.MinValue); WriteLine("UNIX epoch date/time value is: {0}", arg0: DateTime.UnixEpoch); WriteLine("Date/time value Now is: {0}", arg0: DateTime.Now); WriteLine("Date/time value Today is: {0}", arg0: DateTime.Today); #!csharp DateTime christmas = new(year: 2021, month: 12, day: 25); WriteLine("Christmas: {0}", arg0: christmas); // default format WriteLine("Christmas: {0:dddd, dd MMMM yyyy}", arg0: christmas); // custom format WriteLine("Christmas is in month {0} of the year.", arg0: christmas.Month); WriteLine("Christmas is day {0} of the year.", arg0: christmas.DayOfYear); WriteLine("Christmas {0} is on a {1}.", arg0: christmas.Year, arg1: christmas.DayOfWeek); #!csharp DateTime beforeXmas = christmas.Subtract(TimeSpan.FromDays(12)); DateTime afterXmas = christmas.AddDays(12); WriteLine("12 days before Christmas is: {0}", arg0: beforeXmas); WriteLine("12 days after Christmas is: {0}", arg0: afterXmas); TimeSpan untilChristmas = christmas - DateTime.Now; WriteLine("There are {0} days and {1} hours until Christmas.", arg0: untilChristmas.Days, arg1: untilChristmas.Hours); WriteLine("There are {0:N0} hours until Christmas.", arg0: untilChristmas.TotalHours); #!csharp DateTime kidsWakeUp = new( year: 2021, month: 12, day: 25, hour: 6, minute: 30, second: 0); WriteLine("Kids wake up on Christmas: {0}", arg0: kidsWakeUp); WriteLine("The kids woke me up at {0}", arg0: kidsWakeUp.ToShortTimeString()); #!markdown ## Globalization with dates and times The current culture controls how dates and times are parsed: #!csharp using System.Globalization; #!csharp // use the following line to switch to British culture CultureInfo.CurrentCulture = CultureInfo.GetCultureInfo("en-GB"); WriteLine("Current culture is: {0}", arg0: CultureInfo.CurrentCulture.Name); string textDate = "4 July 2021"; DateTime independenceDay = DateTime.Parse(textDate); WriteLine("Text: {0}, DateTime: {1:d MMMM}", arg0: textDate, arg1: independenceDay); textDate = "7/4/2021"; independenceDay = DateTime.Parse(textDate); WriteLine("Text: {0}, DateTime: {1:d MMMM}", arg0: textDate, arg1: independenceDay); independenceDay = DateTime.Parse(textDate, provider: CultureInfo.GetCultureInfo("en-US")); WriteLine("Text: {0}, DateTime: {1:d MMMM}", arg0: textDate, arg1: independenceDay); #!csharp for (int year = 2020; year < 2026; year++) { Write($"{year} is a leap year: {DateTime.IsLeapYear(year)}. "); WriteLine("There are {0} days in February {1}.", arg0: DateTime.DaysInMonth(year: year, month: 2), arg1: year); } WriteLine("Is Christmas daylight saving time? {0}", arg0: christmas.IsDaylightSavingTime()); WriteLine("Is July 4th daylight saving time? {0}", arg0: independenceDay.IsDaylightSavingTime()); #!markdown ## Working with only a date or a time .NET 6 introduces some new types for working with only a date value or only a time value named DateOnly and TimeOnly. #!csharp DateOnly queensBirthday = new(year: 2022, month: 4, day: 21); WriteLine($"The Queen's next birthday is on {queensBirthday}."); TimeOnly partyStarts = new(hour: 20, minute: 30); WriteLine($"The Queen's party starts at {partyStarts}."); DateTime calendarEntry = queensBirthday.ToDateTime(partyStarts); WriteLine($"Add to your calendar: {calendarEntry}."); #!markdown # Pattern matching with regular expressions Regular expressions are useful for validating input from the user. #!csharp using System.Text.RegularExpressions; #!markdown ## Checking for digits entered as text #!csharp #nullable enable // required to support string? // Write("Enter your age: "); string? input = "34"; // ReadLine(); Regex ageChecker = new(@"^\d+$"); if (ageChecker.IsMatch(input)) { WriteLine("Thank you!"); } else { WriteLine($"This is not a valid age: {input}"); } #!markdown ## Splitting a complex comma-separated string Earlier in this chapter, you learned how to split a simple comma-separated `string` variable. But what about the following example of film titles? ``` "Monsters, Inc.","I, Tonya","Lock, Stock and Two Smoking Barrels" ``` #!csharp string films = "\"Monsters, Inc.\",\"I, Tonya\",\"Lock, Stock and Two Smoking Barrels\""; WriteLine($"Films to split: {films}"); string[] filmsDumb = films.Split(','); WriteLine("Splitting with string.Split method:"); foreach (string film in filmsDumb) { WriteLine(film); } #!csharp WriteLine(); Regex csv = new( "(?:^|,)(?=[^\"]|(\")?)\"?((?(1)[^\"]*|[^,\"]*))\"?(?=,|$)"); MatchCollection filmsSmart = csv.Matches(films); WriteLine("Splitting with regular expression:"); foreach (Match film in filmsSmart) { WriteLine(film.Groups[2].Value); } #!markdown # Storing multiple objects in collections Another of the most common types of data is collections. If you need to store multiple values in a variable, then you can use a collection. #!csharp static void Output(string title, IEnumerable collection) { WriteLine(title); foreach (string item in collection) { WriteLine($" {item}"); } } #!markdown ## Working with lists #!csharp static void WorkingWithLists() { // Simple syntax for creating a list and adding three items List cities = new(); cities.Add("London"); cities.Add("Paris"); cities.Add("Milan"); /* Alternative syntax that is converted by the compiler into the three Add method calls above List cities = new() { "London", "Paris", "Milan" }; */ /* Alternative syntax that passes an array of string values to AddRange method List cities = new(); cities.AddRange(new[] { "London", "Paris", "Milan" }); */ Output("Initial list", cities); WriteLine($"The first city is {cities[0]}."); WriteLine($"The last city is {cities[cities.Count - 1]}."); cities.Insert(0, "Sydney"); Output("After inserting Sydney at index 0", cities); cities.RemoveAt(1); cities.Remove("Milan"); Output("After removing two cities", cities); } #!csharp WorkingWithLists(); #!markdown ## Working with dictionaries #!csharp static void WorkingWithDictionaries() { Dictionary keywords = new(); // add using named parameters keywords.Add(key: "int", value: "32-bit integer data type"); // add using positional parameters keywords.Add("long", "64-bit integer data type"); keywords.Add("float", "Single precision floating point number"); /* Alternative syntax; compiler converts this to calls to Add method Dictionary keywords = new() { { "int", "32-bit integer data type" }, { "long", "64-bit integer data type" }, { "float", "Single precision floating point number" }, }; */ /* Alternative syntax; compiler converts this to calls to Add method Dictionary keywords = new() { ["int"] = "32-bit integer data type", ["long"] = "64-bit integer data type", ["float"] = "Single precision floating point number" }; */ Output("Dictionary keys:", keywords.Keys); Output("Dictionary values:", keywords.Values); WriteLine("Keywords and their definitions"); foreach (KeyValuePair item in keywords) { WriteLine($" {item.Key}: {item.Value}"); } // lookup a value using a key string key = "long"; WriteLine($"The definition of {key} is {keywords[key]}"); } #!csharp WorkingWithDictionaries(); #!markdown ## Working with queues #!csharp static void WorkingWithQueues() { Queue coffee = new(); coffee.Enqueue("Damir"); // front of queue coffee.Enqueue("Andrea"); coffee.Enqueue("Ronald"); coffee.Enqueue("Amin"); coffee.Enqueue("Irina"); // back of queue Output("Initial queue from front to back", coffee); // server handles next person in queue string served = coffee.Dequeue(); WriteLine($"Served: {served}."); // server handles next person in queue served = coffee.Dequeue(); WriteLine($"Served: {served}."); Output("Current queue from front to back", coffee); WriteLine($"{coffee.Peek()} is next in line."); Output("Current queue from front to back", coffee); } #!csharp WorkingWithQueues(); #!csharp static void OutputPQ(string title, IEnumerable<(TElement Element, TPriority Priority)> collection) { WriteLine(title); foreach ((TElement, TPriority) item in collection) { WriteLine($" {item.Item1}: {item.Item2}"); } } #!csharp static void WorkingWithPriorityQueues() { PriorityQueue vaccine = new(); // add some people // 1 = high priority people in their 70s or poor health // 2 = medium priority e.g. middle aged // 3 = low priority e.g. teens and twenties vaccine.Enqueue("Pamela", 1); // my mum (70s) vaccine.Enqueue("Rebecca", 3); // my niece (teens) vaccine.Enqueue("Juliet", 2); // my sister (40s) vaccine.Enqueue("Ian", 1); // my dad (70s) OutputPQ("Current queue for vaccination:", vaccine.UnorderedItems); WriteLine($"{vaccine.Dequeue()} has been vaccinated."); WriteLine($"{vaccine.Dequeue()} has been vaccinated."); OutputPQ("Current queue for vaccination:", vaccine.UnorderedItems); WriteLine($"{vaccine.Dequeue()} has been vaccinated."); vaccine.Enqueue("Mark", 2); // me (40s) WriteLine($"{vaccine.Peek()} will be next to be vaccinated."); OutputPQ("Current queue for vaccination:", vaccine.UnorderedItems); } #!csharp WorkingWithPriorityQueues(); #!markdown ## Using immutable collections #!csharp using System.Collections.Immutable; #!csharp static void WorkingWithLists2() { // Simple syntax for creating a list and adding three items List cities = new(); cities.Add("London"); cities.Add("Paris"); cities.Add("Milan"); /* Alternative syntax that is converted by the compiler into the three Add method calls above List cities = new() { "London", "Paris", "Milan" }; */ /* Alternative syntax that passes an array of string values to AddRange method List cities = new(); cities.AddRange(new[] { "London", "Paris", "Milan" }); */ Output("Initial list", cities); WriteLine($"The first city is {cities[0]}."); WriteLine($"The last city is {cities[cities.Count - 1]}."); cities.Insert(0, "Sydney"); Output("After inserting Sydney at index 0", cities); cities.RemoveAt(1); cities.Remove("Milan"); Output("After removing two cities", cities); ImmutableList immutableCities = cities.ToImmutableList(); ImmutableList newList = immutableCities.Add("Rio"); Output("Immutable list of cities:", immutableCities); Output("New list of cities:", newList); } #!csharp WorkingWithLists2(); #!markdown # Working with spans, indexes, and ranges C# 8.0 introduced two features for identifying an item's index within an array and a range of items using two indexes. #!markdown ## Identifying ranges with the Range type #!csharp Range r1 = new(start: new Index(3), end: new Index(7)); Range r2 = new(start: 3, end: 7); // using implicit int conversion Range r3 = 3..7; // using C# 8.0 or later syntax Range r4 = Range.StartAt(3); // from index 3 to last index Range r5 = 3..; // from index 3 to last index Range r6 = Range.EndAt(3); // from index 0 to index 3 Range r7 = ..3; // from index 0 to index 3 #!markdown ## Using indexes, ranges, and spans #!csharp string name = "Samantha Jones"; // Using Substring int lengthOfFirst = name.IndexOf(' '); int lengthOfLast = name.Length - lengthOfFirst - 1; string firstName = name.Substring( startIndex: 0, length: lengthOfFirst); string lastName = name.Substring( startIndex: name.Length - lengthOfLast, length: lengthOfLast); WriteLine($"First name: {firstName}, Last name: {lastName}"); // Using spans ReadOnlySpan nameAsSpan = name.AsSpan(); ReadOnlySpan firstNameSpan = nameAsSpan[0..lengthOfFirst]; ReadOnlySpan lastNameSpan = nameAsSpan[^lengthOfLast..^0]; WriteLine("First name: {0}, Last name: {1}", arg0: firstNameSpan.ToString(), arg1: lastNameSpan.ToString()); #!markdown # Working with network resources Sometimes you will need to work with network resources. #!markdown ## Working with URIs, DNS, and IP addresses #!csharp using System.Net; // IPHostEntry, Dns, IPAddress #!csharp #nullable enable // Write("Enter a valid web address: "); string? url = "http://google.com"; // ReadLine(); if (string.IsNullOrWhiteSpace(url)) { url = "https://stackoverflow.com/search?q=securestring"; } Uri uri = new(url); WriteLine($"URL: {url}"); WriteLine($"Scheme: {uri.Scheme}"); WriteLine($"Port: {uri.Port}"); WriteLine($"Host: {uri.Host}"); WriteLine($"Path: {uri.AbsolutePath}"); WriteLine($"Query: {uri.Query}"); #!csharp IPHostEntry entry = Dns.GetHostEntry(uri.Host); WriteLine($"{entry.HostName} has the following IP addresses:"); foreach (IPAddress address in entry.AddressList) { WriteLine($" {address} ({address.AddressFamily})"); } #!markdown ## Pinging a server #!csharp using System.Net.NetworkInformation; // Ping, PingReply, IPStatus #!csharp try { Ping ping = new(); WriteLine("Pinging server. Please wait..."); PingReply reply = ping.Send(uri.Host); WriteLine($"{uri.Host} was pinged and replied: {reply.Status}."); if (reply.Status == IPStatus.Success) { WriteLine("Reply from {0} took {1:N0}ms", arg0: reply.Address, arg1: reply.RoundtripTime); } } catch (Exception ex) { WriteLine($"{ex.GetType().ToString()} says {ex.Message}"); } #!markdown # Working with reflection and attributes Reflection is a programming feature that allows code to understand and manipulate itself. #!csharp using System.Reflection; // Assembly #!markdown ## Reading assembly metadata Note: This example works best in a console application. In a .NET Interactive notebook, the assembly is `Microsoft.DotNet.Interactive.App`. #!csharp #nullable enable WriteLine("Assembly metadata:"); Assembly? assembly = Assembly.GetEntryAssembly(); if (assembly is null) { WriteLine("Failed to get entry assembly."); return; } WriteLine($" Full name: {assembly.FullName}"); WriteLine($" Location: {assembly.Location}"); IEnumerable attributes = assembly.GetCustomAttributes(); WriteLine($" Assembly-level attributes:"); foreach (Attribute a in attributes) { WriteLine($" {a.GetType()}"); } #!csharp #nullable enable AssemblyInformationalVersionAttribute? version = assembly .GetCustomAttribute(); WriteLine($" Version: {version?.InformationalVersion}"); AssemblyCompanyAttribute? company = assembly .GetCustomAttribute(); WriteLine($" Company: {company?.Company}"); #!markdown ## Creating custom attributes This section should only be tried in a console application. #!markdown # Working with images ImageSharp is a third-party cross-platform 2D graphics library. #!csharp #r "nuget:SixLabors.ImageSharp,1.0.3" #!csharp using SixLabors.ImageSharp; using SixLabors.ImageSharp.Processing; using System.IO; #!csharp string imagesFolder = Path.Combine( Environment.CurrentDirectory, "images"); IEnumerable images = Directory.EnumerateFiles(imagesFolder); foreach (string imagePath in images) { string thumbnailPath = Path.Combine( Environment.CurrentDirectory, "images", Path.GetFileNameWithoutExtension(imagePath) + "-thumbnail" + Path.GetExtension(imagePath)); using (Image image = Image.Load(imagePath)) { image.Mutate(x => x.Resize(image.Width / 10, image.Height / 10)); image.Mutate(x => x.Grayscale()); image.Save(thumbnailPath); } } WriteLine("Image processing complete. View the images folder."); #!markdown # Internationalizing your code #!csharp using System.Globalization; // CultureInfo #!csharp #nullable enable CultureInfo globalization = CultureInfo.CurrentCulture; CultureInfo localization = CultureInfo.CurrentUICulture; WriteLine("The current globalization culture is {0}: {1}", globalization.Name, globalization.DisplayName); WriteLine("The current localization culture is {0}: {1}", localization.Name, localization.DisplayName); WriteLine(); WriteLine("en-US: English (United States)"); WriteLine("da-DK: Danish (Denmark)"); WriteLine("fr-CA: French (Canada)"); // Write("Enter an ISO culture code: "); string? newCulture = "da-DK"; // ReadLine(); if (!string.IsNullOrEmpty(newCulture)) { CultureInfo ci = new(newCulture); // change the current cultures CultureInfo.CurrentCulture = ci; CultureInfo.CurrentUICulture = ci; } WriteLine(); // Write("Enter your name: "); string? name = "Mikkel"; // ReadLine(); // Write("Enter your date of birth: "); string? dob = "12/3/1980"; // ReadLine(); // Write("Enter your salary: "); string? salary = "340000"; // ReadLine(); DateTime date = DateTime.Parse(dob); int minutes = (int)DateTime.Today.Subtract(date).TotalMinutes; decimal earns = decimal.Parse(salary); WriteLine( "{0} was born on a {1:dddd}, is {2:N0} minutes old, and earns {3:C}", name, date, minutes, earns);