Overview

C# refresher based on Pluralsight and LinkedIn C#/ .NET learning paths.

Resources

Study Notes

High-Level
  • C# is a simple general-purpose, multi-paradigm programming language developed by Microsoft
  • C# is managed (execution engine, compilation), static, strongly typed, lexically scoped, imperative, declarative, generic, object-oriented, with functional and component-oriented features
  • C# is resilient to change/ runtime type safety
  • .NET is a free, open-source & cross-platform, developer platform
    • .NET Framework — Windows Only
    • .NET Core/ .NET 5 — Linux, Mac, Windows, ARM
      • Common Language Runtime (CLR)
        • Runtime for all .NET languages, Garbage Collection etc.
      • Base Class Library (BCL) — a core class library
      • Framework Class Library (FCL) — a superset of BCL
        • E.g. networking, cryptography etc.
    • .NET 6 is a unification of different .NET platforms into one, with a single BCL and SDK
      • VS 2022 with Hot Reload, C# 10, ASP.NET Core 6, EF Core 6, C++ updates
    • Xamarin is an open source mobile (iOS and Android) development platform based on mono
      • to be replaced with MAUI in 2022
    • .NET Standard allows to develop cross (.NET) platform
      • e.g. Framework, Core, and Xamarin apps can use one library
  • Software Development Kit (SDK)
    • It contains all the components needed to build apps
Command Line Interface (CLI)
  • dotnet --info
    • list out all environment info, available SDKs, runtimes etc.
  • dotnet new template
    • new project CLI syntax, where the template is console, web, mvc etc.
  • dotnet run --project src\ProjectName
    • (1) dotnet restore
      • restore NuGet packages
    • (2) dotnet build
      • compile source code to DLL (binary/ assembly)

Language Basics

Fundamentals – Class, Prop, Method, Control Statements
// simple class definition
public class MyClass
{
    public string  myField = string.Empty;

    public MyClass()
    {
    }

    public void MyMethod(int parameter1, string parameter2)
    {
        Console.WriteLine($"First {parameter1}, second {parameter2}";
    }

    public int MyAutoImplementedProperty { get; set; }

    private int myPropertyVar;
    public int MyProperty
    {
        get { return myPropertyVar; }
        set { myPropertyVar = value; }
    } 
}

// control flow statements
for(int i = 0; i<1000; i++)
{
  if((i%2) == 0)
  {
    Console.WriteLine($"Even number: {i}");
  }  
}

switch(var_name):
{
  case 'VALUE_1':
    DoSomething();
    break;
  case 'VALUE_2':
    DoSomethingElse();
    break;
  case var x when x == "VALUE_3": // pattern matching
    DoSomethingSpecial();
    break;
  default:
    DefaultAction();
    break
}

// delegate - function pointer
public delegate string WriteMessageLog(string message);
public void WriteLog()
{
  WriteLogDelegate log = new WriteLogDelegate(ReturnMessage); // or just = ReturnMessage;

  // C# supports multi-cast delegates, so that you can add more by
  // log += AnotherHandler;

  var result = log("message");
  Assert.Equal("message", result);
}
string ReturnMessage(string message)
{
  return message;
}

  • C# 8 (VS 2019) — .net core 3.1 (lts), C# 9 — .net 5 (prev)
  • C# can inherit multiple interfaces, not classes
  • Nullable<T> shortcut is T?
  • Ternary operator — condition ? true : false; e.g. x>2 ? "HIGH" : "LOW";
  • ReadOnly variables are assigned at runtime
    • underlying collections can be still modified if a reference is available/ accessible, unlike immutable types
  • A return or break statement cannot exit the finally block
  • Variables declared as var are assigned data type at the compile time
  • Dynamic type in C# escapes compile-time type checking!
  • @ can be used to escape reserved keywords (avoid)
Access Modifiers
public    // code outside of this class will have access to that field
private   // default, available to code within the class definition
protected // accessible withing the same namespace
static    // not associated with an instance but the type
Reference vs. Value Type
var a = new Book("Grades"); // reference type, as b is an address to the value 
var x = 3; // value type, as x holds actual value not a pointer to the value 

Object.ReferenceEquals(ins1, ins2); // checks if the same instance 

// Method parameters are always passed by value, unless REF is used
public void PassByValue(int x) {} 
public void PassByRef(ref int x){} 

// OUT, unlike REF, assumes the vbarioable is already initialized 
public void PassOut(out int x){}
Base Data Types
// Value Types
// Non numeric: bool - System.Boolean, char - System.Char, DateTime - System.DateTime
bool isValid = false;
char grade = 'B';
DateTime currentDateTimeUTC = DateTime.UtcNow;

// Tuple, Struct, Enum
(double, int) t1 = (4.5, 3);
public struct Coords
{
    public Coords(double x, double y)
    {
        X = x;
        Y = y;
    }
    public double X { get; }
    public double Y { get; }
    public override string ToString() => $"({X}, {Y})";
}
enum Season
{
    Spring,
    Summer,
    Autumn,
    Winter
}

// Numbers [bits]
// byte [8] - System.Byte, short [16] - System.Int16, int [32] - System.Int32, 
// long [64] - System.Int64, float [32] - System.Single, double [64] - System.Double, 
// decimal [128] - System.Decimal
int a = 123; 
System.Int32 b = 123;
float myNum = 5.75F;
double myDoubleNum = 5.99D; 
decimal b = 2.1m;

// Reference Types
// string - System.String, object - System.Object, dynamic - System.Object
string name = "name";

// Strings are immutable reference type
// Max object memory is 2GB or 1 billion charachters
// @ is used to declare a multi-line string

// Implicit typig with 'var'
var valueAssignedAtCompileTime = 101;

// Since C# is strongly typed, stick to explicit typing for clarity
String Helpers, Interpolation vs. Concatenation
// interpolation 
var interpolation = $"hello {args[0]}!"; 

// concatination
var concatenation = "hello " + args[0]" + "!"

// For constructing large strings use string builder instead of a string to improve performance
// Immutable vs. mutable data type
StringBuilder sb = new StringBuilder();
sb.Append("Item One");

// Working with whitespaces and nulls
string s = "   ";
bool nullOrEmpty = string.InNullOrEmpty(x);
bool nullOrWhitespace = string.IsNullOrWhitespace(x);

Collections

In general, there are two types of collections used in C# — non-generic collections and generic collections. The System.Collections namespace contains the non-generic collection types and System.Collections.Generic namespace includes the generic collection types. In most cases, it is recommended to use the generic collections because they perform better than non-generic collections (in a single-threaded environment) and minimize exceptions by giving compile-time errors instead.

Array – Single[ ], Multidimensional[ , ], and Jagged[ ][ ]

Arrays are: fixed size, zero-indexed, ordered, the same type items.

Performance: O(1) lookups — by index, O(n) for inserts and deletes in the middle/ beginning of the array, O(1) for inserts and deletes at the end.

// Declare a single-dimensional array of 5 integers
int[] array1 = new int[5];

// Declare and set array element values
int[] array2 = new int[] { 1, 3, 5, 7, 9 };

// Alternative syntax
int[] array3 = { 1, 2, 3, 4, 5, 6 };

// Declare a two dimensional array.         
int[,] multiDimensionalArray1 = new int[2, 3];

// Declare and set array element values.         
int[,] multiDimensionalArray2 = { { 1, 2, 3 }, { 4, 5, 6 } };         

// Declare a jagged array.         
int[][] jaggedArray = new int[6][];         

// Set the values of the first array in the jagged array structure.         
jaggedArray[0] = new int[4] { 1, 2, 3, 4 };

List<T>

Represents a strongly typed list of objects that can be accessed by index. Lists always start empty, and items need to be added. Lists are more flexible than arrays. List<T> is the equivalent of the ArrayList, which implements IList<T>. List<T> performs faster and is less error-prone than the ArrayList.

// Declare a list of ints
List<int> myIntList = new List<int>();

// Enumerating list elements        
foreach (var number in myIntList)
  Console.WriteLine(number);

// Adding elements -- appending to the end of the list
List<T>.Add(item);
List<T>.AddRange(items)

// Inserting element -- insert items in a specific position
List<T>.Inster(index, item);

// Removing elements
List<T>.Remove(item);
List<T>.FindIndex(Predicate<T>);
List<T>.RemoveAr(index);

// NOTE: inserting and removing list elements may cause performance issues on large datasets 
// Refer to 'data structures and algorithm' post for details.
Dictionary<TKey,TValue>

Key-value pairs. Keys must be unique and cannot be null. Values can be null or duplicate. Values can be accessed by passing the associated key in the indexer e.g. myDictionary[key].

// Declare a dict 
Dictionary<string, string> openWith = new Dictionary<string, string>();

// Adding items
openWith.Add("txt", "notepad.exe");

// Iterating dict elements
foreach(KeyValuePair<string, string> kvp in myDictionary)
  Console.WriteLine("Key = {0}, Value = {1}", kvp.Key, kvp.Value);

// Updating dict elements
var cities = new Dictionary<string, string>()
{ 
  {"UK", "London, Manchester, Birmingham"}, 
  {"USA", "Chicago, New York, Washington"}, 
  {"India", "Mumbai, New Delhi, Pune"} 
};

if(cities.ContainsKey("France"))
  cities["France"] = "Paris";

// Throws KeyNotFoundException if a key does not exist

// Remove item
cities.Remove("UK");
Nullable Types

Always express design intent rather than deal with nulls using either nullable value types or non-nullable reference types.

Nullable Value Types are instances of Nullable<T> struct. It represents a valid range of values for the selected type plus a null.

// Value Type
bool = true;  // can hold true or false

// Nullable Value Type 
Nullable<bool> longSyntax = true; // can hold true, false and null
bool? shorthandSyntax = true;

// Strings - reference type (immutable), so that nulls are allowed; helper methods
string text = "sample text";
bool isNullOrEmpty = string.IsNullOrEmpty(text);
bool isNullOrWhitespace = string.IsNullOrWhitespace(text);

//  null-forgiving operator ! 
Exceptions

Exceptions represent any error conditions or unexpected behaviour. In C#, exceptions are objects that inherit from System.Exception and are organized in a hierarchy. We need to handle exceptions, not crash the program, get a chance to fix/ retry, allow for meaningful messages and graceful exit, and error/ audit logging. In general, exceptions provide more readability and less clutter and can be bubbled up the stack.

// Excepations originate from:
// (1): Standard exceptions provided by the .NET (system, e.g., OutOfMemory, StackOverflow)
// (2): Exceptions provides by frameworks/ library author (3rd party, e.g., JsonSerialization)
// (3): Custom application exceptions (app code)

// Base Hierarchy
// Excetpion > SystemException >> OurOfMemoryException
//                             >> StackOverflowException                        
//                             >> ArgumentException
//                                                  >> ArgumentNullException
//                                                  >> ArgumentOutOfRangeException
//           > ArithmeticException >> DivideByZeroException
//                                 >> OverflowException
//           > ApplicationException (depricated)
//           > CustomException

// CONSTRUCTORS 
// Default message and null inner exception
public Exception();

// User-defined message
public Exception(string message);

// User-defined message, wrapped exception
public Exception(string message, Exception innerException);

// THROWING & HANDLING EXCEPTIONS
// When catching, order blocks from the most to the least specific
try
{
  var operation = "/";
  throw new ArgumentOutOfRangeException(nameof(operation));
}
catch(ArgumentNullException exception)
{
  // Handle null
}
catch(CustomException exception)
{
  // Handle custom exception
}
catch
{
  // Handle the rest (Syste.Exception), access to exception variable defined above
}
finally
{
  // Always executed when control leaves try block
  // e.g., call Dispose() to free resources 
}

// Correct way to rethrow exceptions is to throw; not throw ex; 
// to preserve the current call stack
try
{
}
catch (ArgumentNullException ex) when (ex.ParamName == "operation") // exception filters
{
}
catch (Exception ex)
{
  throw;
}

Delegates, Anonymous Methods, and Lambdas

A Delegate holds a reference to a method as a singlecast or multicast type.

Anonymous Method is an inline code that can be used whenever a delegate type is expected.

A Lambda Expression is an anonymous method you can use to create delegates or expression tree types.

using system; 

namespace DataAccessLayer 
{
  public delegate vpooid TestDelegate();
  class Program
  {
    Console
  }
}

Extension Methods

Asynchronous Programming

Concurrent Collections