Merge branch 'main' of gitea.buutti.com:education/csharp-basics
commit
7aad3cf2d7
@ -0,0 +1,3 @@
|
||||
for f in *.pptx; do
|
||||
mv -- "$f" "$(echo "$f" | tr '[:upper:]' '[:lower:]' | tr ' ' '-')"
|
||||
done
|
File diff suppressed because one or more lines are too long
@ -0,0 +1,198 @@
|
||||
---
|
||||
marp: true
|
||||
paginate: true
|
||||
math: mathjax
|
||||
theme: buutti
|
||||
title: 10. Static Members, Methods and Classes
|
||||
---
|
||||
|
||||
# Static Members, Methods and Classes
|
||||
|
||||
<!-- headingDivider: 5 -->
|
||||
<!-- class: invert -->
|
||||
|
||||
## Static Members
|
||||
|
||||
### Non-static members
|
||||
|
||||
* So far, we have used *__non-static__* fields in our classes
|
||||
* Meaning that each instance of the class holds its own version of the field, and changing the value of it only affects that instance:
|
||||
```csharp
|
||||
class MyAwesomeClass
|
||||
{
|
||||
public int MyProperty { get; set; }
|
||||
}
|
||||
class Program
|
||||
{
|
||||
static void Main(string[] args)
|
||||
{
|
||||
MyAwesomeClass instance1 = new MyAwesomeClass();
|
||||
MyAwesomeClass instance2 = new MyAwesomeClass();
|
||||
instance1.MyProperty = 100;
|
||||
instance2.MyProperty = 200; // instance1.MyProperty is still 100
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
* Likewise, non-static class methods **_have to_** be called through an instance:
|
||||
```csharp
|
||||
class MyAwesomeClass
|
||||
{
|
||||
public void PrintText(string text)
|
||||
{
|
||||
Console.WriteLine(text);
|
||||
}
|
||||
}
|
||||
class Program
|
||||
{
|
||||
static void Main(string[] args)
|
||||
{
|
||||
MyAwesomeClass instance = new MyAwesomeClass();
|
||||
instance.PrintText("Hello World"); // Outputs "Hello World"
|
||||
MyAwesomeClass.PrintText("Hello World"); // Results in an error
|
||||
}
|
||||
}
|
||||
```
|
||||
<div class='centered'>
|
||||
|
||||

|
||||
|
||||
</div>
|
||||
|
||||
### Static members
|
||||
|
||||
* Let's declare a property `MyProperty` with the `static` keyword
|
||||
* It can be referenced *through the class*, but ***not*** through the instance:
|
||||
```csharp
|
||||
class MyAwesomeClass
|
||||
{
|
||||
public static int MyProperty { get; set; } = 100;
|
||||
}
|
||||
class Program
|
||||
{
|
||||
static void Main(string[] args)
|
||||
{
|
||||
MyAwesomeClass instance = new MyAwesomeClass();
|
||||
Console.WriteLine(MyAwesomeClass.MyProperty); // Outputs "100"
|
||||
Console.WriteLine(instance.MyProperty); // Results in an error
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
<div class='centered'>
|
||||
|
||||

|
||||
|
||||
</div>
|
||||
|
||||
|
||||
### Static members: An example
|
||||
|
||||
* In this example, a static field is used for keeping count on how many times the class has been instantiated:
|
||||
```csharp
|
||||
class Person
|
||||
{
|
||||
public static int totalPersons = 0;
|
||||
private string name;
|
||||
public Person(string personName) // Person Constructor
|
||||
{
|
||||
name = personName;
|
||||
++totalPersons;
|
||||
}
|
||||
public void PrintInfo()
|
||||
{
|
||||
Console.WriteLine("This person is called " + name + ".");
|
||||
Console.WriteLine("There are " + totalPersons + " persons total.");
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
|
||||
* Now let's instantiate a couple of persons and print their info:
|
||||
|
||||
<div class='columns21' markdown='1'>
|
||||
<div markdown='1'>
|
||||
|
||||
```csharp
|
||||
class Program
|
||||
{
|
||||
static void Main(string[] args)
|
||||
{
|
||||
Person steve = new Person("Steve");
|
||||
Person wendy = new Person("Wendy");
|
||||
steve.PrintInfo();
|
||||
wendy.PrintInfo();
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
</div>
|
||||
<div markdown='1'>
|
||||
|
||||

|
||||
|
||||
</div>
|
||||
</div>
|
||||
|
||||
## Static methods
|
||||
|
||||
* Methods can also be static
|
||||
* What happens when you try to call a non-static method from a static method?
|
||||
```csharp
|
||||
class Program
|
||||
{
|
||||
void PrintHelloName(string name)
|
||||
{
|
||||
Console.WriteLine("Hello, " + name);
|
||||
}
|
||||
static void Main(string[] args)
|
||||
{
|
||||
PrintHelloName(); // Will throw an error
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
<div class='centered'>
|
||||
|
||||

|
||||
|
||||
</div>
|
||||
|
||||
## Static classes
|
||||
|
||||
* Whole classes can also be static
|
||||
* Static classes cannot be instantiated, and all its members have to be static as well
|
||||
```csharp
|
||||
static class Styling
|
||||
{
|
||||
public static string fontFamily = "Verdana";
|
||||
public static float fontSize = 12.5f;
|
||||
}
|
||||
class Program
|
||||
{
|
||||
static void Main(string[] args)
|
||||
{
|
||||
Console.WriteLine
|
||||
("Using font " + Styling.fontFamily + " " + Styling.fontSize + "px");
|
||||
// Outputs "Using font Verdana 12.5px"
|
||||
Styling = new Styling(); // Results in an error
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
## Exercise 1
|
||||
<!--_class: "exercise invert" -->
|
||||
|
||||
* Create a class `Message` that has two `static` properties `int TotalMessages` and `string LastMessage`, and a *__non-static__* property `string MessageText`.
|
||||
* Add a constructor that takes a `string message` as a parameter, increases `TotalMessages` by one and sets the value of `LastMessage` to `message`.
|
||||
* Create a main loop that keeps asking the user for a new message. A new `Message` instance is then created with the user input message as an argument:
|
||||
```csharp
|
||||
Message newMessage = new Message(message);
|
||||
```
|
||||
* `newMessage` is then added to a list `allMessages`.
|
||||
* Finally, the static values `Message.TotalMessages` and `Message.LastMessage` are printed.
|
||||
|
@ -1,233 +0,0 @@
|
||||
# Static Members, Methods and Classes
|
||||
|
||||

|
||||
|
||||
---
|
||||
|
||||
# Static Members
|
||||
|
||||
So far, we have used __non-static__ fields in our classes
|
||||
|
||||
This means, that each instance of the class holds its own version of the field, and changing the value of it only affects that instance:
|
||||
|
||||
class MyAwesomeClass
|
||||
|
||||
{
|
||||
|
||||
public int MyProperty { get; set; }
|
||||
|
||||
}
|
||||
|
||||
class Program
|
||||
|
||||
{
|
||||
|
||||
static void Main(string[] args)
|
||||
|
||||
{
|
||||
|
||||
MyAwesomeClass instance1 = new MyAwesomeClass();
|
||||
|
||||
MyAwesomeClass instance2 = new MyAwesomeClass();
|
||||
|
||||
instance1.MyProperty = 100;
|
||||
|
||||
instance2.MyProperty = 200; // instance1.MyProperty is still 100
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
# Static Members (continued)
|
||||
|
||||
Likewise, non-static class methods _have to _ be called through an instance:
|
||||
|
||||
class MyAwesomeClass
|
||||
|
||||
{
|
||||
|
||||
public void PrintText(string text) { Console.WriteLine(text); }
|
||||
|
||||
}
|
||||
|
||||
class Program
|
||||
|
||||
{
|
||||
|
||||
static void Main(string[] args)
|
||||
|
||||
{
|
||||
|
||||
MyAwesomeClass instance = new MyAwesomeClass();
|
||||
|
||||
instance.PrintText("Hello World"); // Outputs "Hello World"
|
||||
|
||||
MyAwesomeClass.PrintText("Hello World"); // Results in an error
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
|
||||

|
||||
|
||||
__Static fields are shared between all instances of a class__
|
||||
|
||||
Let's declare "MyProperty" property with the __static __ keyword. Now it can be referenced through the class type name, but not through the instance, as shown below:
|
||||
|
||||
class MyAwesomeClass
|
||||
|
||||
{
|
||||
|
||||
public static int MyProperty { get; set; } = 100;
|
||||
|
||||
}
|
||||
|
||||
class Program
|
||||
|
||||
{
|
||||
|
||||
static void Main(string[] args)
|
||||
|
||||
{
|
||||
|
||||
MyAwesomeClass instance = new MyAwesomeClass();
|
||||
|
||||
Console.WriteLine(MyAwesomeClass.MyProperty); // Outputs "100"
|
||||
|
||||
Console.WriteLine(instance.MyProperty); // Results in an error
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
|
||||

|
||||
|
||||
# Static Members - Example
|
||||
|
||||
In this example, a static field is used for keeping count on how many times the class has been instantiated:
|
||||
|
||||
class Person
|
||||
|
||||
{
|
||||
|
||||
public static int totalPersons = 0;
|
||||
|
||||
private string name;
|
||||
|
||||
public Person(string personName) // Person Constructor
|
||||
|
||||
{
|
||||
|
||||
name = personName;
|
||||
|
||||
++totalPersons;
|
||||
|
||||
}
|
||||
|
||||
public void PrintInfo()
|
||||
|
||||
{
|
||||
|
||||
Console.WriteLine("This person is called " + name + ".");
|
||||
|
||||
Console.WriteLine("There are " + totalPersons + " persons total.");
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
# Static Members - Example (continued)
|
||||
|
||||
Now let's instantiate a couple of persons and print their info:
|
||||
|
||||
class Program
|
||||
|
||||
{
|
||||
|
||||
static void Main(string[] args)
|
||||
|
||||
{
|
||||
|
||||
Person steve = new Person("Steve");
|
||||
|
||||
Person wendy = new Person("Wendy");
|
||||
|
||||
steve.PrintInfo();
|
||||
|
||||
wendy.PrintInfo();
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
|
||||

|
||||
|
||||
# Static Methods
|
||||
|
||||
Methods can also be static
|
||||
|
||||
What happens when you try to call a non-static method from a static method?
|
||||
|
||||
class Program
|
||||
|
||||
{
|
||||
|
||||
void PrintHelloName(string name)
|
||||
|
||||
{
|
||||
|
||||
Console.WriteLine("Hello, " + name);
|
||||
|
||||
}
|
||||
|
||||
static void Main(string[] args)
|
||||
|
||||
{
|
||||
|
||||
PrintHelloName(); // Will throw an error
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
|
||||

|
||||
|
||||
---
|
||||
|
||||
This is why until this point all example methods have been inside of the main function
|
||||
|
||||
# Static Classes
|
||||
|
||||
* Classes can also be made static
|
||||
* Static classes cannot be instantiated
|
||||
* All members of a static class also have to be static
|
||||
* static class Styling
|
||||
* {
|
||||
* public static string fontFamily = "Verdana";
|
||||
* public static float fontSize = 12.5f;
|
||||
* }
|
||||
* class Program
|
||||
* {
|
||||
* static void Main(string[] args)
|
||||
* {
|
||||
* Console.WriteLine
|
||||
* ("Using font " + Styling.fontFamily + " " + Styling.fontSize + "px");
|
||||
* // Outputs "Using font Verdana 12.5px"
|
||||
* Styling = new Styling(); // Results in an error
|
||||
* }
|
||||
* }
|
||||
|
||||
# Exercise 1
|
||||
|
||||
Create a class Message which has two __static __ properties int TotalMessages and string LastMessage, and a __non-static __ property string MessageText.
|
||||
|
||||
Add a constructor which takes a string message as a parameter, increases TotalMessages by one and sets the value of LastMessage to message which is the parameter
|
||||
|
||||
Create a main loop which keeps asking the user for a new message. A new Message instance is then created with the user input message as an argument:
|
||||
|
||||
Message newMessage = new Message(message);
|
||||
|
||||
newMessage is then added to a list of messages, allMessages
|
||||
|
||||
Finally the static values Message.TotalMessages and Message.LastMessage are printed
|
||||
|
File diff suppressed because one or more lines are too long
@ -0,0 +1,449 @@
|
||||
---
|
||||
marp: true
|
||||
paginate: true
|
||||
math: mathjax
|
||||
theme: buutti
|
||||
title: N. Delegates and events
|
||||
---
|
||||
|
||||
# Delegates and events
|
||||
|
||||
<!-- headingDivider: 5 -->
|
||||
<!-- class: invert -->
|
||||
|
||||
## Overview
|
||||
|
||||
* Delegates
|
||||
* Multicast Delegates
|
||||
* Anonymous Methods
|
||||
* Events
|
||||
|
||||
## Delegates
|
||||
|
||||
* [Delegates](https://learn.microsoft.com/en-us/dotnet/csharp/programming-guide/delegates/) are reference type variables that hold a *__reference to a method__* or multiple methods
|
||||
* Class objects hold a reference to a class instance
|
||||
* Delegate objects hold a reference to a method / methods
|
||||
* Similar to function pointers in C and C++, or how any function in JavaScript works
|
||||
* Allows for methods to be passed as variables, useful for creating, for example, events
|
||||
|
||||
### Creating a delegate
|
||||
|
||||
* Declare a delegate using the following syntax:
|
||||
```csharp
|
||||
delegate returnType DelegateName(parameters);
|
||||
```
|
||||
* Example:
|
||||
|
||||
```csharp
|
||||
delegate void PrintDelegate(string output);
|
||||
```
|
||||
* This creates a new delegate of type `void`, named `PrintDelegate` and one parameter of type `string`
|
||||
* The referenced method return and parameter types have to match the delegate!
|
||||
|
||||
### Referencing a delegate
|
||||
|
||||
* After creating the delegate, it can be instantiated and the method assigned to it with the method name:
|
||||
```csharp
|
||||
delegate void PrintDelegate(string output);
|
||||
|
||||
static void Main(string[] args)
|
||||
{
|
||||
void PrintInLower(string text)
|
||||
{
|
||||
Console.WriteLine(text.ToLower());
|
||||
}
|
||||
|
||||
PrintDelegate print = PrintInLower;
|
||||
print("AaaBbbCcc"); // Outputs "aaabbbccc"
|
||||
}
|
||||
```
|
||||
|
||||
### Using multicast delegates
|
||||
|
||||
* Delegates can be composed of multiple methods using the `+` operator
|
||||
* Using the same `PrintDelegate` delegate as before, we could do this:
|
||||
<div class='columns21' markdown='1'>
|
||||
<div markdown='1'>
|
||||
|
||||
```csharp
|
||||
delegate void PrintDelegate(string output);
|
||||
static void Main(string[] args)
|
||||
{
|
||||
void PrintInLower(string text)
|
||||
{
|
||||
Console.WriteLine(text.ToLower());
|
||||
}
|
||||
void PrintInUpper(string text)
|
||||
{
|
||||
Console.WriteLine(text.ToUpper());
|
||||
}
|
||||
PrintDelegate print = PrintInLower;
|
||||
print += PrintInUpper;
|
||||
print("AaaBbbCcc");
|
||||
}
|
||||
```
|
||||
|
||||
</div>
|
||||
<div markdown='1'>
|
||||
|
||||

|
||||
|
||||
</div>
|
||||
</div>
|
||||
|
||||
### Removing methods
|
||||
|
||||
* Methods can be removed from the delegate with the `-` operator:
|
||||
```csharp
|
||||
delegate void PrintDelegate(string output);
|
||||
static void Main(string[] args)
|
||||
{
|
||||
...
|
||||
print -= PrintInLower;
|
||||
print("AaaBbbCcc"); // Outputs "AAABBBCCC"
|
||||
...
|
||||
}
|
||||
```
|
||||
|
||||
### Delegates: An example
|
||||
|
||||
* Let's extend our previous example (without the `-=` part) by creating a new class called `DelegateTest`, and giving it a constructor that takes a `PrintDelegate` object as a parameter:
|
||||
```csharp
|
||||
public class DelegateTest
|
||||
{
|
||||
public DelegateTest(PrintDelegate printDelegate)
|
||||
{
|
||||
printDelegate("This Method Was Called From Another Class!");
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
<div class='columns21' markdown='1'>
|
||||
<div markdown='1'>
|
||||
|
||||
```csharp
|
||||
void PrintInLower(string text)
|
||||
{
|
||||
Console.WriteLine(text.ToLower());
|
||||
}
|
||||
void PrintInUpper(string text)
|
||||
{
|
||||
Console.WriteLine(text.ToUpper());
|
||||
}
|
||||
// Initialize new delegate which is composed of PrintInLower method
|
||||
PrintDelegate print = PrintInLower;
|
||||
|
||||
// Add PrintInUpper method to the delegate
|
||||
print += PrintInUpper;
|
||||
|
||||
// Send the delegate to the class constructor
|
||||
DelegateTest delegateTest = new DelegateTest(print);
|
||||
```
|
||||
|
||||
</div>
|
||||
<div markdown='1'>
|
||||
|
||||
Now we can create a new DelegateTest object and pass the delegate to the object constructor:
|
||||
|
||||

|
||||
|
||||
</div>
|
||||
</div>
|
||||
|
||||
|
||||
### Anonymous methods
|
||||
|
||||
* Delegates can be initialized anonymously (without a specified name)
|
||||
* Anonymous method in variable declaration:
|
||||
```csharp
|
||||
delegate void PrintDelegate(string output);
|
||||
bool printUpper = true;
|
||||
PrintDelegate printCheckUpper =
|
||||
delegate (string text)
|
||||
{
|
||||
if (printUpper)
|
||||
Console.WriteLine(text.ToUpper());
|
||||
else
|
||||
Console.WriteLine(text);
|
||||
};
|
||||
|
||||
printCheckUpper("I'm not angry!"); // Outputs I'M NOT ANGRY!
|
||||
```
|
||||
* Notice that the actual method that prints the text is not declared anywhere!
|
||||
|
||||
---
|
||||
|
||||
* You can use an empty anonymous method to initialize a delegate that does nothing:
|
||||
```csharp
|
||||
delegate void SomeDelegate();
|
||||
|
||||
class Program
|
||||
{
|
||||
static void Main(string[] args)
|
||||
{
|
||||
// Initialize an empty delegate, add method later...
|
||||
SomeDelegate myDelegate = new SomeDelegate(delegate { });
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
## Events
|
||||
|
||||
### The problem
|
||||
|
||||
<div class='columns' markdown='1'>
|
||||
<div markdown='1'>
|
||||
|
||||
```csharp
|
||||
class Game
|
||||
{
|
||||
Sound gameOverSound;
|
||||
Window gameOverScreen;
|
||||
|
||||
void OnGameOver()
|
||||
{
|
||||
gameOverSound.Play(); // plays some sound
|
||||
gameOverScreen.Show(); // shows a screen
|
||||
}
|
||||
}
|
||||
...
|
||||
class Program
|
||||
{
|
||||
static void Main()
|
||||
{
|
||||
var game = new Game();
|
||||
|
||||
// somewhere in the game logic...
|
||||
// game.OnGameOver();
|
||||
}
|
||||
}
|
||||
```
|
||||
</div>
|
||||
<div markdown='1'>
|
||||
|
||||
* Consider a game engine with three classes, `Sound`, `Window` and `Game`.
|
||||
* If implemented like this, the `Game` class has to know about the `Sound` and `Window` classes
|
||||
* $\Rightarrow$ `Game` is ***tightly coupled***, and thus ***dependent*** on the `Sound` and `Window` classes
|
||||
* Changes in either of the classes could break the code!
|
||||
|
||||
</div>
|
||||
</div>
|
||||
|
||||
### The solution: Events
|
||||
|
||||
* A solution to this problem is the [Publisher-subscriber pattern](https://en.wikipedia.org/wiki/Publish%E2%80%93subscribe_pattern)
|
||||
* In C#, this pattern is implemented with the [`event` keyword](https://learn.microsoft.com/en-us/dotnet/csharp/language-reference/keywords/event?redirectedfrom=MSDN)
|
||||
* Events are ***signals*** that are raised by a *__Publisher__* and received by a *__Subscriber__*
|
||||
* The publisher does not know or care who, if anyone, receives the signal
|
||||
* Changes in the subscriber classes do not affect the publisher
|
||||
* In C#, events are multicast delegates
|
||||
* When an object triggers an event, the event invokes ***event handlers***
|
||||
* Event handlers are delegate instances added to the event
|
||||
|
||||
### Raising events
|
||||
|
||||
* Events consist of two elements:
|
||||
* A delegate that identifies the method that provides the response to the event
|
||||
* In the simplest case, we can use the built-in [`EventHandler`](https://learn.microsoft.com/en-us/dotnet/api/system.eventhandler?view=net-9.0) delegate
|
||||
* An optional class to hold event data if the event provides data.
|
||||
```csharp
|
||||
public class Publisher
|
||||
{
|
||||
public event EventHandler SampleEvent;
|
||||
// Wrap the event in a protected virtual method
|
||||
// to enable derived classes to raise the event.
|
||||
protected virtual void OnSampleEvent()
|
||||
{
|
||||
// Raise the event in a thread-safe manner using the ?. operator.
|
||||
SampleEvent?.Invoke(this);
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
### Raising the event
|
||||
|
||||
Events can only be invoked (i.e., raised) from within the class (or derived classes) or struct where they're declared (the publisher class)
|
||||
|
||||
<div class='columns' markdown='1'>
|
||||
<div markdown='1'>
|
||||
|
||||
```csharp
|
||||
public class Publisher
|
||||
{
|
||||
public event EventHandler SampleEvent;
|
||||
protected virtual void OnSampleEvent()
|
||||
{
|
||||
SampleEvent?.Invoke(this);
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
</div>
|
||||
<div markdown='1'>
|
||||
|
||||
```csharp
|
||||
class Program
|
||||
{
|
||||
static void Main()
|
||||
{
|
||||
var pub = new Publisher();
|
||||
|
||||
pub.OnSampleEvent(); // ✅ Works fine
|
||||
// pub.SampleEvent(); // ❌ Not allowed!
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
</div>
|
||||
</div>
|
||||
|
||||
### Adding subscribers to the event
|
||||
|
||||
* Now that we know how to raise the event, we can add subscribers to it
|
||||
* i.e., functions that get called when the event is raised
|
||||
|
||||
<div class='columns' markdown='1'>
|
||||
<div markdown='1'>
|
||||
|
||||
```csharp
|
||||
public class Publisher
|
||||
{
|
||||
public event EventHandler SampleEvent;
|
||||
protected virtual void OnSampleEvent()
|
||||
{
|
||||
SampleEvent?.Invoke(this);
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
</div>
|
||||
<div markdown='1'>
|
||||
|
||||
```csharp {7-8}
|
||||
class Program
|
||||
{
|
||||
static void Main()
|
||||
{
|
||||
var pub = new Publisher();
|
||||
|
||||
pub.SampleEvent +=
|
||||
() => Console.WriteLine("Sample event!");
|
||||
|
||||
pub.OnSampleEvent();
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
</div>
|
||||
</div>
|
||||
|
||||
|
||||
### Custom event handler
|
||||
|
||||
* We can define the delegate ourselves, for example if we want to send some data to the event.
|
||||
```csharp
|
||||
public class Publisher
|
||||
{
|
||||
public delegate void SampleEventHandler(object sender);
|
||||
|
||||
// Declare the event.
|
||||
public event SampleEventHandler SampleEvent;
|
||||
|
||||
protected virtual void OnSampleEvent()
|
||||
{
|
||||
SampleEvent?.Invoke(this);
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
### Event arguments
|
||||
|
||||
* Finally, we can send arguments to the event like this:
|
||||
```csharp
|
||||
public class SampleEventArgs
|
||||
{
|
||||
public SampleEventArgs(string text) { Text = text; }
|
||||
public string Text { get; } // readonly
|
||||
}
|
||||
|
||||
public class Publisher
|
||||
{
|
||||
public delegate void SampleEventHandler(object sender, SampleEventArgs e);
|
||||
|
||||
public event SampleEventHandler SampleEvent;
|
||||
|
||||
protected virtual void RaiseSampleEvent()
|
||||
{
|
||||
SampleEvent?.Invoke(this, new SampleEventArgs("Hello"));
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
|
||||
### "Fixing" the Game Over example
|
||||
|
||||
<div class='columns' markdown='1'>
|
||||
<div markdown='1'>
|
||||
|
||||
```csharp
|
||||
public delegate void GameOverHandler();
|
||||
class Game
|
||||
{
|
||||
public event GameOverHandler GameOver;
|
||||
|
||||
protected virtual void OnGameOver()
|
||||
{
|
||||
GameOver?.Invoke(this);
|
||||
}
|
||||
}
|
||||
...
|
||||
class Program
|
||||
{
|
||||
static void Main()
|
||||
{
|
||||
var game = new Game();
|
||||
Sound gameOverSound;
|
||||
Window gameOverScreen;
|
||||
|
||||
game.GameOver += gameOverSound.Play;
|
||||
game.GameOver += gameOverScreen.Show;
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
</div>
|
||||
<div markdown='1'>
|
||||
|
||||
1) Declare an event handler delegate (`GameOverHandler`)
|
||||
2) Declare an instance of the handler with the `event` keyword (`GameOVer`)
|
||||
3) Declare a virtual method that invokes the event
|
||||
4) Add methods that subscribe to the event
|
||||
|
||||
</div>
|
||||
</div>
|
||||
|
||||
## Exercise 1: A rudimentary event system
|
||||
<!--_class: "exercise invert" -->
|
||||
|
||||
Create a console application for controlling a plant treatment system with three methods that print the following outputs:
|
||||
|
||||
| Method | Output |
|
||||
| :-- | :-- |
|
||||
| `void ReleaseWater()` | `Releasing water...` |
|
||||
| `void ReleaseFertilizer()` | `Releasing fertilizer...` |
|
||||
| `void IncreaseTemperature()` | `Increasing temperature...` |
|
||||
|
||||
* All methods are off by default. Create a main loop where the user can...
|
||||
* ...type the name of the method to switch each method on (add it to the delegate)
|
||||
* ...type `run` to execute all the methods that are on
|
||||
|
||||
---
|
||||
|
||||
<!--_class: "exercise invert" -->
|
||||
|
||||
* ***Hint:*** you can just use switch-case for defining which method should be added to the delegate
|
||||
|
||||
* Here's an example console input & output:
|
||||

|
@ -1,290 +0,0 @@
|
||||
# Delegates and Events
|
||||
|
||||

|
||||
|
||||
---
|
||||
|
||||
# Overview
|
||||
|
||||
Delegates
|
||||
|
||||
Multicast Delegates
|
||||
|
||||
Anonymous Methods
|
||||
|
||||
Events
|
||||
|
||||
# Delegates
|
||||
|
||||
* [Delegates](https://learn.microsoft.com/en-us/dotnet/csharp/programming-guide/delegates/) are reference type variables that hold a *__reference to a method__* or multiple methods
|
||||
* Class objects hold a reference to a class instance
|
||||
* Delegate objects hold a reference to a method / methods
|
||||
* Similar to function pointers in C and C++, or how any function in JavaScript works
|
||||
* Allows for methods to be passed as variables, useful for creating, for example, events
|
||||
|
||||
# Creating a Delegate
|
||||
|
||||
Declare a delegate using the following syntax:
|
||||
|
||||
delegate <return type> <delegate name>(<parameters>);
|
||||
|
||||
Example:
|
||||
|
||||
delegate void PrintDelegate(string output);
|
||||
|
||||
This creates a new delegate of type void, named PrintDelegate and one parameter of type string
|
||||
|
||||
The referenced method return and parameter types have to match the delegate!
|
||||
|
||||
# Referencing a Delegate
|
||||
|
||||
After creating the delegate, it can be instantiated and the method assigned to it with the method name:
|
||||
|
||||
delegate void PrintDelegate(string output);
|
||||
|
||||
static void Main(string[] args)
|
||||
|
||||
{
|
||||
|
||||
void PrintInLower(string text)
|
||||
|
||||
{
|
||||
|
||||
Console.WriteLine(text.ToLower());
|
||||
|
||||
}
|
||||
|
||||
PrintDelegate print = PrintInLower;
|
||||
|
||||
print("AaaBbbCcc"); // Outputs "aaabbbccc"
|
||||
|
||||
}
|
||||
|
||||
# Using Multicast Delegates
|
||||
|
||||
Delegates can be __composed of__ multiple methods using the "+" operator
|
||||
|
||||
Using the same PrintDelegate delegate as before, we could do this:
|
||||
|
||||
delegate void PrintDelegate(string output);
|
||||
|
||||
static void Main(string[] args)
|
||||
|
||||
{
|
||||
|
||||
void PrintInLower(string text)
|
||||
|
||||
{
|
||||
|
||||
Console.WriteLine(text.ToLower());
|
||||
|
||||
}
|
||||
|
||||
void PrintInUpper(string text)
|
||||
|
||||
{
|
||||
|
||||
Console.WriteLine(text.ToUpper());
|
||||
|
||||
}
|
||||
|
||||
PrintDelegate print = PrintInLower;
|
||||
|
||||
print += PrintInUpper;
|
||||
|
||||
print("AaaBbbCcc");
|
||||
|
||||
}
|
||||
|
||||

|
||||
|
||||
# Using Multicast Delegates (continued)
|
||||
|
||||
Methods can be removed from the delegate with the "-" operator:
|
||||
|
||||
delegate void PrintDelegate(string output);
|
||||
|
||||
static void Main(string[] args)
|
||||
|
||||
{
|
||||
|
||||
...
|
||||
|
||||
print -= PrintInLower;
|
||||
|
||||
print("AaaBbbCcc"); // Outputs "AAABBBCCC"
|
||||
|
||||
...
|
||||
|
||||
}
|
||||
|
||||
# Delegates - Example
|
||||
|
||||
Let's extend our previous example (without the "-=" part) by creating a new class called DelegateTest, and giving it a constructor that takes a PrintDelegate object as a parameter:
|
||||
|
||||
public class DelegateTest
|
||||
|
||||
{
|
||||
|
||||
public DelegateTest(PrintDelegate printDelegate)
|
||||
|
||||
{
|
||||
|
||||
printDelegate("This Method Was Called From Another Class!");
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
# Delegates - Example (continued)
|
||||
|
||||
Now we can create a new DelegateTest object and pass the delegate to the object constructor:
|
||||
|
||||
void PrintInLower(string text)
|
||||
|
||||
{
|
||||
|
||||
Console.WriteLine(text.ToLower());
|
||||
|
||||
}
|
||||
|
||||
void PrintInUpper(string text)
|
||||
|
||||
{
|
||||
|
||||
Console.WriteLine(text.ToUpper());
|
||||
|
||||
}
|
||||
|
||||
// Initialize new delegate which is composed of PrintInLower method
|
||||
|
||||
PrintDelegate print = PrintInLower;
|
||||
|
||||
// Add PrintInUpper method to the delegate
|
||||
|
||||
print += PrintInUpper;
|
||||
|
||||
// Send the delegate to the class constructor
|
||||
|
||||
DelegateTest delegateTest = new DelegateTest(print);
|
||||
|
||||

|
||||
|
||||
# Anonymous Methods
|
||||
|
||||
Delegates can be initialized anonymously (without a specified name)
|
||||
|
||||
Anonymous method in variable declaration:
|
||||
|
||||
delegate void PrintDelegate(string output);
|
||||
|
||||
bool printUpper = true;
|
||||
|
||||
PrintDelegate printCheckUpper =
|
||||
|
||||
delegate (string text)
|
||||
|
||||
{
|
||||
|
||||
if (printUpper)
|
||||
|
||||
Console.WriteLine(text.ToUpper());
|
||||
|
||||
else
|
||||
|
||||
Console.WriteLine(text);
|
||||
|
||||
};
|
||||
|
||||
printCheckUpper("I'm not angry!"); // Outputs I'M NOT ANGRY!
|
||||
|
||||
Notice that the actual method that prints the text is not declared anywhere
|
||||
|
||||
You can use an empty anonymous method to initialize a delegate which does nothing:
|
||||
|
||||
delegate void SomeDelegate();
|
||||
|
||||
class Program
|
||||
|
||||
{
|
||||
|
||||
static void Main(string[] args)
|
||||
|
||||
{
|
||||
|
||||
// Initialize an empty delegate, add method later...
|
||||
|
||||
SomeDelegate myDelegate = new SomeDelegate(delegate { });
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
# Events
|
||||
|
||||
* Consider the following Game class:
|
||||
* class Game
|
||||
* {
|
||||
* Sound gameOverSound;
|
||||
* Window gameOverScreen;
|
||||
* // Some game logic here...
|
||||
* // ...
|
||||
* void OnGameOver()
|
||||
* {
|
||||
* gameOverSound.Play(); // This plays some sound
|
||||
* gameOverScreen.Show(); // This shows some screen
|
||||
* }
|
||||
* }
|
||||
* This raises a couple of problems:
|
||||
* The Game class has to know about the Sound and Window classes
|
||||
* Changes in either of the classes could break the code
|
||||
* = Game is dependent of Sound and Window classes
|
||||
---
|
||||
|
||||
Tight coupling
|
||||
|
||||
# Events (continued)
|
||||
|
||||
Events are signals that are raised by a __Publisher __ and received by a __Subscriber__
|
||||
|
||||
The publisher does not know or care what, if any, object receives the signal
|
||||
|
||||
Changes in the subscriber classes do not affect the publisher
|
||||
|
||||
# Events - Example
|
||||
|
||||
Events are created in two steps:
|
||||
|
||||
Declare a delegate
|
||||
|
||||
Declare a variable of the delegate with the event keyword
|
||||
|
||||
public delegate void GameOver();
|
||||
|
||||
class Game
|
||||
|
||||
{
|
||||
|
||||
public event GameOver GameOverEventHandler;
|
||||
|
||||
// Some game logic here...
|
||||
|
||||
// ...
|
||||
|
||||
}
|
||||
|
||||
# Exercise 1: A Rudimentary Event System
|
||||
|
||||
Create a console application for controlling a plant treatment system, that has three methods which print the following outputs:
|
||||
|
||||
| void ReleaseWater() | Releasing water... |
|
||||
| :-: | :-: |
|
||||
| void ReleaseFertilizer() | Releasing fertilizer... |
|
||||
| void IncreaseTemperature() | Increasing temperature... |
|
||||
|
||||
Create a main loop where the user can switch each method on (= add to a delegate) by writing its name. All methods are off by default. (Hint: you can just use switch-case for defining which method should be added to the delegate)
|
||||
|
||||
If the user types run, all the methods that are on (= added to the delegate), will be executed.
|
||||
|
||||

|
||||
|
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
@ -0,0 +1,307 @@
|
||||
---
|
||||
marp: true
|
||||
paginate: true
|
||||
math: mathjax
|
||||
theme: buutti
|
||||
title: 8. Inheritance & Abstract Classes
|
||||
---
|
||||
|
||||
# Inheritance & Abstract Classes
|
||||
|
||||
<!-- headingDivider: 5 -->
|
||||
<!-- class: invert -->
|
||||
|
||||
## Overview
|
||||
|
||||
* Inheritance
|
||||
* Abstract classes
|
||||
* Enums
|
||||
* OOP
|
||||
|
||||
## Inheritance
|
||||
|
||||
* Classes can be made to inherit functionality of some other class
|
||||
* If class B inherits class A, all of the (public) functionality in class A is also available in class B
|
||||
* A is called the *__base class__* (or parent class) and B is called the *__derived class__* (or child class)
|
||||
* Use the `:` symbol to make a class inherit from another
|
||||
|
||||
### An inheritance example
|
||||
|
||||
<center>
|
||||
|
||||
| `class Animal` | `class Dog : Animal` | `class Cat : Animal` |
|
||||
|:--------------:|:--------------------:|:--------------------:|
|
||||
| `Eat()` | `Bark()` | `Meow()` |
|
||||
| `Sleep()` | | |
|
||||
|
||||
</center>
|
||||
|
||||
<br>
|
||||
|
||||
<div class='columns111' markdown='1'>
|
||||
<div markdown='1'>
|
||||
|
||||
* Animal can eat
|
||||
* Animal can sleep
|
||||
|
||||
</div>
|
||||
<div markdown='1'>
|
||||
|
||||
* Dog can eat
|
||||
* Dog can sleep
|
||||
* Dog can bark
|
||||
|
||||
</div>
|
||||
<div markdown='1'>
|
||||
|
||||
* Cat can eat
|
||||
* Cat can sleep
|
||||
* Cat can meow
|
||||
|
||||
</div>
|
||||
</div>
|
||||
|
||||
### Inheritance: An example implementation
|
||||
|
||||
<div class='columns' markdown='1'>
|
||||
<div markdown='1'>
|
||||
|
||||
```csharp
|
||||
class Animal
|
||||
{
|
||||
public void Eat()
|
||||
{ Console.WriteLine("Eating..."); }
|
||||
public void Sleep()
|
||||
{ Console.WriteLine("Sleeping..."); }
|
||||
}
|
||||
class Cat : Animal
|
||||
{
|
||||
public void Meow()
|
||||
{ Console.WriteLine("Meow!"); }
|
||||
}
|
||||
class Dog : Animal
|
||||
{
|
||||
public void Bark()
|
||||
{ Console.WriteLine("Bark!"); }
|
||||
}
|
||||
```
|
||||
|
||||
</div>
|
||||
<div markdown='1'>
|
||||
|
||||
```csharp
|
||||
class Program
|
||||
{
|
||||
static void Main(string[] args)
|
||||
{
|
||||
Dog pluto = new Dog();
|
||||
pluto.Eat(); // Outputs "Eating..."
|
||||
pluto.Bark(); // Outputs "Bark!"
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
</div>
|
||||
</div>
|
||||
|
||||
### Inheritance continued
|
||||
|
||||
* All the objects deriving from the same base class can be referenced with the base class name:
|
||||
```csharp
|
||||
Animal whiskersAnimal = new Cat();
|
||||
Animal plutoAnimal = new Dog();
|
||||
```
|
||||
* However, only the methods of the base class are available for objects of the base class type:
|
||||
```csharp
|
||||
plutoAnimal.Eat(); // Outputs "Eating..."
|
||||
plutoAnimal.Bark(); // Error
|
||||
```
|
||||
* An object of base class type can be *__cast__* into the child class type:
|
||||
```csharp
|
||||
Dog pluto = (Dog)plutoAnimal;
|
||||
pluto.Bark(); // Outputs "Bark!"
|
||||
```
|
||||
|
||||
## Abstract Classes
|
||||
|
||||
* In some cases you want the base class to be made [abstract](https://learn.microsoft.com/en-us/dotnet/csharp/language-reference/keywords/abstract)
|
||||
* Objects of an abstract class ***cannot*** be instantiated
|
||||
* For example, where would you need a generic `Animal` by itself?
|
||||
* Make a class abstract with the `abstract` keyword:
|
||||
```csharp
|
||||
abstract class Animal
|
||||
{
|
||||
public void Eat() { Console.WriteLine("Eating..."); }
|
||||
public void Sleep() { Console.WriteLine("Sleeping..."); }
|
||||
}
|
||||
class Program
|
||||
{
|
||||
static void Main(string[] args)
|
||||
{
|
||||
Animal animal = new Animal(); // This will throw an error
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
* Instead, the methods are accessible through a derived class:
|
||||
```csharp
|
||||
abstract class Animal
|
||||
{
|
||||
public void Eat() { Console.WriteLine("Eating..."); }
|
||||
public void Sleep() { Console.WriteLine("Sleeping..."); }
|
||||
}
|
||||
class Pig : Animal
|
||||
{
|
||||
public void Squeal() { Console.WriteLine("Squeee!"); }
|
||||
}
|
||||
class Program
|
||||
{
|
||||
static void Main(string[] args)
|
||||
{
|
||||
Pig pig = new Pig();
|
||||
pig.Sleep(); // Outputs "Sleeping..."
|
||||
pig.Squeal(); // Outputs "Squeee!"
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
* If you know you only need the functionality of the abstract class, instantiate the new class as a type of abstract class:
|
||||
```csharp
|
||||
abstract class Animal
|
||||
{
|
||||
public void Eat() { Console.WriteLine("Eating..."); }
|
||||
public void Sleep() { Console.WriteLine("Sleeping..."); }
|
||||
}
|
||||
class Pig : Animal
|
||||
{
|
||||
public void Squeal() { Console.WriteLine("Squeee!"); }
|
||||
}
|
||||
class Program
|
||||
{
|
||||
static void Main(string[] args)
|
||||
{
|
||||
Animal pig = new Pig();
|
||||
pig.Sleep(); // Outputs "Sleeping..."
|
||||
pig.Squeal(); // This will throw an error
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
## Exercise 1. Animal farm
|
||||
<!--_class: "exercise invert" -->
|
||||
|
||||
1) Create the classes `Animal`, `Dog`, `Cat` and `Pig`. `Animal` is the same as before (strings `name` and `sound` and the `Greet()` method). `Dog`, `Cat` and `Pig` inherit `Animal`
|
||||
2) Give `Dog`, `Cat` and `Pig` some fields and/or methods specific to that animal
|
||||
3) Create a few instances of `Dog`, `Cat` and `Pig`, and add them to a list `allAnimals`
|
||||
4) Loop through `allAnimals` with the `foreach` statement, and call the `Greet()` method of each animal
|
||||
|
||||
## Enums
|
||||
|
||||
* [Enum](https://learn.microsoft.com/en-us/dotnet/csharp/language-reference/builtin-types/enum) (short for enumeration) is a data type for holding a set of constant (immutable) names
|
||||
* Enums can be useful when you have a given number of ***states*** that are predefined
|
||||
* For example, weekdays
|
||||
* Another example is an extension of boolean with three states `True`, `False` and `Maybe`
|
||||
|
||||
### Creating enums
|
||||
|
||||
* Create an `enum` type with the enum keyword:
|
||||
```csharp
|
||||
enum Season
|
||||
{
|
||||
Spring,
|
||||
Summer,
|
||||
Autumn,
|
||||
Winter
|
||||
}
|
||||
```
|
||||
* Then, create a new variable with the type `Season` and assign one of the enum type's values to it:
|
||||
```csharp
|
||||
Season currentSeason = Season.Spring;
|
||||
```
|
||||
|
||||
### Enums: An example
|
||||
|
||||
<div class='columns12' markdown='1'>
|
||||
<div markdown='1'>
|
||||
|
||||
In this example, an enum is used to keep track of the state of the program.
|
||||
|
||||
```csharp
|
||||
enum ProgramState
|
||||
{
|
||||
Login,
|
||||
Menu,
|
||||
Exit
|
||||
}
|
||||
```
|
||||
|
||||
</div>
|
||||
<div markdown='1'>
|
||||
|
||||
```csharp
|
||||
static void Main(string[] args)
|
||||
{
|
||||
ProgramState currentState = ProgramState.Login;
|
||||
while (true)
|
||||
{
|
||||
switch (currentState)
|
||||
{
|
||||
case ProgramState.Login:
|
||||
// Switch current state to Menu after logging in
|
||||
break;
|
||||
case ProgramState.Menu:
|
||||
// Switch current state to Exit if user exits
|
||||
break;
|
||||
case ProgramState.Exit:
|
||||
// Exit the program with an exit message
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
</div>
|
||||
</div>
|
||||
|
||||
## Extra: Object Oriented Programming
|
||||
<!-- _class: "extra invert" -->
|
||||
|
||||
* The instances that are created with the `new` keyword are ***objects***.
|
||||
* This is literally what ***object orientation*** refers to: packing functionality into these reusable variables that are holding some data and can be passed around
|
||||
* The key concepts of OOP are:
|
||||
* Encapsulation
|
||||
* Inheritance
|
||||
* Abstraction
|
||||
* Polymorphism
|
||||
|
||||
### Encapsulation
|
||||
<!-- _class: "extra invert" -->
|
||||
|
||||
* Earlier we created classes that hold properties and methods that are only accessible elsewhere ***after*** instantiating an object of the class
|
||||
* All the functionality is encapsulated inside of the class instead of lying around in the codebase
|
||||
* All the functionality is made available **_only when it is needed_** by instantiating an object of the class
|
||||
|
||||
### Inheritance
|
||||
<!-- _class: "extra invert" -->
|
||||
|
||||
* As shown earlier, inheritance allows you to write some functionality once, and then create separate classes which all share that same functionality
|
||||
* This removes the need to rewrite the same code inside every class
|
||||
|
||||
### Abstraction
|
||||
<!-- _class: "extra invert" -->
|
||||
|
||||
* When your class contains a lot of complicated functionality, it doesn't always make sense to reveal everything when the class is used
|
||||
* Instead, reveal only the parts that the user (you, a workmate, etc) actually need, with abstraction
|
||||
* Example: The user of a microwave doesn't have to know about the complicated circuitry inside of the microwave. Only the buttons are revealed
|
||||
|
||||
### Polymorphism
|
||||
<!-- _class: "extra invert" -->
|
||||
|
||||
* This concept becomes more clear after we have covered interfaces in the next lecture
|
||||
* Polymorphism refers to multiple classes having the same method but with different functionality
|
||||
* This reduces the need for massive if-else and switch..case statements
|
||||
|
@ -1,302 +0,0 @@
|
||||
# Inheritance & Abstract Classes
|
||||
|
||||

|
||||
|
||||
---
|
||||
|
||||
# Overview
|
||||
|
||||
Inheritance
|
||||
|
||||
Abstract Classes
|
||||
|
||||
Enums
|
||||
|
||||
# Inheritance
|
||||
|
||||
Classes can be made to inherit functionality of some other class
|
||||
|
||||
If class B inherits class A, all of the (public) functionality in class A is also available in class B
|
||||
|
||||
A is called the __base class__ (or parent class) and B is called the __derived class __ (or child class)
|
||||
|
||||
Use the " __:"__ symbol to make a class inherit from another
|
||||
|
||||
Try to understand the following example:
|
||||
|
||||
| class Animal |
|
||||
| :-: |
|
||||
| -Eat()-Sleep() |
|
||||
|
||||
| class Dog : Animal |
|
||||
| :-: |
|
||||
| -Bark() |
|
||||
|
||||
| class Cat : Animal |
|
||||
| :-: |
|
||||
| -Meow() |
|
||||
|
||||
Animal can eat
|
||||
|
||||
Animal can sleep
|
||||
|
||||
Dog can eat
|
||||
|
||||
Dog can sleep
|
||||
|
||||
Dog can bark
|
||||
|
||||
Cat can eat
|
||||
|
||||
Cat can sleep
|
||||
|
||||
Cat can meow
|
||||
|
||||
# Inheritance - Example
|
||||
|
||||
class Animal
|
||||
|
||||
{
|
||||
|
||||
public void Eat() { Console.WriteLine("Eating..."); }
|
||||
|
||||
public void Sleep() { Console.WriteLine("Sleeping..."); }
|
||||
|
||||
}
|
||||
|
||||
class Cat : Animal
|
||||
|
||||
{
|
||||
|
||||
public void Meow() { Console.WriteLine("Meow!"); }
|
||||
|
||||
}
|
||||
|
||||
class Dog : Animal
|
||||
|
||||
{
|
||||
|
||||
public void Bark() { Console.WriteLine("Bark!"); }
|
||||
|
||||
}
|
||||
|
||||
class Program
|
||||
|
||||
{
|
||||
|
||||
static void Main(string[] args)
|
||||
|
||||
{
|
||||
|
||||
Dog pluto = new Dog();
|
||||
|
||||
pluto.Eat(); // Outputs "Eating..."
|
||||
|
||||
pluto.Bark(); // Outputs "Bark!"
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
# Inheritance
|
||||
|
||||
All the objects deriving from the same base class can be referenced with the base class name:
|
||||
|
||||
Animal whiskersAnimal = new Cat();
|
||||
|
||||
Animal plutoAnimal = new Dog();
|
||||
|
||||
However, only the methods of the base class are available for objects of the base class type:
|
||||
|
||||
plutoAnimal.Eat(); // Outputs "Eating..."
|
||||
|
||||
plutoAnimal.Bark(); // Error
|
||||
|
||||
An object of base class type can be __casted __ into the child class type:
|
||||
|
||||
Dog pluto = (Dog)plutoAnimal;
|
||||
|
||||
pluto.Bark(); // Outputs "Bark!"
|
||||
|
||||
# Abstract Classes
|
||||
|
||||
* In some cases you want the base class to be made __abstract__
|
||||
* Objects of an abstract class cannot be instantiated
|
||||
* For example, where would you need a generic Animal by itself?
|
||||
* Make a class abstract with the abstract keyword:
|
||||
* abstract class Animal
|
||||
* {
|
||||
* public void Eat() { Console.WriteLine("Eating..."); }
|
||||
* public void Sleep() { Console.WriteLine("Sleeping..."); }
|
||||
* }
|
||||
* class Program
|
||||
* {
|
||||
* static void Main(string[] args)
|
||||
* {
|
||||
* Animal animal = new Animal(); // This will throw an error
|
||||
* }
|
||||
* }
|
||||
|
||||
Instead, the methods are accessible through a derived class:
|
||||
|
||||
abstract class Animal
|
||||
|
||||
{
|
||||
|
||||
public void Eat() { Console.WriteLine("Eating..."); }
|
||||
|
||||
public void Sleep() { Console.WriteLine("Sleeping..."); }
|
||||
|
||||
}
|
||||
|
||||
class Pig : Animal
|
||||
|
||||
{
|
||||
|
||||
public void Squeal() { Console.WriteLine("Squeee!"); }
|
||||
|
||||
}
|
||||
|
||||
class Program
|
||||
|
||||
{
|
||||
|
||||
static void Main(string[] args)
|
||||
|
||||
{
|
||||
|
||||
Pig pig = new Pig();
|
||||
|
||||
pig.Sleep(); // Outputs "Sleeping..."
|
||||
|
||||
pig.Squeal(); // Outputs "Squeee!"
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
If you know you only need the functionality of the abstract class, instantiate the new class as a type of abstract class:
|
||||
|
||||
abstract class Animal{ public void Eat() { Console.WriteLine("Eating..."); } public void Sleep() { Console.WriteLine("Sleeping..."); }}class Pig : Animal{ public void Squeal() { Console.WriteLine("Squeee!"); }}class Program{ static void Main(string[] args) { Animal pig = new Pig(); pig.Sleep(); // Outputs "Sleeping..." pig.Squeal(); // This will throw an error }}
|
||||
|
||||
---
|
||||
|
||||
This is called abstraction, which is one of the key concepts of OOP
|
||||
|
||||
# Exercise 1
|
||||
|
||||
Create the classes Animal, Dog, Cat and Pig. Animal is the same as before (strings name and sound and the Greet() method). Dog, Cat and Pig inherit Animal
|
||||
|
||||
Give Dog, Cat and Pig some fields and/or methods specific to that animal
|
||||
|
||||
Create a few instances of Dog, Cat and Pig classes, and add them to a new list of Animals, named "allAnimals"
|
||||
|
||||
Loop through allAnimals with the foreach statement, and call the Greet() method of each animal
|
||||
|
||||
# Enum
|
||||
|
||||
__Enum __ (short for enumeration) is a data type for holding a set of constant (immutable) names
|
||||
|
||||
Enums can be useful when you have a number of items or states that are predefined, for example, weekdays
|
||||
|
||||
Create the enum type with the enum keyword:
|
||||
|
||||
enum Weekday{ Monday, Tuesday, Wednesday, Thursday, Friday, Saturday}
|
||||
|
||||
New instances of the enum can only be assigned one of the values within:
|
||||
|
||||
Weekday currentDay = Weekday.Monday;
|
||||
|
||||
# Enum - Example
|
||||
|
||||
In this example, enum is used to keep track of the state of the program:
|
||||
|
||||
enum ProgramState
|
||||
|
||||
{
|
||||
|
||||
Login,
|
||||
|
||||
Menu,
|
||||
|
||||
Exit
|
||||
|
||||
}
|
||||
|
||||
static void Main(string[] args)
|
||||
|
||||
{
|
||||
|
||||
ProgramState currentState = ProgramState.Login;
|
||||
|
||||
while (true)
|
||||
|
||||
{
|
||||
|
||||
switch (currentState)
|
||||
|
||||
{
|
||||
|
||||
case ProgramState.Login:
|
||||
|
||||
// Switch current state to Menu after logging in
|
||||
|
||||
break;
|
||||
|
||||
case ProgramState.Menu:
|
||||
|
||||
// Switch current state to Exit if user exits
|
||||
|
||||
break;
|
||||
|
||||
case ProgramState.Exit:
|
||||
|
||||
// Exit the program with an exit message
|
||||
|
||||
break;
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
# Going Further: Object Oriented Programming
|
||||
|
||||
* The instances that are created with the new -keyword are objects. This is literally what Object Orientation refers to: packing functionality into these reusable variables that are holding some data and can be passed around
|
||||
* The key concepts of OOP are
|
||||
* Encapsulation
|
||||
* Inheritance
|
||||
* Abstraction, and
|
||||
* Polymorphism
|
||||
|
||||
# OOP: Encapsulation
|
||||
|
||||
Earlier we created classes which hold properties and methods, which are only accessible elsewhere _after _ instantiating an object of the class
|
||||
|
||||
All the functionality is encapsulated inside of the class instead of lying around in the codebase
|
||||
|
||||
All the functionality made available _only when it is needed _ by instantiating an object of the class
|
||||
|
||||
# OOP: Inheritance
|
||||
|
||||
As shown in the lecture slides, inheritance allows you to write some functionality once, and then create separate classes which all share that same functionality
|
||||
|
||||
This removes the need to write the same code inside every class
|
||||
|
||||
# OOP: Abstraction
|
||||
|
||||
When your class contains a lot of complicated functionality, it doesn't always make sense to reveal everything when the class is used
|
||||
|
||||
Instead, reveal only the parts that the user (you, a workmate, etc) actually need, with abstraction
|
||||
|
||||
Parable: The user of a microwave doesn't have to know about the complicated circuitry inside of the microwave. Only the buttons are revealed
|
||||
|
||||
# OOP: Polymorphism
|
||||
|
||||
This concept becomes more clear after we have covered interfaces
|
||||
|
||||
Polymorphism refers to multiple classes having the same method but with different functionality
|
||||
|
||||
This reduces the need for massive if-else and switch..case statements
|
||||
|
File diff suppressed because one or more lines are too long
@ -0,0 +1,248 @@
|
||||
---
|
||||
marp: true
|
||||
paginate: true
|
||||
math: mathjax
|
||||
theme: buutti
|
||||
title: 9. Interfaces
|
||||
---
|
||||
# Interfaces
|
||||
|
||||
<!-- headingDivider: 5 -->
|
||||
<!-- class: invert -->
|
||||
|
||||
## Overview
|
||||
|
||||
* Interfaces
|
||||
* Interfaces or Inheritance?
|
||||
|
||||
## Interfaces
|
||||
|
||||
* In addition to abstract classes, *__interfaces__* are a way to achieve abstraction to your program
|
||||
* Interfaces are classes that have *__no internal functionality__*
|
||||
* Interfaces describe the methods and properties that a class has to have when ***implementing*** the interface
|
||||
* Think of it as a ***contract***: by implementing an interface, the class **_has to use_** all the methods and properties defined in the interface
|
||||
* As with [abstract classes](8-inheritance-and-abstract-classes.md#abstract-classes), interfaces cannot be instantiated directly
|
||||
* (It wouldn't make any sense as interfaces have no implementation)
|
||||
* Interfaces are way more commonly used than abstract classes
|
||||
|
||||
### Creating an interface
|
||||
|
||||
* Define an interface using the interface keyword instead of class:
|
||||
```csharp
|
||||
interface IUser
|
||||
{
|
||||
int Id { get; set; }
|
||||
string Name { get; set; }
|
||||
void GetUserStatistics();
|
||||
}
|
||||
```
|
||||
* [Interface names should begin](https://learn.microsoft.com/en-us/dotnet/standard/design-guidelines/names-of-classes-structs-and-interfaces) with the capital letter `I` to more easily identify them as interfaces and not classes
|
||||
* Notice that interfaces can contain both properties and methods ***but not fields***
|
||||
* Methods are declared without the method body (no implementation)
|
||||
* The methods are implemented on the classes that uses the interface
|
||||
|
||||
### Implementing an interface
|
||||
|
||||
* Implement an interface just like you would inherit a class:
|
||||
```csharp
|
||||
class User : IUser
|
||||
{
|
||||
|
||||
}
|
||||
```
|
||||
* The compiler will now throw a bunch of errors saying that the User class does not implement the properties and methods defined in the interface
|
||||
* Let's fix that by defining those next
|
||||
|
||||
---
|
||||
|
||||
```csharp
|
||||
class User : IUser
|
||||
{
|
||||
public int Id { get; set; }
|
||||
public string Name { get; set; }
|
||||
public void GetUserStatistics()
|
||||
{
|
||||
// Some code here
|
||||
}
|
||||
}
|
||||
```
|
||||
* The interface is now fully implemented and the compiler is happy
|
||||
* The interface does not dictate **_how_** the methods are implemented, those just need to be implemented
|
||||
* ***Note:*** To quickly implement the interface, click the IUser interface name and click _💡 > Implement interface_.
|
||||
|
||||
### Implementing multiple interfaces
|
||||
|
||||
* Unlike with inheritance, classes can implement multiple interfaces
|
||||
* In contrast, classes can only inherit from one base class
|
||||
* This is done by separating the interfaces with a comma:
|
||||
```csharp
|
||||
class Rock : IPickable, IThrowable, ICrushable
|
||||
{
|
||||
// Code that implements IPickable, IThrowable and ICrushable
|
||||
}
|
||||
```
|
||||
|
||||
### Why use interfaces?
|
||||
|
||||
* Interfaces allow common functionality among classes that are otherwise unrelated to each other
|
||||
* In those cases, inheriting from some shared base class wouldn't make sense
|
||||
* Consider the following example:
|
||||
```csharp
|
||||
public void DeleteData(IDeletable data)
|
||||
{
|
||||
data.Delete();
|
||||
}
|
||||
```
|
||||
* The above method accepts **_any_** type of object that implements `IDeletable`, regardless of other functionality
|
||||
|
||||
## Interface or Inheritance?
|
||||
|
||||
### Example 1
|
||||
|
||||
<center>
|
||||
|
||||
| `class Dog` | `class Human` | `class Bear` |
|
||||
|:------------|:----------------|:--------------|
|
||||
| `Eat()` | `Eat()` | `Eat()` |
|
||||
| `Sleep()` | `Sleep()` | `Sleep()` |
|
||||
| `WagTail()` | `Contemplate()` | `Hibernate()` |
|
||||
|
||||
</center>
|
||||
|
||||
* All classes could inherit from a base class `Animal`, which has methods `Eat()` and `Sleep()`
|
||||
* The question is, should the base class be abstract?
|
||||
* Depends on your program: do you ever need an object that can ***only*** `Eat()` and `Sleep()`?
|
||||
|
||||
### Example 2
|
||||
|
||||
<center>
|
||||
|
||||
| `class Tree` | `class Human` | `class Car` |
|
||||
|:--------------------|:--------------|:------------|
|
||||
| `Grow()` | `Grow()` | `Explode()` |
|
||||
| `Photosynthesize()` | `Move()` | `Move()` |
|
||||
|
||||
</center>
|
||||
|
||||
* It wouldn't make sense to use inheritance here, since there is no functionality that is shared between ***all*** classes
|
||||
* Instead you could make two interfaces: `IGrowable` and `IMovable`
|
||||
|
||||
## Interfaces: An example
|
||||
|
||||
* Let's make a program that has two lists: one for all objects that can move (`IMovable`) and one for all objects that can be carried (`ICarryable`)
|
||||
* Finally, every movable object is moved and carryable object is carried
|
||||
|
||||
---
|
||||
|
||||
<div class='columns12' markdown='1'>
|
||||
<div markdown='1'>
|
||||
|
||||
```csharp
|
||||
interface IMovable
|
||||
{
|
||||
void Move();
|
||||
}
|
||||
interface ICarryable
|
||||
{
|
||||
void Carry();
|
||||
}
|
||||
```
|
||||
|
||||
</div>
|
||||
<div markdown='1'>
|
||||
|
||||
```csharp
|
||||
class Elephant : IMovable
|
||||
{
|
||||
public void Move()
|
||||
{
|
||||
Console.WriteLine("The elephant is moving'");
|
||||
}
|
||||
}
|
||||
class Cat : IMovable, ICarryable
|
||||
{
|
||||
public void Move()
|
||||
{
|
||||
Console.WriteLine("The cat is moving'");
|
||||
}
|
||||
public void Carry()
|
||||
{
|
||||
Console.WriteLine("You are carrying the cat");
|
||||
}
|
||||
}
|
||||
class Rock : ICarryable
|
||||
{
|
||||
public void Carry()
|
||||
{
|
||||
Console.WriteLine("You are carrying the rock");
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
</div>
|
||||
</div>
|
||||
|
||||
---
|
||||
|
||||
<div class='columns21' markdown='1'>
|
||||
<div markdown='1'>
|
||||
|
||||
```csharp
|
||||
class Program
|
||||
{
|
||||
static void Main(string[] args)
|
||||
{
|
||||
Elephant elephant = new Elephant();
|
||||
Cat cat = new Cat();
|
||||
Rock rock = new Rock();
|
||||
|
||||
List<IMovable> movables =
|
||||
new List<IMovable>{ elephant, cat };
|
||||
List<ICarryable> carryables =
|
||||
new List<ICarryable>{ cat, rock };
|
||||
|
||||
foreach (IMovable movable in movables)
|
||||
movable.Move();
|
||||
|
||||
foreach (ICarryable carryable in carryables)
|
||||
carryable.Carry();
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
</div>
|
||||
<div markdown='1'>
|
||||
|
||||

|
||||
|
||||
</div>
|
||||
</div>
|
||||
|
||||
|
||||
|
||||
## Exercise 1: A web of relations
|
||||
<!--_class: "exercise invert" -->
|
||||
|
||||
Create a console application that has an interface `IInfo` and two classes `Product` and `Category` that both implement `IInfo`.
|
||||
|
||||
1) Inside `IInfo`, declare a property `InfoText` and a method `PrintInfo`
|
||||
2) Implement both the property and method in `Product` and `Category`
|
||||
3) Initialize a couple of products and categories, with info texts of your choice
|
||||
4) Create a list of type `IInfo`, and add the products and categories to it
|
||||
5) Create a main loop, where each time the user presses enter, all the info texts inside the list are printed
|
||||
|
||||
## Exercise 2: The `IComparable` Interface
|
||||
<!--_class: "exercise invert" -->
|
||||
|
||||
Create a program that sorts a list of shapes by area, using the [IComparable](https://docs.microsoft.com/en-us/dotnet/api/system.collections.icomparer?view=netcore-3.1) interface which is used by the `List.Sort()` method to know whether the elements should come before or after each other in the list.
|
||||
|
||||
1) Start by creating 4 classes: `Shape`, `Square`, `Triangle`, and `Circle`. `Square`, `Triangle` and `Circle` inherit from `Shape`, which implements the `IComparable<Shape>` interface.
|
||||
2) Shape has a public property `double Area`. `Square`, `Triangle` and `Circle` have to have constructors to calculate the area: `Square` and `Triangle` with `length` and `height`, and `Circle` with `radius`.
|
||||
|
||||
---
|
||||
<!--_class: "exercise invert" -->
|
||||
|
||||
3) `The CompareTo(Shape? other)` method should return `1` if the area is greater than what is being compared with (`Shape other`), `0` if the areas are equal, and `-1` if the area is smaller.
|
||||
4) Try out your solution by creating a couple of squares, triangles and circles in a list of shapes, and sorting the list with the `.Sort()` method.
|
||||
5) Print the areas in the sorted array with a `foreach` loop. They should be printed in an increasing order.
|
||||
|
@ -1,270 +0,0 @@
|
||||
# Interfaces
|
||||
|
||||

|
||||
|
||||
---
|
||||
|
||||
# Overview
|
||||
|
||||
Interfaces
|
||||
|
||||
Interfaces or Inheritance?
|
||||
|
||||
# Interfaces
|
||||
|
||||
* In addition to abstract classes, __interfaces __ are a way to achieve abstraction to your program
|
||||
* Interfaces are classes that have __no internal functionality__
|
||||
* Interfaces describe the methods and properties that a class has to have when implementing the interface
|
||||
* Think of it as a _contract_ : by __implementing __ an interface, the class _has to_ _use _ all the methods and properties that are defined in the interface
|
||||
* As with abstract classes, interfaces cannot be instantiated directly
|
||||
* It wouldn't make any sense as interfaces have no implementation
|
||||
* Interfaces are way more commonly used than abstract classes
|
||||
|
||||
# Creating an Interface
|
||||
|
||||
* Define an interface using the interface keyword instead of class:
|
||||
* interface IUser
|
||||
* {
|
||||
* int Id { get; set; }
|
||||
* string Name { get; set; }
|
||||
* void GetUserStatistics();
|
||||
* }
|
||||
* Interface names should begin with the capital I letter to more easily identify them as interfaces and not classes
|
||||
* Notice that interfaces can contain both properties and methods but not fields
|
||||
* Methods are declared without the method body (no implementation)
|
||||
* The methods are implemented on the classes that uses the interface
|
||||
|
||||
# Implementing an Interface
|
||||
|
||||
Implement an interface just like you would inherit a class:
|
||||
|
||||
class User : IUser
|
||||
|
||||
{
|
||||
|
||||
}
|
||||
|
||||
The compiler will now throw a bunch of errors saying that the User class does not implement the properties and methods defined in the interface
|
||||
|
||||
Let's fix that by defining those now
|
||||
|
||||
# Implementing an Interface (continued)
|
||||
|
||||
class User : IUser
|
||||
|
||||
{
|
||||
|
||||
public int Id { get; set; }
|
||||
|
||||
public string Name { get; set; }
|
||||
|
||||
public void GetUserStatistics()
|
||||
|
||||
{
|
||||
|
||||
// Some code here
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
The interface is now fully implemented and the compiler is happy
|
||||
|
||||
The interface do not dictate _how_ the methods are implemented, those just need to be implemented.
|
||||
|
||||
❕
|
||||
|
||||
To quickly implement the interface, click the IUser interface name and click the light bulb -> implement interface.
|
||||
|
||||
# Implementing Multiple Interfaces
|
||||
|
||||
* Unlike with inheritance, classes can implement multiple interfaces
|
||||
* In contrast, classes can only inherit from one base class
|
||||
* This is done by separating the interfaces with a comma:
|
||||
* class Rock : IPickable, IThrowable, ICrushable
|
||||
* {
|
||||
* // Code that implements IPickable, IThrowable and ICrushable
|
||||
* }
|
||||
|
||||
# Why Use Interfaces?
|
||||
|
||||
Interfaces allow common functionality among classes that are otherwise unrelated to each other
|
||||
|
||||
In those cases, inheriting from some shared base class wouldn't make sense
|
||||
|
||||
Consider the following example:
|
||||
|
||||
public void DeleteData(IDeletable data)
|
||||
|
||||
{
|
||||
|
||||
data.Delete();
|
||||
|
||||
}
|
||||
|
||||
The above method accepts _any _ type of object that implements IDeletable, regardless of other functionality
|
||||
|
||||
# Interface or Inheritance?
|
||||
|
||||
| class Dog |
|
||||
| :-: |
|
||||
| -Eat()-Sleep()-WagTail() |
|
||||
|
||||
| class Human |
|
||||
| :-: |
|
||||
| -Eat()-Sleep()-Contemplate() |
|
||||
|
||||
| class Bear |
|
||||
| :-: |
|
||||
| -Eat()-Sleep()-Hibernate() |
|
||||
|
||||
All classes could inherit from a base class Animal, which has methods Eat() and Sleep()
|
||||
|
||||
Should the base class be abstract? Depends on your program: do you ever need an object that can only Eat() and Sleep()?
|
||||
|
||||
| class Tree |
|
||||
| :-: |
|
||||
| -Grow()-Photosynthesize() |
|
||||
|
||||
| class Human |
|
||||
| :-: |
|
||||
| -Grow()-Move() |
|
||||
|
||||
| class Car |
|
||||
| :-: |
|
||||
| -Move()-Explode() |
|
||||
|
||||
It wouldn't make sense to use inheritance here, since there is no functionality that is shared between all classes
|
||||
|
||||
Instead you could make two interfaces: IGrowable and IMovable
|
||||
|
||||
# Interfaces - Example
|
||||
|
||||
Let's make a program that has two lists: one for all objects that can move (IMovable) and one for all objects that can be carried (ICarryable)
|
||||
|
||||
Finally every movable object is moved and carryable object is carried
|
||||
|
||||
interface IMovable
|
||||
|
||||
{
|
||||
|
||||
void Move();
|
||||
|
||||
}
|
||||
|
||||
interface ICarryable
|
||||
|
||||
{
|
||||
|
||||
void Carry();
|
||||
|
||||
}
|
||||
|
||||
class Elephant : IMovable
|
||||
|
||||
{
|
||||
|
||||
public void Move()
|
||||
|
||||
{
|
||||
|
||||
Console.WriteLine("The elephant is moving'");
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
class Cat : IMovable, ICarryable
|
||||
|
||||
{
|
||||
|
||||
public void Move()
|
||||
|
||||
{
|
||||
|
||||
Console.WriteLine("The cat is moving'");
|
||||
|
||||
}
|
||||
|
||||
public void Carry()
|
||||
|
||||
{
|
||||
|
||||
Console.WriteLine("You are carrying the cat");
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
class Rock : ICarryable
|
||||
|
||||
{
|
||||
|
||||
public void Carry()
|
||||
|
||||
{
|
||||
|
||||
Console.WriteLine("You are carrying the rock");
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
class Program
|
||||
|
||||
{
|
||||
|
||||
static void Main(string[] args)
|
||||
|
||||
{
|
||||
|
||||
Elephant elephant = new Elephant();
|
||||
|
||||
Cat cat = new Cat();
|
||||
|
||||
Rock rock = new Rock();
|
||||
|
||||
List<IMovable> movables = new List<IMovable>{ elephant, cat };
|
||||
|
||||
List<ICarryable> carryables = new List<ICarryable>{ cat, rock };
|
||||
|
||||
foreach (IMovable movable in movables)
|
||||
|
||||
movable.Move();
|
||||
|
||||
foreach (ICarryable carryable in carryables)
|
||||
|
||||
carryable.Carry();
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
|
||||

|
||||
|
||||
# Exercise 1: A Web of Relations
|
||||
|
||||
Create a console application that has an interface IInfo, and two classes Product and Category, which both implement IInfo.
|
||||
|
||||
Inside IInfo, declare a property InfoText and a method PrintInfo
|
||||
|
||||
Implement the property and method in Product and Category
|
||||
|
||||
Initialize a couple of products and categories, with info texts of your choice
|
||||
|
||||
Create a list of type IInfo, and add the products and categories to it
|
||||
|
||||
Create a main loop, where each time the user presses enter, all the info texts inside the list are printed
|
||||
|
||||
# Exercise 2: The IComparable Interface
|
||||
|
||||
Create a program that sorts a list of shapes by area, using the [IComparable](https://docs.microsoft.com/en-us/dotnet/api/system.collections.icomparer?view=netcore-3.1) interface, which is used by the List.Sort() method to know whether the elements should come before or after each other in the list.
|
||||
|
||||
Start by creating 4 classes: __Shape__ , __Square__ , __Triangle__ and __Circle__ . Square, Triangle and Circle inherit from Shape. Shape implements the __IComparable\<Shape>__ __ __ interface.
|
||||
|
||||
Shape has a public property double Area. Square, Triangle and Circle have to have constructors to calculate the area: Square and Triangle with length and height, and Circle with radius.
|
||||
|
||||
The CompareTo(Shape? other) method should return 1 if the area is greater than what is being compared with (Shape other), 0 if the areas are equal, and -1 if the area is smaller.
|
||||
|
||||
Try out your solution by creating a couple of squares, triangles and circles in a list of shapes, and sorting the list with the .Sort() method. Print the areas in the sorted array with a foreach loop. They should be printed in an increasing order.
|
||||
|
Loading…
Reference in New Issue