cs11dotnet7/notebooks/Chapter05.dib

656 lines
13 KiB
Plaintext
Raw Normal View History

2022-02-09 11:30:33 +01:00
#!markdown
# Chapter 5 - Building Your Own Types with Object-Oriented Programming
Execute the following code cell to make `Console` methods available in every code cell in this notebook.
We cannot create a class library in a .NET Interactive notebook so we will put all code in the same notebook.
#!csharp
using static System.Console;
using System.Collections.Generic;
#!markdown
## Storing a value using an enum type (defining the enum)
#!csharp
[System.Flags]
public enum WondersOfTheAncientWorld : byte
{
None = 0b_0000_0000, // i.e. 0
GreatPyramidOfGiza = 0b_0000_0001, // i.e. 1
HangingGardensOfBabylon = 0b_0000_0010, // i.e. 2
StatueOfZeusAtOlympia = 0b_0000_0100, // i.e. 4
TempleOfArtemisAtEphesus = 0b_0000_1000, // i.e. 8
MausoleumAtHalicarnassus = 0b_0001_0000, // i.e. 16
ColossusOfRhodes = 0b_0010_0000, // i.e. 32
LighthouseOfAlexandria = 0b_0100_0000 // i.e. 64
}
#!markdown
## Defining a class
.NET Interactive notebooks do not support namespaces. You would get a "Cannot declare namesapce in script code" error. But we can declare a class.
#!markdown
## Storing a value using an enum type (using the enum)
#!csharp
public class Person : object
{
// fields
public string Name;
public DateTime DateOfBirth;
public WondersOfTheAncientWorld FavoriteAncientWonder;
public WondersOfTheAncientWorld BucketList;
public List<Person> Children = new();
// constants
public const string Species = "Homo Sapien";
// read-only fields
public readonly string HomePlanet = "Earth";
public readonly DateTime Instantiated;
// constructors
public Person()
{
// set default values for fields
// including read-only fields
Name = "Unknown";
Instantiated = DateTime.Now;
}
public Person(string initialName, string homePlanet)
{
Name = initialName;
HomePlanet = homePlanet;
Instantiated = DateTime.Now;
}
// methods
public void WriteToConsole()
{
WriteLine($"{Name} was born on a {DateOfBirth:dddd}.");
}
public string GetOrigin()
{
return $"{Name} was born on {HomePlanet}.";
}
public (string, int) GetFruit()
{
return ("Apples", 5);
}
public (string Name, int Number) GetNamedFruit()
{
return (Name: "Apples", Number: 5);
}
public string SayHello()
{
return $"{Name} says 'Hello!'";
}
public string SayHello(string name)
{
return $"{Name} says 'Hello {name}!'";
}
public string OptionalParameters(
string command = "Run!",
double number = 0.0,
bool active = true)
{
return string.Format(
format: "command is {0}, number is {1}, active is {2}",
arg0: command,
arg1: number,
arg2: active);
}
public void PassingParameters(int x, ref int y, out int z)
{
// out parameters cannot have a default
// AND must be initialized inside the method
z = 99;
// increment each parameter
x++;
y++;
z++;
}
// a property defined using C# 1 - 5 syntax
public string Origin
{
get
{
return $"{Name} was born on {HomePlanet}";
}
}
// two properties defined using C# 6+ lambda expression body syntax
public string Greeting => $"{Name} says 'Hello!'";
public int Age => System.DateTime.Today.Year - DateOfBirth.Year;
public string FavoriteIceCream { get; set; } // auto-syntax
private string favoritePrimaryColor;
public string FavoritePrimaryColor
{
get
{
return favoritePrimaryColor;
}
set
{
switch (value.ToLower())
{
case "red":
case "green":
case "blue":
favoritePrimaryColor = value;
break;
default:
throw new System.ArgumentException(
$"{value} is not a primary color. " +
"Choose from: red, green, blue.");
}
}
}
// indexers
public Person this[int index]
{
get
{
return Children[index]; // pass on to the List<T> indexer
}
set
{
Children[index] = value;
}
}
// end of class
}
#!markdown
## Instantiating a class
The namespace for a class defined in a .NET Interactive notebook follows the pattern `Submission#[number]+[classname]`.
#!csharp
// Person bob = new Person(); // C# 1.0 or later
// var bob = new Person(); // C# 3.0 or later
Person bob = new(); // C# 9.0 or later
WriteLine(bob.ToString());
bob.Name = "Bob Smith";
bob.DateOfBirth = new DateTime(1965, 12, 22); // C# 1.0 or later
WriteLine(format: "{0} was born on {1:dddd, d MMMM yyyy}",
arg0: bob.Name,
arg1: bob.DateOfBirth);
Person alice = new()
{
Name = "Alice Jones",
DateOfBirth = new(1998, 3, 7) // C# 9.0 or later
};
WriteLine(format: "{0} was born on {1:dd MMM yy}",
arg0: alice.Name,
arg1: alice.DateOfBirth);
#!csharp
bob.FavoriteAncientWonder = WondersOfTheAncientWorld.StatueOfZeusAtOlympia;
WriteLine(
format: "{0}'s favorite wonder is {1}. Its integer is {2}.",
arg0: bob.Name,
arg1: bob.FavoriteAncientWonder,
arg2: (int)bob.FavoriteAncientWonder);
bob.BucketList =
WondersOfTheAncientWorld.HangingGardensOfBabylon
| WondersOfTheAncientWorld.MausoleumAtHalicarnassus;
// bob.BucketList = (WondersOfTheAncientWorld)18;
WriteLine($"{bob.Name}'s bucket list is {bob.BucketList}");
#!markdown
## Storing multiple values using collections
#!csharp
bob.Children.Add(new Person { Name = "Alfred" });
bob.Children.Add(new Person { Name = "Zoe" });
WriteLine(
$"{bob.Name} has {bob.Children.Count} children:");
for (int childIndex = 0; childIndex < bob.Children.Count; childIndex++)
{
WriteLine($" {bob.Children[childIndex].Name}");
}
#!markdown
## Making a field static
#!csharp
public class BankAccount
{
public string AccountName; // instance member
public decimal Balance; // instance member
public static decimal InterestRate; // shared member
}
#!csharp
BankAccount.InterestRate = 0.012M; // store a shared value
var jonesAccount = new BankAccount();
jonesAccount.AccountName = "Mrs. Jones";
jonesAccount.Balance = 2400;
WriteLine(format: "{0} earned {1:C} interest.",
arg0: jonesAccount.AccountName,
arg1: jonesAccount.Balance * BankAccount.InterestRate);
var gerrierAccount = new BankAccount();
gerrierAccount.AccountName = "Ms. Gerrier";
gerrierAccount.Balance = 98;
WriteLine(format: "{0} earned {1:C} interest.",
arg0: gerrierAccount.AccountName,
arg1: gerrierAccount.Balance * BankAccount.InterestRate);
#!markdown
## Making a field constant
#!csharp
WriteLine($"{bob.Name} is a {Person.Species}");
#!markdown
## Making a field read-only
#!csharp
WriteLine($"{bob.Name} was born on {bob.HomePlanet}");
#!markdown
## Initializing fields with constructors
#!csharp
var blankPerson = new Person();
WriteLine(format:
"{0} of {1} was created at {2:hh:mm:ss} on a {2:dddd}.",
arg0: blankPerson.Name,
arg1: blankPerson.HomePlanet,
arg2: blankPerson.Instantiated);
#!markdown
## Defining multiple constructors
#!csharp
var gunny = new Person("Gunny", "Mars");
WriteLine(format:
"{0} of {1} was created at {2:hh:mm:ss} on a {2:dddd}.",
arg0: gunny.Name,
arg1: gunny.HomePlanet,
arg2: gunny.Instantiated);
#!markdown
## Returning values from methods
#!csharp
bob.WriteToConsole();
WriteLine(bob.GetOrigin());
#!markdown
## Combining multiple returned values using tuples
#!csharp
public class TextAndNumber
{
public string Text;
public int Number;
}
public class LifeTheUniverseAndEverything
{
public TextAndNumber GetTheData()
{
return new TextAndNumber
{
Text = "What's the meaning of life?",
Number = 42
};
}
}
#!markdown
### Language support for tuples
#!csharp
(string, int) fruit = bob.GetFruit();
WriteLine($"{fruit.Item1}, {fruit.Item2} there are.");
#!markdown
### Naming the fields of a tuple
#!csharp
var fruitNamed = bob.GetNamedFruit();
WriteLine($"There are {fruitNamed.Number} {fruitNamed.Name}.");
#!markdown
### Inferring tuple names
#!csharp
var thing1 = ("Neville", 4);
WriteLine($"{thing1.Item1} has {thing1.Item2} children.");
var thing2 = (bob.Name, bob.Children.Count);
WriteLine($"{thing2.Name} has {thing2.Count} children.");
#!markdown
### Deconstructing tuples
#!csharp
(string fruitName, int fruitNumber) = bob.GetFruit();
WriteLine($"Deconstructed: {fruitName}, {fruitNumber}");
#!markdown
## Defining and passing parameters to methods
#!csharp
WriteLine(bob.SayHello());
WriteLine(bob.SayHello("Emily"));
#!markdown
## Passing optional and named parameters
#!csharp
WriteLine(bob.OptionalParameters());
#!csharp
WriteLine(bob.OptionalParameters("Jump!", 98.5));
#!csharp
WriteLine(bob.OptionalParameters(
number: 52.7, command: "Hide!"));
#!csharp
WriteLine(bob.OptionalParameters("Poke!", active: false));
#!markdown
## Controlling how parameters are passed
When a parameter is passed into a method, it can be passed in one of three ways:
- By value (this is the default): Think of these as being in-only.
- By reference as a ref parameter: Think of these as being in-and-out.
- As an out parameter: Think of these as being out-only.
#!csharp
int a = 10;
int b = 20;
int c = 30;
WriteLine($"Before: a = {a}, b = {b}, c = {c}");
bob.PassingParameters(a, ref b, out c);
WriteLine($"After: a = {a}, b = {b}, c = {c}");
#!markdown
## Simplified out parameters
In C# 7.0 and later, we can simplify code that uses the out variables.
#!csharp
int d = 10;
int e = 20;
WriteLine($"Before: d = {d}, e = {e}, f doesn't exist yet!");
// simplified C# 7.0 or later syntax for the out parameter
bob.PassingParameters(d, ref e, out int f);
WriteLine($"After: d = {d}, e = {e}, f = {f}");
#!markdown
# Splitting classes using partial
Partial classes are not supported in .NET Interactive notebooks. I have added the code to the original class near the top of this notebook.
#!csharp
Person sam = new()
{
Name = "Sam",
DateOfBirth = new(1972, 1, 27)
};
WriteLine(sam.Origin);
WriteLine(sam.Greeting);
WriteLine(sam.Age);
#!csharp
sam.FavoriteIceCream = "Chocolate Fudge";
WriteLine($"Sam's favorite ice-cream flavor is {sam.FavoriteIceCream}.");
sam.FavoritePrimaryColor = "Red";
WriteLine($"Sam's favorite primary color is {sam.FavoritePrimaryColor}.");
#!markdown
# Defining indexers
#!csharp
sam.Children.Add(new() { Name = "Charlie" });
sam.Children.Add(new() { Name = "Ella" });
WriteLine($"Sam's first child is {sam.Children[0].Name}");
WriteLine($"Sam's second child is {sam.Children[1].Name}");
WriteLine($"Sam's first child is {sam[0].Name}");
WriteLine($"Sam's second child is {sam[1].Name}");
#!markdown
# Pattern matching with objects
#!csharp
public class BusinessClassPassenger
{
public override string ToString()
{
return $"Business Class";
}
}
public class FirstClassPassenger
{
public int AirMiles { get; set; }
public override string ToString()
{
return $"First Class with {AirMiles:N0} air miles";
}
}
public class CoachClassPassenger
{
public double CarryOnKG { get; set; }
public override string ToString()
{
return $"Coach Class with {CarryOnKG:N2} KG carry on";
}
}
#!csharp
object[] passengers = {
new FirstClassPassenger { AirMiles = 1_419 },
new FirstClassPassenger { AirMiles = 16_562 },
new BusinessClassPassenger(),
new CoachClassPassenger { CarryOnKG = 25.7 },
new CoachClassPassenger { CarryOnKG = 0 },
};
foreach (object passenger in passengers)
{
decimal flightCost = passenger switch
{
/*
FirstClassPassenger p when p.AirMiles > 35000 => 1500M,
FirstClassPassenger p when p.AirMiles > 15000 => 1750M,
FirstClassPassenger _ => 2000M, */
// C# 9 or later syntax
FirstClassPassenger p => p.AirMiles switch
{
> 35000 => 1500M,
> 15000 => 1750M,
_ => 2000M
},
BusinessClassPassenger => 1000M,
CoachClassPassenger p when p.CarryOnKG < 10.0 => 500M,
CoachClassPassenger => 650M,
_ => 800M
};
WriteLine($"Flight costs {flightCost:C} for {passenger}");
}
#!markdown
# Working with records
#!markdown
## Init-only properties
#!csharp
#nullable enable
public class ImmutablePerson
{
public string? FirstName { get; init; }
public string? LastName { get; init; }
}
#!csharp
ImmutablePerson jeff = new()
{
FirstName = "Jeff",
LastName = "Winger"
};
jeff.FirstName = "Geoff";
#!markdown
## Understanding records
#!csharp
#nullable enable
public record ImmutableVehicle
{
public int Wheels { get; init; }
public string? Color { get; init; }
public string? Brand { get; init; }
}
#!csharp
ImmutableVehicle car = new()
{
Brand = "Mazda MX-5 RF",
Color = "Soul Red Crystal Metallic",
Wheels = 4
};
ImmutableVehicle repaintedCar = car
with { Color = "Polymetal Grey Metallic" };
WriteLine($"Original car color was {car.Color}.");
WriteLine($"New car color is {repaintedCar.Color}.");
#!markdown
## Simplifying data members in records
#!csharp
// simpler way to define a record
// auto-generates the properties, constructor, and deconstructor
public record ImmutableAnimal(string Name, string Species);
#!csharp
ImmutableAnimal oscar = new("Oscar", "Labrador");
var (who, what) = oscar; // calls Deconstruct method
WriteLine($"{who} is a {what}.");