c#9/C#19 Flashcards
.Net Framework [vs] .Net Core [vs] .Net
.Net Framework: Since 2002
.Net Core: Since 2016
.Net: Since 2020
.Net Framework Closed-source, Monolithic, Thick, Average-performance
.Net Core Open-source, Modular, Cross-platform, Minimalistic, Faster-performance
.Net Unified, Open-source, Modular, Cross-platform, Minimalistic, Faster-performance
Top Level Statements
The compiler-generated class and Main method are NOT accessible through code of any other areas of the project.
The compiled Main method would be ‘async’, by default. So it allows ‘await’ statements in top-level statements.
Only one compilation unit (C# file) can have top level statements in a C# project.
The local variables / local functions declared in the top-level statements are NOT accessible elsewhere (in other types / files).
Top level statements can access command-line arguments using ‘args’. A “string[ ] args” parameter would be generated by the compiler automatically.
File Scoped Namespaces
Allows you to declare a namespace at the top of the file (before/after the ‘using’ statements) and all types of the same file would be a part of that namespace.
Advantage: Allows developers to quickly create one-or-few types in a namespace without nesting them in the ‘namespace declaration’.
Only one ‘file-scoped namespace’ statement is allowed for one source file (C# file).
The ‘file-scoped namespace’ statement CAN be written before / after the ‘using’ statements.
A source file can’t contain both ‘file-scoped namespace’ and ‘normal namespace declarations’.
Global ‘using’ directives
Allows you import a namespace for the entire project, by adding ‘global’ keyword to the ‘using’ statement.
Advantage: Allows developers to reduce attention on lengthy ‘using’ statements at the top of every file; but concentrate on actual code (types in the file).
It is recommended to write all ‘global using’ statements in a separate file (one-for each project)
The following namespaces are implicitly imported in every C# project implicitly:
System
System.Collections.Generic
System.IO
System.Linq
System.Net.Http
System.Threading
System.Threading.Tasks
Module Initializers
Allows you to run some code with ‘global initialization logic’ at application startup, when the application loads into memory.
It would execute at application startup (before the Main method).
Advantage over static constructors: The static constructors execute ONLY if the class is used at least once; otherwise will NOT execute.
One project CAN have more than module initializer methods (if so, they are called based on alphabetical order of file names).
The initializer method must be:
Either “internal”, “protected internal” or “public” only.
Static method
Parameterless method
Return type is ‘void’
Not be a generic method
Can’t be a local function
Use Cases:
Loading environment variables
Initializing connection strings / database server names
Initializing URL’s of API servers
Loading Azure connection strings
Initializing file paths
etc.
The initializer class must be:
Either “internal” or “public” only.
Can be static class [optionally]
Not be a generic class
Nullable Reference Types
Introduces ‘nullable reference types’ and ‘non-nullable reference types’ to allow the compiler to perform
‘static flow analysis’ for purpose of null-safety.
Advantage: The compiler can perform a static analysis to identify where there is a possibility of ‘null’ values and can show warnings; so we can avoid NullReference Exceptions at coding-time itself.
By default, all classes and interfaces are ‘non-nullable reference types’. To convert them as ‘nullable reference type’, suffix a question mark (?). Eg: class?
Null forgiving operator (!)
Meaning: “Trust me it’s not null”.
Suffix your expression (variable or property) with “!” operator to make that expression as “not null”, at compilation time.
It has no effect at run time.
It means, the developer says to the C# compiler - that, a variable or property is “not null”. But at run time, if it is actually null, it leads to “NullReference Exception” as normal.
So use this operator only when you are sure that your expression (variable of property) is NOT null.
Target-typed ‘new’ expressions
Allows the developer “not-to-mention” the class name; but allows to create an object in the ‘new’ expression.
class_name variable_name = new( ); //equivalent to “new class_name( )”
Benefit: We can create object of a class in shortcut way.
Pattern Matching - Overview
Enables developers to easily check the data type of a variable and also check its value with some conditions.
“is” expression:
if (variable_name is class_name another_variable)
{
if (another_variable.property == value)
{
statements…
}
}
“switch-case” expression:
switch (variable_name)
{
case class_name another_variable
when another_variable.property == value:
statements…
break;
}
“switch” expression:
variable_name switch {
class_name another_variable when another_variable.property == value => result_expression
}
Need of Immutability
Goal: The values of fields and properties should be readonly (immutable). No other classes can change them, after they get initialized.
Immutable class:
class class_name
{
data_type readonly field_name; //readonly field
data_type property_name { get => field_name } //readonly property
}
Benefits:
Avoid unexpected value changes in response data retrieved from API servers.
Avoid unexpected value changes in the data retrieved from database servers.
Use objects of immutable classes as ‘key’ in Dictionary and in Hashtable.
Avoid unexpected value changes in objects while multiple threads access the same objects simultaneously.
Immutable Classes
A class with readonly fields and readonly properties.
Immutable class:
class class_name
{
data_type readonly field_name; //readonly field
data_type property_name { get => field_name } //readonly property
public class_name( ) //constructor
{
field_name = value; //initialize the field
}
}
‘init’ only properties
‘init’ only properties can be initialized either inline with declaration, in the constructor or in the object initializer.
Readonly structs
Enforces you to write only ‘readonly fields’ and ‘readonly properties’ to achieve immutability in your struct.
Records
Records
Goal: Concise syntax to create a reference-type with immutable properties.
Record
record record_name(data_type Property1, data_type Property2, …);
Features:
Records are ‘immutable’ by default.
All the record members become as ‘init-only’ properties.
Records can also be partially / fully mutable - by adding mutable properties.
Supports value-based equality.
Supports inheritance.
Supports non-destructive mutation using ‘with’ expression.
Records - Equality
Supports value-based equality.
Records provide a compiler-generated Equals() method and overloads == and != operators that compares two instances of records that compare the values of fields (but doesn’t compare references) .
Records - “with” expression
‘with’ expression acts as object initializer for ‘records’.
It creates a shallow copy of an existing record object and also overwrites the values of specified properties.