cs11dotnet7/notebooks/Chapter08.dib

879 lines
20 KiB
Plaintext
Raw Normal View History

2022-02-09 11:30:33 +01:00
#!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<string> 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<string> 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<string> cities = new()
{ "London", "Paris", "Milan" };
*/
/* Alternative syntax that passes an
array of string values to AddRange method
List<string> 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<string, string> 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<string, string> 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<string, string> 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<string, string> 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<string> 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<TElement, TPriority>(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<string, int> 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<string> 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<string> cities = new()
{ "London", "Paris", "Milan" };
*/
/* Alternative syntax that passes an
array of string values to AddRange method
List<string> 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<string> immutableCities = cities.ToImmutableList();
ImmutableList<string> 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<char> nameAsSpan = name.AsSpan();
ReadOnlySpan<char> firstNameSpan = nameAsSpan[0..lengthOfFirst];
ReadOnlySpan<char> 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<Attribute> attributes = assembly.GetCustomAttributes();
WriteLine($" Assembly-level attributes:");
foreach (Attribute a in attributes)
{
WriteLine($" {a.GetType()}");
}
#!csharp
#nullable enable
AssemblyInformationalVersionAttribute? version = assembly
.GetCustomAttribute<AssemblyInformationalVersionAttribute>();
WriteLine($" Version: {version?.InformationalVersion}");
AssemblyCompanyAttribute? company = assembly
.GetCustomAttribute<AssemblyCompanyAttribute>();
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<string> 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);