#!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 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 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}.");