02 - OOP (2)
Nullable types
Sometimes value types need special state – value not known.
1 |
|
If state of switch is not observed yet, both true and false values are unsuitable. For such cases, declare variable as nullable. Add ? to the type declaration.
1 |
|
Example of operations with nullable type (int? x)
:
if ( x.HasValue)
if (x != null)
int y = x ?? 0;
- (
??
- null-coalescing operator) - If value before??
is not null, use it. Otherwise take value after??
. - Pattern matching with
someVariable is type newVariableName
.
1 2 3 4 5 6 |
|
?: ?. ?[ ] ??
? :
- conditional operator (ternary conditional operator)classify = (input > 0) ? "positive" : "negative";
?.
and?[]
- null-conditional Operatorsint?
length = customers?.Length;Customer first = customers?[0];
int? count = customers?[0]?.Orders?.Count();
?? - null-coalescing operator
int count = customers?[0]?.Orders?.Count() ?? 0;
Struct
Like a class, but struct is value type. Class is reference type.
Important qualities:
- Can’t be inherited or used as a base class.
- Can implement an interface(s)
- Can’t have default constructor
- Fields cannot be initialized unless they are declared as const or static.
- Can be instantiated without the new keyword
- Fields will remain unassigned and the object cannot be used until all of the fields are initialized. This includes the inability to get or set values through auto-implemented properties.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 |
|
When to use Struct
?
CONSIDER defining a struct instead of a class if instances of the type are small and commonly short-lived or are commonly embedded in other objects. AVOID defining a struct unless the type has all of the following characteristics:
- It logically represents a single value, similar to primitive types (int, double, etc.).
- It has an instance size under 16 bytes.
- It is immutable.
- It will not have to be boxed frequently.
Record C# 9.0
Reference type that provides functionality to capsulate data.
While records can be mutable, they are primarily intended for supporting immutable data models.
- Concise syntax for creating a reference type with immutable properties
- Built-in behavior useful for a data-centric reference type:
- Value equality
- Concise syntax for nondestructive mutation
- Built-in formatting for display
- Support for inheritance hierarchies (from record to record)
Immutable record
1 |
|
1 2 3 4 5 |
|
Mutable record
1 2 3 4 5 |
|
Records are distinct from classes in that record types use value-based equality. Two variables of a record type are equal if the record type definitions are identical, and if for every field, the values in both records are equal.
C# 10 added record struct
to define records as value types. Default is record class
.
Boxing
Boxing is the process of converting a value type to the type object or to any interface type implemented by this value type.
Boxing is implicit; unboxing is explicit.
Boxing is expensive!
1 2 3 4 5 6 |
|
Tuples
Lightweight, unnamed value types that contain multiple public fields.
1 2 |
|
1 |
|
1 |
|
Warning
For the last example, left side wins!
Tuples are most useful as return types for private and internal methods. Tuples provide a simple syntax for those methods to return multiple discrete values: You save the work of authoring a class or a struct that defines the type returned. There is no need for creating a new type.
Deconstructing the tuple:
1 |
|
Discarding tuple elements:
Discard is a write-only variable whose name is _
(the underscore character). You can assign all of the values that you intend to discard to the single variable.
1 |
|
Delegates
A delegate is a type that defines a method signature, and can provide a reference to any method with a compatible signature.
You can invoke (or call) the method through the delegate.
Delegates are used to pass methods as arguments to other methods.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 |
|
Func<T1,..T16, OutT>
, Action<T1,..T16>
and Predicate<T>
delegates
Three predefined universal (generic) delegates. Action returns void
, Predicate returns bool
and Func returns user defined type.
Number of input paramaters: 0 to 16.
1 2 3 |
|
1 2 3 4 |
|
1 2 3 |
|
1 2 |
|
Parameter type T – can use up to 16 parameters.
1 |
|
Array and Collections
For many applications, you want to create and manage groups of related objects. There are two ways to group objects:
- by creating arrays of objects
- by creating collections of objects.
Arrays are most useful for creating and working with a fixed number of strongly-typed objects.
Collections provide a more flexible way to work with groups of objects. Unlike arrays, the group of objects you work with can grow and shrink dynamically as the needs of the application change.
1 2 3 4 5 6 7 |
|
All array (and collection) types are implicitly derived from System.Array, which itself is derived from System.Object. This means that all arrays are always reference types which are allocated on the managed heap, and your app's variable contains a reference to the array and not the array itself.
Arrays
Array – when working with fixed number of strongly typed objects.
int[] intArray = new int[5];
Arrays cannot be resized. You have to create new copy - Array.Resize
public static void Resize<T> (ref T[] array, int newSize);
Arrays can be multidimensional.
int[,, ] intarray = new int[4, 2, 3];
Array can contain arrays (Jagged Arrays).
1 2 3 4 5 |
|
1 2 3 4 5 6 7 8 9 10 |
|
Properties
Rank – how many dimensions
Length – total number of elements in array (over all dimensions)
Methods
void Clear (Array array, int index, int length)
– set elements to default values
object Clone()
– create shallow copy
void Copy(...)
– copy array elements to another array
bool Exists<T> (T[] array, Predicate<T> match)
public delegate bool Predicate<in T>(T obj);
T Find<T> (T[] array, Predicate<T> match);
FindIndex<T>(T[], Predicate<T>)
ForEach<T> (T[] array, Action<T> action);
int GetUpperBound (int dimension);
Sort(Array, Int32, Int32, IComparer)
, Sort(Array)
, ....
Collections and Arrays
All array (and collection) types are implicitly derived from System.Array, which itself is derived from System.Object. This means that all arrays are always reference types which are allocated on the managed heap, and your app's variable contains a reference to the array and not the array itself
Collections
Collections grow and shrink dynamically and provide lots of functionality. 90%++ of time you need one of these 5 essential collection types, however there are lots more of special collections for cases when speed/concurrency are critical.
The five essential ones are:
- List (Estonian – nimekiri)
- Dictionary (Estonian - sõnastik)
- HashSet (Estonian – paisktabel)
- Stack (Estonian – magasin ehk pinu)
- Queue (Estonian – järjekord ehk saba)
List
Represents a list of objects that can be accessed by an index.
Unlike arrays that are fixed in size, lists can grow in size dynamically.
Internally, a list uses an array for storage. If it becomes full, it’ll create a new larger array, and will copy items from the existing array into the new one.
1 2 |
|
You can set an initial size to list to avoid cost of resizing.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 |
|
Dictionary
Dictionary is a collection type that is useful when you need fast lookups by keys (keys are unique).
To create a dictionary, first you need to specify the type of keys and values.
1 |
|
A dictionary internally stores objects in an array, but unlike a list, where objects are added at the end of the array (or at the provided index), the index is calculated using a hash function.
1 2 3 4 5 6 7 8 9 10 11 12 |
|
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 |
|
HashSet
A HashSet represents a set of unique items, just like a mathematical set (e.g. { 1, 2, 3 } == {3, 2, 1}
).
Use a HashSet when you need super fast lookups against a unique list of items.
A HashSet, similar to a Dictionary, is a hash-based collection, so look ups are very fast with O(1). But unlike a dictionary, it doesn’t store key/value pairs; it only stores values.
So, every objects should be unique and this is determined by the value returned from the GetHashCode method.
So, if you’re going to store custom types in a set, you need to override GetHashCode and Equals methods in your type.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 |
|
HashSet provides many mathematical set operations:
1 2 3 4 5 6 7 8 9 10 11 12 13 |
|
Stack
Stack is a collection type with Last-In-First-Out (LIFO) behaviour.
We often use stacks in scenarios where we need to provide the user with a way to go back.
Internally, a stack is implemented using an array. Since arrays in C# have a fixed size, as you push items into a stack, it may need to increase its capacity by re-allocating a larger array and copying existing items into the new array.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 |
|
Queue
Queue represents a collection with First-In-First-Out (FIFO) behaviour. We use queues in situations where we need to process items as they arrive.
Three main operations on queue include:
- Enqueue: adding an element to the end of a queue
- Dequeue: removing the element at the front of the queue
- Peek: inspecting the element at the front without removing it.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 |
|
Collections summarized
Lists are fast when you need to access an element by index, but searching for an item in a list is slow since it requires a linear search.
Dictionaries provide fast lookups by key. Keys should be unique and cannot be null.
HashSets are useful when you need fast lookups to see if an element exists in a set or not.
Stacks provide LIFO (Last-In-First-Out) behaviour and are useful when you need to provide the user with a way to go back.
Queues provide FIFO (First-In-First-Out) behaviour and are useful to process items in the order arrived.