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.
Command Line Arguments
Goal: Supply inputs from the command line / terminal to an application.
app.exe value1 value2
– will be converted as array:
Code that receives arguments from command line / terminal
class class_name
{
static void Main(string[ ] args)
{
//Use args
}
}
Features:
CLR converts all the command line arguments (space-separated values) as string array (string[ ]) only (in the same order).
The Main method can’t have additional arguments - other than
string[ ] args.
The parameter name ‘args’ isn’t fixed. You can give it any other name.
Top level statements have an implicit parameter called ‘args’ of string[ ] type, which contains the command line argument s received.
Return Type of Lambda Functions
A lambda expression (or lambda function) can have a return type before the list of parenthesized parameters.
Useful when you return a value of any one of two or more types.
Lambda Function Return Type in C# 10
return_type (Parameters_list) => return_value;
Interface Default Methods
Default methods are methods in interfaces with concrete implementation.
These methods are accessible through a reference variable of the interface type.
Interface Default Methods
interface interface_name
{
access_modifier return_type method_name(parameters)
{
//method body
}
}
Introduction to Concurrent Execution
Concurrent
Concurrent execution is concept where the programmer can divide the program into independent parts (threads) and can execute them in order-independent manner or in partial order, without affecting the outcome.
Parallel
Parallel execution is a concept that enables a program to execute multiple tasks simultaneously, typically by utilizing multiple CPUs or processing cores.
Concurrent & Parallel
It is a combination of both concurrent execution and parallel execution. The threads may execute in parallel (based on time-slicing algorithm of the O/S).
Thread is a light-weight unit of execution within a process.
Multi-Threading refers to the ability to create and run more than one independent threads concurrently within a single process.
It enables concurrent program execution.
By default, all the code written in the entire C# application executes under a default thread called “Main Thread”.
Benefits of Threading
Improved Performance
Multithreading can help to increase the overall performance of a program by allowing it take full utilization of multiple cores of the processor.
Improved UI Responsiveness
Multithreading can help to improve the responsiveness of a program by allowing it to execute tasks in background, without making the UI hang / stuck until the task gets fully completed.