Chapter 4 - Advanced C#

Delegates

Delegates

// A delegate type declaration is like an abstract method declaration, prefixed with the delegate keyword.
// To create a delegate instance, assign a method to a delegate variable:  

Transformer t = Square;          // Create delegate instance
int result = t(3);               // Invoke delegate
Console.WriteLine (result);      // 9

int Square (int x) => x * x;

delegate int Transformer (int x);   // Our delegate type declaration

Delegates - longhand

Transformer t = new Transformer (Square);  // Create delegate instance
int result = t(3);                         // Invoke delegate
Console.WriteLine (result);                // 9

int Square (int x) => x * x;

delegate int Transformer (int x);          // Delegate type declaration

Delegates - Writing Plug-in Methods

// A delegate variable is assigned a method dynamically. This is useful for writing plug-in methods:

int[] values = { 1, 2, 3 };
Transform (values, Square);   // Hook in the Square method
values.Dump();

values = new int[] { 1, 2, 3 };
Transform (values, Cube);          // Hook in the Cube method
values.Dump();

void Transform (int[] values, Transformer t)
{
  for (int i = 0; i < values.Length; i++)
    values [i] = t (values [i]);
}

int Square (int x) => x * x;
int Cube (int x) => x * x * x;

delegate int Transformer (int x);

Static method target

Transformer t = Test.Square;
Console.WriteLine (t(10));      // 100

class Test
{
  public static int Square (int x) => x * x;
}

delegate int Transformer (int x);

Instance method target

// When a delegate object is assigned to an instance method, the delegate object must maintain
// a reference not only to the method, but also to the instance to which the method belongs:

MyReporter r = new MyReporter();
r.Prefix = "%Complete: ";
ProgressReporter p = r.ReportProgress;
p(99);                                 // 99
Console.WriteLine (p.Target == r);     // True
Console.WriteLine (p.Method);          // Void InstanceProgress(Int32)
r.Prefix = "";
p(99);                                 // 99

public delegate void ProgressReporter (int percentComplete);

class MyReporter
{
  public string Prefix = "";
  public void ReportProgress (int percentComplete) => Console.WriteLine (Prefix + percentComplete);
}

Multicast Delegates

// All delegate instances have multicast capability:

SomeDelegate d = SomeMethod1;
d += SomeMethod2;

d();
" -- SomeMethod1 and SomeMethod2 both fired\r\n".Dump();

d -= SomeMethod1;
d();
" -- Only SomeMethod2 fired".Dump();

void SomeMethod1 () => "SomeMethod1".Dump();
void SomeMethod2 () => "SomeMethod2".Dump();

delegate void SomeDelegate();

Multicast Delegates - ProgressReporter

ProgressReporter p = WriteProgressToConsole;
p += WriteProgressToFile;
Util.HardWork (p);

void WriteProgressToConsole (int percentComplete)
{
  Console.WriteLine (percentComplete);
}

void WriteProgressToFile (int percentComplete)
{
  System.IO.File.WriteAllText ("progress.txt", percentComplete.ToString());
}

delegate void ProgressReporter (int percentComplete);

class Util
{
  public static void HardWork (ProgressReporter p)
  {
    for (int i = 0; i < 10; i++)
    {
      p (i * 10);                           // Invoke delegate
      System.Threading.Thread.Sleep (100);  // Simulate hard work
    }
  }
}

Generic Delegate Types

// A delegate type may contain generic type parameters:
int[] values = { 1, 2, 3 };
Util.Transform (values, Square);      // Dynamically hook in Square
values.Dump();

int Square (int x) => x * x;

public delegate T Transformer<T> (T arg);

// With this definition, we can write a generalized Transform utility method that works on any type:
public class Util
{
  public static void Transform<T> (T[] values, Transformer<T> t)
  {
    for (int i = 0; i < values.Length; i++)
      values[i] = t (values[i]);
  }
}

Func and Action Delegates

// With the Func and Action family of delegates in the System namespace, you can avoid the
// need for creating most custom delegate types:

int[] values = { 1, 2, 3 };
Util.Transform (values, Square);      // Dynamically hook in Square
values.Dump();

int Square (int x) => x * x;

public class Util
{
  // Define this to accept Func<T,TResult> instead of a custom delegate:
  public static void Transform<T> (T[] values, Func<T, T> transformer)
  {
    for (int i = 0; i < values.Length; i++)
      values [i] = transformer (values [i]);
  }
}

Delegates vs Interfaces

// A problem that can be solved with a delegate can also be solved with an interface:

int[] values = { 1, 2, 3 };
Util.TransformAll (values, new Squarer());
values.Dump();

public interface ITransformer
{
  int Transform (int x);
}

public class Util
{
  public static void TransformAll (int[] values, ITransformer t)
  {
    for (int i = 0; i < values.Length; i++)
      values[i] = t.Transform (values[i]);
  }
}

class Squarer : ITransformer
{
  public int Transform (int x) => x * x;
}

Delegates vs Interfaces - Clumsiness

// With interfaces, we’re forced into writing a separate type per transform
// since Test can only implement ITransformer once:

int[] values = { 1, 2, 3 };
Util.TransformAll (values, new Cuber());
values.Dump();

public interface ITransformer
{
  int Transform (int x);
}

public class Util
{
  public static void TransformAll (int[] values, ITransformer t)
  {
    for (int i = 0; i < values.Length; i++)
      values[i] = t.Transform (values[i]);
  }
}

class Squarer : ITransformer
{
  public int Transform (int x) => x * x;
}

class Cuber : ITransformer
{
  public int Transform (int x) => x * x * x;
}

Delegate Type Incompatibility

// Delegate types are all incompatible with each other, even if their signatures are the same:

D1 d1 = Method1;
D2 d2 = d1;            // Compile-time error

static void Method1() { }

delegate void D1();
delegate void D2();

Delegate Type Incompatibility - Workaround

// Delegate types are all incompatible with each other, even if their signatures are the same:

D1 d1 = Method1;
D2 d2 = new D2 (d1);  // Legal

void Method1() { }

delegate void D1();
delegate void D2();

Delegate Equality

// Delegate instances are considered equal if they have the same method targets:

D d1 = Method1;
D d2 = Method1;
Console.WriteLine (d1 == d2);         // True

static void Method1() { }

delegate void D();

Parameter Compatibility (Contravariance)

// A delegate can have more specific parameter types than its method target. This is called contravariance:

delegate void StringAction (string s);

static void Main()
{
  StringAction sa = new StringAction (ActOnObject);
  sa ("hello");
}

static void ActOnObject (object o) => Console.WriteLine (o);   // hello

Return Type Compatibility (Covariance)

// A delegate can have more specific parameter types than its method target. This is called contravariance:

ObjectRetriever o = new ObjectRetriever (RetriveString);
object result = o();
Console.WriteLine (result);      // hello

string RetriveString() => "hello";

delegate object ObjectRetriever();

Type Parameter Variance

/* From C# 4.0, type parameters on generic delegates can be marked as covariant (out) or contravariant (in).

For instance, the System.Func delegate in the Framework is defined as follows:

  public delegate TResult Func<out TResult>();

This makes the following legal:  */

Func<string> x = () => "Hello, world";
Func<object> y = x;

/* The System.Action delegate is defined as follows:

  void Action<in T> (T arg);

This makes the following legal:  */

Action<object> x2 = o => Console.WriteLine (o);
Action<string> y2 = x2;
Events

Events

// The easiest way to declare an event is to put the event keyword in front of a delegate member.
// Code within the containing type has full access and can treat the event as a delegate.
// Code outside of the containing type can only perform += and -= operations on the event.

var stock = new Stock ("MSFT");
stock.PriceChanged += ReportPriceChange;
stock.Price = 123;
stock.Price = 456;

void ReportPriceChange (decimal oldPrice, decimal newPrice)
{
  ("Price changed from " + oldPrice + " to " + newPrice).Dump();
}

public delegate void PriceChangedHandler (decimal oldPrice, decimal newPrice);

public class Stock
{
  string symbol;
  decimal price;
  
  public Stock (string symbol) { this.symbol = symbol; }
  
  public event PriceChangedHandler PriceChanged;
  
  public decimal Price
  {
    get { return price; }
    set
    {
      if (price == value) return;      // Exit if nothing has changed
      decimal oldPrice = price;
      price = value;
      if (PriceChanged != null)      // If invocation list not empty,
        PriceChanged (oldPrice, price);  // fire event.
    }
  }
}

Standard Event Pattern

// There's a standard pattern for writing events. The pattern provides
// consistency across both Framework and user code.

Stock stock = new Stock ("THPW");
stock.Price = 27.10M;
// Register with the PriceChanged event
stock.PriceChanged += stock_PriceChanged;
stock.Price = 31.59M;

void stock_PriceChanged (object sender, PriceChangedEventArgs e)
{
  if ((e.NewPrice - e.LastPrice) / e.LastPrice > 0.1M)
    Console.WriteLine ("Alert, 10% stock price increase!");
}

public class PriceChangedEventArgs : EventArgs
{
  public readonly decimal LastPrice;
  public readonly decimal NewPrice;
  
  public PriceChangedEventArgs (decimal lastPrice, decimal newPrice)
  {
    LastPrice = lastPrice; NewPrice = newPrice;
  }
}

public class Stock
{
  string symbol;
  decimal price;
  
  public Stock (string symbol) {this.symbol = symbol;}
  
  public event EventHandler<PriceChangedEventArgs> PriceChanged;
  
  protected virtual void OnPriceChanged (PriceChangedEventArgs e)
  {
    PriceChanged?.Invoke (this, e);
  }
  
  public decimal Price
  {
    get { return price; }
    set
    {
      if (price == value) return;
      decimal oldPrice = price;
      price = value;
      OnPriceChanged (new PriceChangedEventArgs (oldPrice, price));
    }
  }
}

Standard Event Pattern - Simple EventHandler

// The predefined nongeneric EventHandler delegate can be used when an event doesn't
// carry extra information:

Stock stock = new Stock ("THPW");
stock.Price = 27.10M;
// Register with the PriceChanged event
stock.PriceChanged += stock_PriceChanged;
stock.Price = 31.59M;

void stock_PriceChanged (object sender, EventArgs e)
{
  Console.WriteLine ("New price = " + ((Stock)sender).Price);
}

public class Stock
{
  string symbol;
  decimal price;

  public Stock (string symbol) { this.symbol = symbol; }

  public event EventHandler PriceChanged;

  protected virtual void OnPriceChanged (EventArgs e)
  {
    PriceChanged?.Invoke (this, e);
  }

  public decimal Price
  {
    get { return price; }
    set
    {
      if (price == value) return;
      price = value;
      OnPriceChanged (EventArgs.Empty);
    }
  }
}

Event Accessors

// We can take over the default event implementation by writing our own accessors:

Stock stock = new Stock ("THPW");
stock.Price = 27.10M;
// Register with the PriceChanged event
stock.PriceChanged += stock_PriceChanged;
stock.Price = 31.59M;

void stock_PriceChanged (object sender, EventArgs e)
{
  Console.WriteLine ("New price = " + ((Stock)sender).Price);
}

public class Stock
{
  string symbol;
  decimal price;

  public Stock (string symbol) { this.symbol = symbol; }

  private EventHandler _priceChanged;         // Declare a private delegate

  public event EventHandler PriceChanged
  {
    add { _priceChanged += value; }   // Explicit accessor
    remove { _priceChanged -= value; }    // Explicit accessor
  }

  protected virtual void OnPriceChanged (EventArgs e)
  {
    _priceChanged?.Invoke (this, e);
  }

  public decimal Price
  {
    get { return price; }
    set
    {
      if (price == value) return;
      price = value;
      OnPriceChanged (EventArgs.Empty);
    }
  }
}

Event Accessors - Interfaces

// When explicitly implementing an interface that declares an event, you must use event accessors:

public interface IFoo { event EventHandler Ev; }

class Foo : IFoo
{
  private EventHandler ev;
  
  event EventHandler IFoo.Ev
  {
    add    { ev += value; }
    remove { ev -= value; }
  }
}
Lambda Expressions

Lambda Expressions

// A lambda expression is an unnamed method written in place of a delegate instance.
// A lambda expression has the following form:
//   (parameters) => expression-or-statement-block

Transformer sqr = x => x * x;
Console.WriteLine (sqr (3));    // 9

// Using a statement block instead:
Transformer sqrBlock = x => { return x * x; };
Console.WriteLine (sqr (3));

// Using a generic System.Func delegate:
Func<int, int> sqrFunc = x => x * x;
Console.WriteLine (sqrFunc (3));

// Zero arguments:
Func<string> greeter = () => "Hello, world";
Console.WriteLine (greeter());

// With implicit typing (from C# 10):
var greeter2 = () => "Hello, world";
Console.WriteLine (greeter());

// Using multiple arguments:
Func<string, string, int> totalLength = (s1, s2) => s1.Length + s2.Length;
int total = totalLength ("hello", "world");
total.Dump ("total");

// Explicitly specifying parameter types:
Func<int, int> sqrExplicit = (int x) => x * x;
Console.WriteLine (sqrFunc (3));

delegate int Transformer (int i);

Lambda Expressions - Default Parameters

// Just as ordinary methods can have optional parameters:
void Print (string message = "") => Console.WriteLine (message);

// So, too, can lambda expressions:  
var print = (string message = "") => Console.WriteLine (message);

print ("Hello");
print ();

// This feature is useful with libraries such as ASP.NET Minimal API.

Capturing Outer Variables

// A lambda expression can reference the local variables and parameters of the method
// in which it’s defined (outer variables)

int factor = 2;
Func<int, int> multiplier = n => n * factor;
Console.WriteLine (multiplier (3));           // 6

// Captured variables are evaluated when the delegate is invoked, not when the variables were captured:

factor = 10;
Console.WriteLine (multiplier (3));           // 30

// Lambda expressions can themselves update captured variables:

int seed = 0;
Func<int> natural = () => seed++;
Console.WriteLine (natural());           // 0
Console.WriteLine (natural());           // 1
Console.WriteLine (seed);                // 2

Capturing Outer Variables - Lifetime

// Captured variables have their lifetimes extended to that of the delegate:

static Func<int> Natural()
{
  int seed = 0;
  return () => seed++;    // Returns a closure
}

static void Main()
{
  Func<int> natural = Natural();
  Console.WriteLine (natural());      // 0
  Console.WriteLine (natural());      // 1
}

Capturing Outer Variables - Uniqueness

// A local variable instantiated within a lambda expression is unique per invocation of the
// delegate instance:

static Func<int> Natural()
{    
  return() => { int seed = 0; return seed++; };
}

static void Main()
{
  Func<int> natural = Natural();
  Console.WriteLine (natural());           // 0
  Console.WriteLine (natural());           // 0
}

Static lambdas

Func<int, int> multiplier = static n => n * 2;
multiplier (123).Dump();

Foo();

void Foo()
{
  Multiply (123).Dump();
  
  static int Multiply (int x) => x * 2;   // Local static method
}

Capturing Iteration Variables

// When you capture the iteration variable in a for-loop, C# treats that variable as though it was
// declared outside the loop. This means that the same variable is captured in each iteration:
{
  Action[] actions = new Action[3];

  for (int i = 0; i < 3; i++)
    actions [i] = () => Console.Write (i);
  
  foreach (Action a in actions) a();     // 333 (instead of 123)
}

// Each closure captures the same variable, i. When the delegates are later invoked, each delegate
// sees its value at time of invocation - which is 3. We can illustrate this better by expanding
// the for loop as follows:
{
  Action[] actions = new Action[3];
  int i = 0;
  actions[0] = () => Console.Write (i);
  i = 1;
  actions[1] = () => Console.Write (i);
  i = 2;
  actions[2] = () => Console.Write (i);
  i = 3;
  foreach (Action a in actions) a();    // 333
}

Capturing Iteration Variables - Workaround

// The solution, if we want to write 012, is to assign the iteration variable to a local
// variable that’s scoped inside the loop:

Action[] actions = new Action[3];

for (int i = 0; i < 3; i++)
{
  int loopScopedi = i;
  actions [i] = () => Console.Write (loopScopedi);
}

foreach (Action a in actions) a();     // 012

Anonymous Methods

// Anonymous methods are a C# 2.0 feature that has been subsumed largely by C# 3.0 lambda expressions:

delegate int Transformer (int i);

static void Main()
{
  // This can be done more easily with a lambda expression:
  Transformer sqr = delegate (int x) { return x * x; };
  Console.WriteLine (sqr(3));                            // 9
}

// A unique feature of anonymous methods is that you can omit the parameter declaration entirely - even 
// if the delegate expects them. This can be useful in declaring events with a default empty handler:
public static event EventHandler Clicked = delegate { };
// because it avoids the need for a null check before firing the event.

// The following is also legal:
static void HookUp()
{
  Clicked += delegate { Console.WriteLine ("clicked"); };   // No parameters
}
try Statements and Exceptions

DivideByZeroException unhandled

// Because Calc is called with x==0, the runtime throws a DivideByZeroException: 

int y = Calc (0);
Console.WriteLine (y);

int Calc (int x) { return 10 / x; }

DivideByZeroException handled

// We can catch the DivideByZeroException as follows:

try
{
  int y = Calc (0);
  Console.WriteLine (y);
}
catch (DivideByZeroException ex)
{
  Console.WriteLine ("x cannot be zero");
}
Console.WriteLine ("program completed");

int Calc (int x) { return 10 / x; }

The catch Clause

// You can handle multiple exception types with multiple catch clauses:

static void Main() { MainMain ("one"); }

static void MainMain (params string[] args)
{
  try
  {
    byte b = byte.Parse (args[0]);
    Console.WriteLine (b);
  }
  catch (IndexOutOfRangeException)
  {
    Console.WriteLine ("Please provide at least one argument");
  }
  catch (FormatException)
  {
    Console.WriteLine ("That's not a number!");
  }
  catch (OverflowException)
  {
    Console.WriteLine ("You've given me more than a byte!");
  }
}

static int Calc (int x) { return 10 / x; }

Exception Filters

try
{
  await new HttpClient().GetStringAsync ("http://www.albahari.com/x");
}
catch (HttpRequestException ex) when (ex.StatusCode == HttpStatusCode.NotFound)
{
  "Page not found".Dump();
}
catch (HttpRequestException ex) when (ex.StatusCode == HttpStatusCode.InternalServerError)
{
  "Internal server error!".Dump();
}
catch (HttpRequestException ex) 
{
  $"Some other failure: {ex.StatusCode}".Dump();
}

The finally Block

// finally blocks are typically used for cleanup code:

File.WriteAllText ("file.txt", "test");
ReadFile ();

void ReadFile()
{
  StreamReader reader = null;    // In System.IO namespace
  try
  {
    reader = File.OpenText ("file.txt");
    if (reader.EndOfStream) return;
    Console.WriteLine (reader.ReadToEnd());
  }
  finally
  {
    if (reader != null) reader.Dispose();
  }
}

The using Statement

// The using statement provides an elegant syntax for calling Dispose on
// an IDisposable object within a finally block:

File.WriteAllText ("file.txt", "test");
ReadFile ();

void ReadFile()
{
  using (StreamReader reader = File.OpenText ("file.txt"))
  {
    if (reader.EndOfStream) return;
    Console.WriteLine (reader.ReadToEnd());
  }
}

using Declarations

if (File.Exists ("file.txt"))
{
  using var reader = File.OpenText ("file.txt");
  Console.WriteLine (reader.ReadLine());
}

// reader is now disposed

Throwing Exceptions

// Exceptions can be thrown either by the runtime or in user code:

try
{
  Display (null);
}
catch (ArgumentNullException ex)
{
  Console.WriteLine ("Caught the exception");
}

static void Display (string name)
{
  if (name == null)
    throw new ArgumentNullException (nameof (name));

  Console.WriteLine (name);
}

Shortcut for ArgumentNullException

// Exceptions can be thrown either by the runtime or in user code:

try
{
  Display (null);
}
catch (ArgumentNullException ex)
{
  ex.Dump ("Caught the exception");
}

void Display (string name)
{
  ArgumentNullException.ThrowIfNull (name);
  Console.WriteLine (name);
}

throw Expressions

// Prior to C# 7, throw was always a statement. Now it can also appear as an expression in
// expression-bodied functions:

ProperCase ("test").Dump();
ProperCase (null).Dump();     // throws an ArgumentException

string Foo() => throw new NotImplementedException();

// A throw expression can also appear in a ternary conditional expression:

string ProperCase (string value) =>
  value == null ? throw new ArgumentException ("value") :
  value == "" ? "" :
  char.ToUpper (value [0]) + value.Substring (1);

Rethrowing an Exception

// Rethrowing lets you back out of handling an exception should circumstances turn out to be
// outside what you expected:

string s = null;

try 
{
  s = await new HttpClient().GetStringAsync ("http://www.albahari.com/x");
}
catch (HttpRequestException ex)
{
  if (ex.StatusCode == HttpStatusCode.Forbidden)
    Console.WriteLine ("Forbidden");
  else
    throw;     // Can’t handle other cases, so rethrow
}

s.Dump();

Rethrowing More Specific Exception

//The other common scenario is to rethrow a more specific exception type:

DateTime dt;
string dtString = "2010-4-31";  // Assume we're writing an XML parser and this is from an XML file
try
{
    // Parse a date of birth from XML element data
  dt = XmlConvert.ToDateTime (dtString, XmlDateTimeSerializationMode.Utc);
}
catch (FormatException ex)
{
    throw new XmlException ("Invalid DateTime", ex);
}

The TryXXX Pattern

bool result;
  TryToBoolean ("Bad", out result).Dump ("Successful");
  result = ToBoolean ("Bad");    // throws Exception

bool ToBoolean (string text)
{
  bool returnValue;
  if (!TryToBoolean (text, out returnValue))
    throw new FormatException ("Cannot parse to Boolean");
  return returnValue;
}

bool TryToBoolean (string text, out bool result)
{
  text = text.Trim().ToUpperInvariant();  
  if (text == "TRUE" || text == "YES" || text == "Y")
  {
    result = true;
    return true;
  }
  if (text == "FALSE" || text == "NO" || text == "N")
  {
    result = false;
    return true;
  }
  result = false;
  return false;
}

The Atomicity Pattern

Accumulator a = new Accumulator();
try
{
  a.Add (4, 5);             // a.Total is now 9
  a.Add (1, int.MaxValue);  // Will cause OverflowException
}
catch (OverflowException)
{
  Console.WriteLine (a.Total);  // a.Total is still 9
}

public class Accumulator
{
  public int Total { get; private set; }

  public void Add (params int[] ints)
  {
    bool success = false;
    int totalSnapshot = Total;
    try
    {
      foreach (int i in ints)
      {
        checked { Total += i; }
      }
      success = true;
    }
    finally
    {
      if (!success)
        Total = totalSnapshot;
    }
  }
}
Enumeration and Iterators (see also CH7)

Enumeration

// High-level way of iterating through the characters in the word “beer”:

foreach (char c in "beer")
  Console.WriteLine (c);

// Low-level way of iterating through the same characters:

using (var enumerator = "beer".GetEnumerator())
  while (enumerator.MoveNext())
  {
    var element = enumerator.Current;
    Console.WriteLine (element);
  }

Collection Initializers

// You can instantiate and populate an enumerable object in a single step with collection initializers:
{
  List<int> list = new List<int> {1, 2, 3};
  
  list.Dump();
}

// Equivalent to:
{
  List<int> list = new List<int>();
  list.Add (1);
  list.Add (2);
  list.Add (3);
  
  list.Dump();
}

Collection expressions

// From C# 12, you can use square brackets when initializing a array.
// This is called a collection expression:

int[] array = [1, 2, 3];
array.Dump();

// Collection expressions also work with other collection types:

List<int> list = [1, 2, 3];
list.Dump();

Span<int> span = [1, 2, 3];
span.Dump();

HashSet<int> set = [1, 2, 3];
set.Dump();

ImmutableArray<int> immutable = [1, 2, 3];
immutable.Dump();

// Unfortunately, collection expressions don't have a natural type, so the following is illegal:
// var foo = [1, 2, 3];

Collection expressions - target-typing

// Collection expressions are target-typed, meaning that the type of [1,2,3] depends on the
// type to which it’s assigned. This means that you can omit the type in other scenarios 
// where the compiler can infer it, such as when calling methods:

Foo ([1, 2, 3]);

void Foo (List<int> numbers) => numbers.Dump();

// In the following example, we export timezones to Excel using LINQPad's ToSpreadsheet extension method,
// specifying the particular columns to include.

TimeZoneInfo.GetSystemTimeZones().ToSpreadsheet (membersToInclude: ["Id", "DisplayName", "BaseUtcOffset"]).Open();

Extra - Collection expressions - spread operator

// A collection expression can include other collections if prefixed by the .. (spread) operator:

string[] primaryLightColors = ["Red", "Green", "Blue"];
List<string> primaryPigments = ["Magenta", "Cyan", "Yellow"];

HashSet<string> allColors = [..primaryLightColors, ..primaryPigments, "Black", "White"];
allColors.Dump();

Collection Initializers - dictionaries

var dict1 = new Dictionary<int, string>()
{
  { 5, "five" },
  { 10, "ten" }
};

dict1.Dump();

var dict2 = new Dictionary<int, string>()
{
  [3] = "three",
  [10] = "ten"
};

dict2.Dump();

Iterators

// Whereas a foreach statement is a consumer of an enumerator, an iterator is a producer of an enumerator:

foreach (int fib in Fibs(6))
  Console.Write (fib + "  ");

IEnumerable<int> Fibs (int fibCount)
{
  for (int i = 0, prevFib = 1, curFib = 1; i < fibCount; i++)
  {
    yield return prevFib;
    int newFib = prevFib+curFib;
    prevFib = curFib;
    curFib = newFib;
  }
}

yield break

// The yield break statement indicates that the iterator block should exit early,
// without returning more elements:

foreach (string s in Foo (true))
  Console.WriteLine (s);

static IEnumerable<string> Foo (bool breakEarly)
{
  yield return "One";
  yield return "Two";

  if (breakEarly)
    yield break;

  yield return "Three";
}

Multiple yield Statements

// Multiple yield statements are permitted:

foreach (string s in Foo())
  Console.WriteLine (s);         // Prints "One","Two","Three"

IEnumerable<string> Foo()
{
  yield return "One";
  yield return "Two";
  yield return "Three";
}

Iterators and try-catch blocks

// A yield return statement cannot appear in a try block that has a catch clause:

foreach (string s in Foo())
  Console.WriteLine (s);

IEnumerable<string> Foo()
{
  try { yield return "One"; }   // Illegal
  catch { /*...*/ }
}

Iterators and try-finally blocks

// You can, however, yield within a try block that has (only) a finally block:

foreach (string s in Foo()) s.Dump();

Console.WriteLine();

foreach (string s in Foo())
{
  ("First element is " + s).Dump();
  break;
}

IEnumerable<string> Foo()
{
  try
  {
    yield return "One";
    yield return "Two";
    yield return "Three";
  }
  finally { "Finally".Dump(); }
}

Composing Iterators

// Iterators are highly composable:

foreach (int fib in EvenNumbersOnly (Fibs (6)))
  Console.WriteLine (fib);

IEnumerable<int> Fibs (int fibCount)
{
  for (int i = 0, prevFib = 1, curFib = 1; i < fibCount; i++)
  {
    yield return prevFib;
    int newFib = prevFib + curFib;
    prevFib = curFib;
    curFib = newFib;
  }
}

IEnumerable<int> EvenNumbersOnly (IEnumerable<int> sequence)
{
  foreach (int x in sequence)
    if ((x % 2) == 0)
      yield return x;
}

// See Chapter 7 for more information on Iterators
Nullable (Value) Types

Nullable Types

// To represent null in a value type, you must use a special construct called a nullable type:
{
  int? i = null;                     // Nullable Type
  Console.WriteLine (i == null);     // True
}
// Equivalent to:
{
  Nullable<int> i = new Nullable<int>();
  Console.WriteLine (! i.HasValue);           // True
}

Implicit and Explicit Nullable Conversions

// The conversion from T to T? is implicit, and from T? to T is explicit:

int? x = 5;        // implicit
int y = (int)x;    // explicit

Boxing and Unboxing Nullable Values

// When T? is boxed, the boxed value on the heap contains T, not T?.
// C# also permits the unboxing of nullable types with the as operator:

object o = "string";
int? x = o as int?;
Console.WriteLine (x.HasValue);   // False

Operator Lifting

// Despite the Nullable<T> struct not defining operators such as <, >, or even ==, the 
// following code compiles and executes correctly, thanks to operator lifting:

int? x = 5;
int? y = 10;
{
  bool b = x < y;      // true
  b.Dump();
}
// The above line is equivalent to:
{
  bool b = (x.HasValue && y.HasValue) ? (x.Value < y.Value) : false;
  b.Dump();
}

Operator Lifting - More Examples

// Operator lifting means you can implicitly use T’s operators on T? - without extra code:

int? x = 5;
int? y = null;

// Equality operator examples
Console.WriteLine (x == y);    // False
Console.WriteLine (x == null); // False
Console.WriteLine (x == 5);    // True
Console.WriteLine (y == null); // True
Console.WriteLine (y == 5);    // False
Console.WriteLine (y != 5);    // True

// Relational operator examples
Console.WriteLine (x < 6);     // True
Console.WriteLine (y < 6);     // False
Console.WriteLine (y > 6);     // False

// All other operator examples
Console.WriteLine (x + 5);     // 10
Console.WriteLine (x + y);     // null

Operator Lifting - Equality Operators

// Lifted equality operators handle nulls just like reference types do:

Console.WriteLine (       null ==        null);   // True
Console.WriteLine ((bool?)null == (bool?)null);   // True

Operator Lifting - Relational Operators

// The relational operators work on the principle that it is meaningless to compare null operands:

int? x = 5;
int? y = null;
{
  bool b = x < y;
  b.Dump();
}

// Translation:
{
  bool b = (x.HasValue && y.HasValue) ? (x.Value < y.Value) : false;
  b.Dump();
}

All Other Operators (except for And+Or)

// These operators return null when any of the operands are null. This pattern should be familiar to SQL users:

int? x = 5;
int? y = null;
{
  int? c = x + y;
  c.Dump();
}
// Translation:
{
  int? c = (x.HasValue && y.HasValue)
     ? (int?) (x.Value + y.Value) 
     : null;
     
  c.Dump();
}

Mixing Nullable and Nonnullable Operators

// You can mix and match nullable and non-nullable types
// (this works because there is an implicit conversion from T to T?):

int? a = null;
int b = 2;
int? c = a + b;   // c is null - equivalent to a + (int?)b

c.Dump();

And+Or operators

// When supplied operands of type bool?, the & and | operators treat null as an unknown
// value, rather like with SQL:

bool? n = null;
bool? f = false;
bool? t = true;
Console.WriteLine (n | n);    // (null)
Console.WriteLine (n | f);    // (null)
Console.WriteLine (n | t);    // True
Console.WriteLine (n & n);    // (null)
Console.WriteLine (n & f);    // False
Console.WriteLine (n & t);    // (null)

Null Coalescing Operator

// The ?? operator is the null coalescing operator, and it can be used with both 
// nullable types and reference types. It says “If the operand is non-null, give
// it to me; otherwise, give me a default value.”:

int? x = null;
int y = x ?? 5;
Console.WriteLine (y);  // 5

int? a = null, b = 1, c = 2;
Console.WriteLine (a ?? b ?? c);  // 1 (first non-null value)

Null-Conditional Operator

// Nullable types also work well with the null-conditional operator (see “Null-Conditional Operator”)

System.Text.StringBuilder sb = null;
int? length = sb?.ToString().Length;
length.Dump();

// We can combine this with the null coalescing operator to evaluate to zero instead of null:

int length2 = sb?.ToString().Length ?? 0;  // Evaluates to 0 if sb is null
length2.Dump();

Scenarios for Nullable Types

// Maps to a Customer table in a database

public class Customer
{
  /*...*/
  public decimal? AccountBalance;  // Works well with SQL's nullable column
}

// Color is an ambient property:
public class Row
{
  /*...*/
  Grid parent;
  Color? color;

  public Color Color
  {
  get { return color ?? parent.Color; }
  set { color = Color == parent.Color ? (Color?)null : value; }
  }
}

class Grid
{
  /*...*/
  public Color Color { get; set; }
}
Nullable Reference Types

Null-Forgiving Operator

#nullable enable

// Enable nullable reference types

// This generates a warning:

void Foo1 (string? s) => Console.Write (s.Length);

// which we can remove with the null-forgiving operator:
void Foo2 (string? s) => Console.Write (s!.Length);

// If we add a check, we no longer need the null-forgiving operator in this case:
void Foo3 (string? s)
{
  if (s != null) Console.Write (s.Length);
}

Nullable Reference Types

#nullable enable

string s1 = null;   // Generates a compiler warning!
string? s2 = null;  // OK: s2 is nullable reference type

class Foo
{
  string x;     // Generates a warning
}

Separating the Annotation and Warning Contexts

#nullable enable annotations   // Enable just the nullable annotation context

// Because we've enabled the annotation context, s1 is non-nullable, and s2 is nullable:
public void Foo (string s1, string? s2)
{
  // Our use of s2.Length doesn't generate a warning, however,
  // because we've enabled just the annotation context:
  Console.Write (s2.Length);
}

void Main() 
{
  // Now let's enable the warning context, too  
  #nullable enable warnings
  
  // Notice that this now generates a warning:  
  Foo (null, null);
}
Extension Methods

Extension Methods

// Extension methods allow an existing type to be extended with new methods without altering
// the definition of the original type:

Console.WriteLine ("Perth".IsCapitalized());
// Equivalent to:
Console.WriteLine (StringHelper.IsCapitalized ("Perth"));

// Interfaces can be extended, too:
Console.WriteLine ("Seattle".First());   // S

public static class StringHelper
{
  public static bool IsCapitalized (this string s)
  {
    if (string.IsNullOrEmpty (s)) return false;
    return char.IsUpper (s [0]);
  }

  public static T First<T> (this IEnumerable<T> sequence)
  {
    foreach (T element in sequence)
      return element;

    throw new InvalidOperationException ("No elements!");
  }
}

Extension Method Chaining

// Extension methods, like instance methods, provide a tidy way to chain functions:

string x = "sausage".Pluralize().Capitalize();
x.Dump();

// Equivalent to:
string y = StringHelper.Capitalize (StringHelper.Pluralize ("sausage"));
y.Dump();

// LINQPad's Dump method is an extension method:
"sausage".Pluralize().Capitalize().Dump();

public static class StringHelper
{
  public static string Pluralize (this string s) => s + "s";   // Very naiive implementation!

  public static string Capitalize (this string s) => s.ToUpper();
}

Extension Methods vs Instance Methods

// Any compatible instance method will always take precedence over an extension method:

new Test().Foo ("string");  // Instance method wins, as you'd expect
new Test().Foo (123);       // Instance method still wins

public class Test
{
  public void Foo (object x) { "Instance".Dump(); }    // This method always wins
}

public static class Extensions
{
  public static void Foo (this object t, int x) => "Extension".Dump();
}

Extension Methods vs Extension Methods

// The extension method with more specific arguments wins. Classes & structs are
// considered more specific than interfaces:

"Perth".IsCapitalized().Dump();

static class StringHelper
{
  public static bool IsCapitalized (this string s)
  {
    "StringHelper.IsCapitalized".Dump();
    return char.IsUpper (s[0]);
  }
}

static class EnumerableHelper
{
  public static bool IsCapitalized (this IEnumerable<char> s)
  {
    "Enumerable.IsCapitalized".Dump();
    return char.IsUpper (s.First());
  }
}

Extension Methods on Interfaces

// The extension method with more specific arguments wins. Classes & structs are
// considered more specific than interfaces:

string[] strings = { "a", "b", null, "c" };
foreach (string s in strings.StripNulls())
  Console.WriteLine (s);

static class Test
{
  public static IEnumerable<T> StripNulls<T> (this IEnumerable<T> seq)
  {
    foreach (T t in seq)
      if (t != null)
        yield return t;
  }
}

Extension Methods Calling Another

Console.WriteLine ("FF".IsHexNumber());  // True
Console.WriteLine ("1A".NotHexNumber()); // False

static public class Ext
{
  static public bool IsHexNumber (this string candidate)
  {
    return int.TryParse(candidate, NumberStyles.HexNumber, null, out int _);
  }
  static public bool NotHexNumber (this string candidate)
  {
    return !IsHexNumber (candidate);
  }
}
Anonymous Types

Anonymous Types

// An anonymous type is a simple class created by the compiler on the fly to store a set of values

var dude = new { Name = "Bob", Age = 23 };
dude.Dump();

// The ToString() method is overloaded:
dude.ToString().Dump();

Anonymous Types - Omitting Identifiers

int Age = 23;

// The following:
{
  var dude = new { Name = "Bob", Age, Age.ToString().Length };
  dude.Dump();
}
// is shorthand for:
{
  var dude = new { Name = "Bob", Age = Age, Length = Age.ToString().Length };
  dude.Dump();
}

Anonymous Types - Identity

// Two anonymous type instances will have the same underlying type if their elements are 
// same-typed and they’re declared within the same assembly

var a1 = new { X = 2, Y = 4 };
var a2 = new { X = 2, Y = 4 };
Console.WriteLine (a1.GetType() == a2.GetType());   // True

// Additionally, the Equals method is overridden to perform equality comparisons:

Console.WriteLine (a1 == a2);         // False
Console.WriteLine (a1.Equals (a2));   // True

Anonymous Types - with keyword

var a1 = new { A = 1, B = 2, C = 3, D = 4, E = 5 };
var a2 = a1 with { E = 10 };
a2.Dump();
Tuples

Tuple literals

var bob = ("Bob", 23);    // Allow compiler to infer the element types

Console.WriteLine (bob.Item1);   // Bob
Console.WriteLine (bob.Item2);   // 23

// Tuples are mutable value types:

var joe = bob;                 // joe is a *copy* of job
joe.Item1 = "Joe";             // Change joe’s Item1 from Bob to Joe
Console.WriteLine (bob);       // (Bob, 23)
Console.WriteLine (joe);       // (Joe, 23)

Tuple literals - specifying types

(string,int) bob  = ("Bob", 23);   // var is not compulsory with tuples!

bob.Item1.Dump();
bob.Item2.Dump();

Returning tuple from method

(string, int) person = GetPerson();   // Could use 'var' here if we want
Console.WriteLine (person.Item1);    // Bob
Console.WriteLine (person.Item2);    // 23

static (string,int) GetPerson() => ("Bob", 23);

Naming tuple elements

var tuple = (Name:"Bob", Age:23);

Console.WriteLine (tuple.Name);     // Bob
Console.WriteLine (tuple.Age);      // 23

Naming tuple elements - types

var person = GetPerson();
Console.WriteLine (person.Name);    // Bob
Console.WriteLine (person.Age);     // 23

static (string Name, int Age) GetPerson() => ("Bob", 23);

Tuple type compatibility

(string Name, int Age, char Sex)  bob1 = ("Bob", 23, 'M');
(string Age,  int Sex, char Name) bob2 = bob1;   // No error!

// Our particular example leads to confusing results:
Console.WriteLine (bob2.Name);    // M
Console.WriteLine (bob2.Age);     // Bob
Console.WriteLine (bob2.Sex);     // 23

Aliasing tuples

// From C#, you can leverage the using directive to define aliases for tuples:
using Point = (int, int);

// This feature also works with tuples that have named elements:
using Point1 = (int X, int Y);    // Legal (but not necessarily *good*!)

Point1 p = (3, 4);

// We’ll see shortly how records offer a fully-typed solution with the same level of conciseness:
record Point2 (int X, int Y);

Tuple.Create

ValueTuple<string,int> bob1 = ValueTuple.Create ("Bob", 23);
(string, int) bob2 = ValueTuple.Create ("Bob", 23);

bob1.Dump();
bob2.Dump();

Deconstructing tuples

var bob = ("Bob", 23);

(string name, int age) = bob;   // Deconstruct the bob tuple into
                                // separate variables (name and age).
Console.WriteLine (name);
Console.WriteLine (age);

Deconstructing tuples - method call

var (name, age, sex) = GetBob();
Console.WriteLine (name);        // Bob
Console.WriteLine (age);         // 23
Console.WriteLine (sex);         // M

static (string, int, char) GetBob() => ( "Bob", 23, 'M');

Equality Comparison

var t1 = ("one", 1);
var t2 = ("one", 1);
Console.WriteLine (t1.Equals (t2));    // True

Extra - Tuple Order Comparison

var tuples = new[]
{
  ("B", 50),
  ("B", 40),
  ("A", 30),
  ("A", 20)
};

tuples.OrderBy (x => x).Dump ("They're all now in order!");
Records

Defining a record

new Point (2, 3).Dump();

// Run the line below to look at Point in ILSpy.
// Util.OpenILSpy (typeof (Point));

record Point
{
  public Point (double x, double y) => (X, Y) = (x, y);

  public double X { get; init; }
  public double Y { get; init; }    
}

Defining a record - struct

new Point (2, 3).Dump();

// Run the line below to look at Point in ILSpy.
// Util.OpenILSpy (typeof (Point));

record struct Point
{
  public Point (double x, double y) => (X, Y) = (x, y);

  public double X { get; init; }
  public double Y { get; init; }    
}

Parameter lists in records

new Point (2, 3).Dump();

// Run the line below to look at Point in ILSpy.
// Util.OpenILSpy (typeof (Point));

record Point (double X, double Y);

Parameter lists in records - subclassing

var point3d = new Point3D (2, 3, 4).Dump();

// Run the line below to look at Point in ILSpy.
// Util.OpenILSpy (typeof (Point));

record Point (double X, double Y);

record Point3D (double X, double Y, double Z) : Point (X, Y);

Non-destructive mutation and the 'with' keyword

Point p1 = new Point (3, 3);
Point p2 = p1 with { Y = 4 };
p2.Dump();

Test t1 = new Test (1, 2, 3, 4, 5, 6, 7, 8);
Test t2 = t1 with { A = 10, C = 30 };
t2.Dump();

record Point (double X, double Y);

record Test (int A, int B, int C, int D, int E, int F, int G, int H);

Writing your own copy constructor

Point p1 = new Point (3, 4);
Point p2 = p1 with { Y = 5 };
p2.Dump();

record Point (double X, double Y)
{
  protected Point (Point original)
  {
    "Copying...".Dump();
    this.X = original.X;
    this.Y = original.Y;
  }
}

Property validation in records

var p1 = new Point (2, 3).Dump ("p1");                // OK

try 
{
  var p2 = new Point (double.NaN, 3);
}
catch (ArgumentException ex)
{
  ex.Dump ("Expected error");
}

try
{
  var p3 = p1 with { X = double.NaN };
}
catch (ArgumentException ex)
{
  ex.Dump ("Expected error");
}

record Point
{
  // Notice that we assign x to the X property (and not the _x field):
  public Point (double x, double y) => (X, Y) = (x, y);

  double _x;
  public double X
  { 
    get => _x;
    init
    {
      if (double.IsNaN (value))
        throw new ArgumentException ("X Cannot be NaN");
      _x = value;
    }
  }
  public double Y { get; init; }
}

Record with simple calculated property

Point p1 = new Point (2, 3);
Console.WriteLine (p1.DistanceFromOrigin);

record Point (double X, double Y)
{
  // This gets recomputes every time we call it.
  public double DistanceFromOrigin => Math.Sqrt (X * X + Y * Y);
}

Record with read-only and calculated property

Point p1 = new Point (2, 3);
Console.WriteLine (p1.DistanceFromOrigin);

record Point
{
  public double X { get; }
  public double Y { get; }
  public double DistanceFromOrigin { get; }
  
  // We calculate DistanceFromOrigin just once.
  // This works, but doesn't allow for non-destructive mutation.

  public Point (double x, double y) =>
    (X, Y, DistanceFromOrigin) = (x, y, Math.Sqrt (x * x + y * y));
}

Record with lazy calculated field

Point p1 = new Point (2, 3);
Console.WriteLine (p1.DistanceFromOrigin);

Point p2 = p1 with { Y = 4 };
Console.WriteLine (p2.DistanceFromOrigin);

// The best solution is to use lazy evaluation.

record Point
{
  public Point (double x, double y) => (X, Y) = (x, y);

  double _x, _y;
  public double X { get => _x; init { _x = value; _distance = null; } }
  public double Y { get => _y; init { _y = value; _distance = null; } }

  double? _distance;
  public double DistanceFromOrigin => _distance ??= Math.Sqrt (X * X + Y * Y);
}

Lazy calculated field - alternative

Point p1 = new Point (2, 3);
Console.WriteLine (p1.DistanceFromOrigin);

Point p2 = p1 with { Y = 4 };
Console.WriteLine (p2.DistanceFromOrigin);

// This also works, but is not quite as efficient if we had additional
// properties that weren't part of the calculation.

record Point (double X, double Y)
{
  double? _distance;
  public double DistanceFromOrigin => _distance ??= Math.Sqrt (X * X + Y * Y);

  protected Point (Point other) => (X, Y) = other;
}

Primary constructors - readonly property

var student = new Student ("2021000477", "Bloggs", "Joe").Dump();

record Student (string ID, string LastName, string GivenName)
{
  // ID in the property initializer refers to the primary constructor parameter:
  public string ID { get; } = ID;

  // ID in the field initializer refers to the primary constructor parameter:
  public readonly int EnrolmentYear = int.Parse (ID.Substring (0, 4));

  // We can't non-destructive mutate ID, so storing it in this field is safe.
}

Primary constructors and property validation

var p1 = new Person1 (null).Dump();   // Null check is bypassed.

try
{
  var p2 = new Person2 (null).Dump();   // Null check succeeds (throws).
}
catch (ArgumentNullException ex)
{
  ex.Dump ("Expected exception");
}

// Primary constructors don't play well when you need property validation:
record Person1 (string Name)
{
  string _name = Name;         // Assigns to *FIELD*
  public string Name
  {
    get => _name;
    init => _name = value ?? throw new ArgumentNullException ("Name");
  }
}

// The easiest solution is usually to write the constructor yourself:
record Person2
{
  public Person2 (string name) => Name = name;  // Assigns to *PROPERTY*

  string _name;
  public string Name
  {
    get => _name;
    init => _name = value ?? throw new ArgumentNullException ("Name");
  }
}

Records and equality comparison

var p1 = new Point (1, 2);
var p2 = new Point (1, 2);
Console.WriteLine (p1.Equals (p2));   // True
Console.WriteLine (p1 == p2);         // True

record Point (double X, double Y);

Record with customized equality comparison

Console.WriteLine (new Point1 (1, 2) == new Point1 (1, 2));   // False
Console.WriteLine (new Point2 (1, 2) == new Point2 (1, 2));   // True

record Point1 (double X, double Y)
{
  static int _nextInstance;
  double _someOtherField = _nextInstance++;
}

record Point2 (double X, double Y)
{
  static int _nextInstance;
  double _someOtherField = _nextInstance++;

  public virtual bool Equals (Point2 other) =>
    other != null && X == other.X && Y == other.Y;
    
  public override int GetHashCode() => (X, Y).GetHashCode();
}
Patterns

var pattern

IsJanetOrJohn ("Janet").Dump();
IsJanetOrJohn ("john").Dump();

bool IsJanetOrJohn (string name) =>
  name.ToUpper() is var upper && (upper == "JANET" || upper == "JOHN");

Constant pattern

Foo (3);

void Foo (object obj)
{
  // C# won’t let you use the == operator, because obj is object.
  // However, we can use ‘is’
  if (obj is 3) Console.WriteLine ("three");
}

Relational patterns

int x = 150;
if (x is > 100) Console.WriteLine ("x is greater than 100");

GetWeightCategory (15).Dump();
GetWeightCategory (20).Dump();
GetWeightCategory (25).Dump();

string GetWeightCategory (decimal bmi) => bmi switch
{
  < 18.5m => "underweight",
  < 25m => "normal",
  < 30m => "overweight",
  _ => "obese"
};

Relational patterns with object type

object obj = 2m;                  // decimal
Console.WriteLine (obj is < 3m);  // True
Console.WriteLine (obj is < 3);   // False

Pattern combinators

IsJanetOrJohn ("Janet").Dump();
IsVowel ('e').Dump();
Between1And9 (5).Dump();
IsLetter ('!').Dump();

bool IsJanetOrJohn (string name) => name.ToUpper() is "JANET" or "JOHN";

bool IsVowel (char c) => c is 'a' or 'e' or 'i' or 'o' or 'u';

bool Between1And9 (int n) => n is >= 1 and <= 9;

bool IsLetter (char c) => c is >= 'a' and <= 'z'
                            or >= 'A' and <= 'Z';

Not pattern

object obj = true;

if (obj is not string) 
  "obj is not a string".Dump();

Tuple patterns

AverageCelsiusTemperature (Season.Spring, true).Dump();

int AverageCelsiusTemperature (Season season, bool daytime) =>
  (season, daytime) switch
  {
    (Season.Spring, true) => 20,
    (Season.Spring, false) => 16,
    (Season.Summer, true) => 27,
    (Season.Summer, false) => 22,
    (Season.Fall, true) => 18,
    (Season.Fall, false) => 12,
    (Season.Winter, true) => 10,
    (Season.Winter, false) => -2,
    _ => throw new Exception ("Unexpected combination")
  };

enum Season { Spring, Summer, Fall, Winter };

Positional pattern

var p = new Point (2, 2);
Console.WriteLine (p is (2, 2));                     // True
Console.WriteLine (p is (var x, var y) && x == y);   // True

Print (new Point (0, 0)).Dump();
Print (new Point (1, 1)).Dump();

string Print (object obj) => obj switch
{
  Point (0, 0) => "Empty point",
  Point (var x, var y) when x == y => "Diagonal",
 
  _ => "Other"
};

record Point (int X, int Y);

Positional pattern - with class

Print (new Point (0, 0)).Dump();
Print (new Point (1, 1)).Dump();

string Print (object obj) => obj switch
{
  Point (0, 0) => "Empty point",
  Point (var x, var y) when x == y => "Diagonal",
  
  _ => "Other"
};

class Point
{
  public readonly int X, Y;
  
  public Point (int x, int y) => (X, Y) = (x, y);

  // Here's our deconstructor:
  public void Deconstruct (out int x, out int y)
  {
    x = X; y = Y;
  }
}

Property pattern with is operator

object obj = "test";

if (obj is string { Length:4 })
  Console.WriteLine ("string with length of 4");

Property pattern with switch

Console.WriteLine (ShouldAllow (new Uri ("http://www.linqpad.net")));
Console.WriteLine (ShouldAllow (new Uri ("ftp://ftp.microsoft.com")));
Console.WriteLine (ShouldAllow (new Uri ("tcp:foo.database.windows.net")));

bool ShouldAllow (Uri uri) => uri switch
{
  { Scheme: "http", Port: 80 } => true,
  { Scheme: "https", Port: 443 } => true,
  { Scheme: "ftp", Port: 21 } => true,
  { IsLoopback: true } => true,
  _ => false
};

Property pattern with switch - nested

Console.WriteLine (ShouldAllow (new Uri ("http://www.linqpad.net")));
Console.WriteLine (ShouldAllow (new Uri ("ftp://ftp.microsoft.com")));
Console.WriteLine (ShouldAllow (new Uri ("tcp:foo.database.windows.net")));

bool ShouldAllow (Uri uri) => uri switch
{
  { Scheme: { Length: 4 }, Port: 80 } => true,
  _ => false
};

Property pattern with switch - nested simplified

Console.WriteLine (ShouldAllow (new Uri ("http://www.linqpad.net")));
Console.WriteLine (ShouldAllow (new Uri ("ftp://ftp.microsoft.com")));
Console.WriteLine (ShouldAllow (new Uri ("tcp:foo.database.windows.net")));

bool ShouldAllow (Uri uri) => uri switch
{
  { Scheme.Length: 4, Port: 80 } => true,   // Simplified syntax (from C# 10)
  _ => false
};

Property pattern with relational pattern

Console.WriteLine (ShouldAllow (new Uri ("http://www.linqpad.net")));
Console.WriteLine (ShouldAllow (new Uri ("ftp://ftp.microsoft.com")));
Console.WriteLine (ShouldAllow (new Uri ("tcp:foo.database.windows.net")));

bool ShouldAllow (Uri uri) => uri switch
{
  { Host: { Length: < 1000 }, Port: > 0 } => true,
  _ => false
};

Property pattern with switch - with when clause

Console.WriteLine (ShouldAllow (new Uri ("http://www.linqpad.net")));
Console.WriteLine (ShouldAllow (new Uri ("ftp://ftp.microsoft.com")));
Console.WriteLine (ShouldAllow (new Uri ("tcp:foo.database.windows.net")));

bool ShouldAllow (Uri uri) => uri switch
{
  { Scheme: "http" } when string.IsNullOrWhiteSpace (uri.Query) => true,
  _ => false
};

Property pattern with type pattern

Console.WriteLine (ShouldAllow (new Uri ("http://www.linqpad.net")));
Console.WriteLine (ShouldAllow (new Uri ("ftp://ftp.microsoft.com")));
Console.WriteLine (ShouldAllow (new Uri ("tcp:foo.database.windows.net")));

bool ShouldAllow (object uri) => uri switch
{
  Uri { Scheme: "http", Port: 80 } httpUri => httpUri.Host.Length < 1000,
  Uri { Scheme: "https", Port: 443 } => true,
  Uri { Scheme: "ftp", Port: 21 } => true,
  Uri { IsLoopback: true } => true,
  _ => false
};

Property pattern with type pattern and when

Console.WriteLine (ShouldAllow (new Uri ("http://www.linqpad.net")));
Console.WriteLine (ShouldAllow (new Uri ("ftp://ftp.microsoft.com")));
Console.WriteLine (ShouldAllow (new Uri ("tcp:foo.database.windows.net")));

bool ShouldAllow (object uri) => uri switch
{
  Uri { Scheme: "http", Port: 80 } httpUri
                                     when httpUri.Host.Length < 1000 => true,
  Uri { Scheme: "https", Port: 443 } => true,
  Uri { Scheme: "ftp", Port: 21 } => true,
  Uri { IsLoopback: true } => true,
  _ => false
};

Property pattern with property variable

Console.WriteLine (ShouldAllow (new Uri ("http://www.linqpad.net")));
Console.WriteLine (ShouldAllow (new Uri ("ftp://ftp.microsoft.com")));
Console.WriteLine (ShouldAllow (new Uri ("tcp:foo.database.windows.net")));

bool ShouldAllow (Uri uri) => uri switch
{
  { Scheme: "http", Port: 80, Host: var host } => host.Length < 1000,
  { Scheme: "https", Port: 443 } => true,
  { Scheme: "ftp", Port: 21 } => true,
  { IsLoopback: true } => true,
  _ => false
};

List patterns

// List patterns (from C# 11) work with any collection type that is countable
// (with a Count or Length property) and indexable (with an indexer of type int or System.Index).

// A list pattern matches a series of elements in square brackets:
int[] numbers = { 0, 1, 2, 3, 4 };
Console.Write (numbers is [0, 1, 2, 3, 4]);   // True

// An underscore matches a single element of any value:
Console.Write (numbers is [0, 1, _, _, 4]);   // True

// The var pattern also works in matching a single element:
Console.Write (numbers is [0, 1, var x, 3, 4] && x > 1);   // True

// Two dots indicate a slice.A slice matches zero or more elements:
Console.Write (numbers is [0, .., 4]);    // True

// With arrays and other types that support indices and ranges
//   - see query://../../Chapter_2_-_C#_Language_Basics/Arrays/Indices 
//     and query://../../Chapter_2_-_C#_Language_Basics/Arrays/Ranges 
// you can follow a slice with a var pattern:
Console.Write (numbers is [0, .. var mid, 4] && mid.Contains (2)); // True

// A list pattern can include at most one slice.
Attributes (see also CH19)

Attaching Attributes

new Foo();   // Generates a warning because Foo is obsolete

[Obsolete]
public class Foo 
{  
}

Named and Positional Attribute Parameters

void Main()
{
}

[XmlType ("Customer", Namespace = "http://oreilly.com")]
public class CustomerEntity
{  
}

Applying Attributes to Assemblies and Backing Fields

[assembly: AssemblyFileVersion ("1.2.3.4")]

class Foo
{
  [field: NonSerialized]
  public int MyProperty { get; set; }
}

void Main()
{
}

Applying Attributes to lambda expressions

Action<int> a = [Description ("Method")]
                [return: Description ("Return value")]
                ([Description ("Parameter")]int x) => Console.WriteLine (x);

a.GetMethodInfo().GetCustomAttributes().Dump();
a.GetMethodInfo().GetParameters() [0].GetCustomAttributes().Dump();
a.GetMethodInfo().ReturnParameter.GetCustomAttributes().Dump();

Specifying Multiple Attributes

[assembly:CLSCompliant(false)]

[Serializable, Obsolete, CLSCompliant (false)]
public class Bar1 {}

[Serializable]
[Obsolete]
[CLSCompliant (false)]
public class Bar2 {}

[Serializable, Obsolete]
[CLSCompliant (false)]
public class Bar3 {}

Caller Info Attributes

static void Main() => Foo();

static void Foo (
  [CallerMemberName] string memberName = null,
  [CallerFilePath] string filePath = null,
  [CallerLineNumber] int lineNumber = 0)
{
  Console.WriteLine (memberName);
  Console.WriteLine (filePath);
  Console.WriteLine (lineNumber);
}

Caller Info Attributes - INotifyPropertyChanged

var foo = new Foo();
foo.PropertyChanged += (sender, args) => args.Dump ("Property changed!");
foo.CustomerName = "asdf";

public class Foo : INotifyPropertyChanged
{
  public event PropertyChangedEventHandler PropertyChanged = delegate { };

  void RaisePropertyChanged ([CallerMemberName] string propertyName = null)
    => PropertyChanged (this, new PropertyChangedEventArgs (propertyName));

  string customerName;
  public string CustomerName
  {
    get => customerName;
    set
    {
      if (value == customerName) return;
      customerName = value;
      RaisePropertyChanged();
      // The compiler converts the above line to:
      // RaisePropertyChanged ("CustomerName");
    }
  }
}

CallerArgumentExpression

Print (Math.PI * 2);
Print (Math.PI /*(π)*/ * 2);

void Print (double number,
           [CallerArgumentExpression("number")] string expr = null)
  => Console.WriteLine (expr);

Assert (2 + 2 == 5);

void Assert (bool condition,
            [CallerArgumentExpression ("condition")] string message = null)
{
  if (!condition) throw new Exception ("Assertion failed: " + message);
}
Dynamic Binding (see also CH20)

Custom Binding

// Custom binding occurs when a dynamic object implements IDynamicMetaObjectProvider:

dynamic d = new Duck();
d.Quack();                  // Quack method was called
d.Waddle();                 // Waddle method was called

public class Duck : System.Dynamic.DynamicObject
{
  public override bool TryInvokeMember (
    InvokeMemberBinder binder, object[] args, out object result)
  {
    Console.WriteLine (binder.Name + " method was called");
    result = null;
    return true;
  }
}

Language Binding

// Language binding occurs when a dynamic object does not implement IDynamicMetaObjectProvider:

int x = 3, y = 4;
Console.WriteLine (Mean (x, y));

static dynamic Mean (dynamic x, dynamic y) => (x + y) / 2;

RuntimeBinderException

// If a member fails to bind, a RuntimeBinderException is thrown. This can be
// thought of like a compile-time error at runtime:

dynamic d = 5;
d.Hello();                  // throws RuntimeBinderException

Runtime Representation of Dynamic

// The following expression is true, although the compiler does not permit it:
// typeof (dynamic) == typeof (object)

// This principle extends to constructed types and array types:
(typeof (List<dynamic>) == typeof (List<object>)).Dump();    // True

(typeof (dynamic[]) == typeof (object[])).Dump();    // True

// Like an object reference, a dynamic reference can point to an object of any type (except pointer types):
dynamic x = "hello";
Console.WriteLine (x.GetType().Name);  // String

x = 123;  // No error (despite same variable)
Console.WriteLine (x.GetType().Name);  // Int32

// You can convert from object to dynamic to perform any dynamic operation you want on an object:
object o = new System.Text.StringBuilder();
dynamic d = o;
d.Append ("hello");
Console.WriteLine (o);   // hello

Dynamic Conversions

// The dynamic type has implicit conversions to and from all other types:

int i = 7;
dynamic d = i;
int j = d;        // Implicit conversion (or more precisely, an *assignment conversion*)

j.Dump();

// The following throws a RuntimeBinderException because an int is not implicitly convertible to a short:
short s = d;

var vs dynamic

// var says, “let the compiler figure out the type”.
// dynamic says, “let the runtime figure out the type”.

dynamic x = "hello";  // Static type is dynamic, runtime type is string
var y = "hello";      // Static type is string, runtime type is string
int i = x;            // Run-time error
int j = y;            // Compile-time error

Static type of var can be dynamic

// The static type of a variable declared of type var can be dynamic:

dynamic x = "hello";
var y = x;            // Static type of y is dynamic
int z = y;            // Run-time error

Dynamic Expressions

// Trying to consume the result of a dynamic expression with a void return type is
// prohibited — just as with a statically typed expression. However, the error occurs at runtime:

dynamic list = new List<int>();
var result = list.Add (5);         // RuntimeBinderException thrown

// Expressions involving dynamic operands are typically themselves dynamic:
dynamic x = 2;
var y = x * 3;       // Static type of y is dynamic

// However, casting a dynamic expression to a static type yields a static expression:
dynamic a = 2;
var b = (int)2;      // Static type of b is int

// And constructor invocations always yield static expressions:
dynamic capacity = 10;
var sb = new System.Text.StringBuilder (capacity);
int len = sb.Length;

Dynamic Calls without Dynamic Receivers

// You can also call statically known functions with dynamic arguments.
// Such calls are subject to dynamic overload resolution:

static void Foo (int x)    { Console.WriteLine ("1"); }
static void Foo (string x) { Console.WriteLine ("2"); }

static void Main()
{
  dynamic x = 5;
  dynamic y = "watermelon";

  Foo (x);                // 1
  Foo (y);                // 2
}

Static Types in Dynamic Expressions

// Static types are also used — wherever possible — in dynamic binding:

// Note: the following sometimes throws a RuntimeBinderException in Framework 4.0 beta 2. This is a bug.

static void Foo (object x, object y) { Console.WriteLine ("oo"); }
static void Foo (object x, string y) { Console.WriteLine ("os"); }
static void Foo (string x, object y) { Console.WriteLine ("so"); }
static void Foo (string x, string y) { Console.WriteLine ("ss"); }

static void Main()
{
  object o = "hello";
  dynamic d = "goodbye";
  Foo (o, d);               // os
}

Uncallable Functions

// You cannot dynamically call:
//  • Extension methods (via extension method syntax)
//  • Any member of an interface
//  • Base members hidden by a subclass

IFoo f = new Foo();
dynamic d = f;
try
{
  d.Test();             // Exception thrown
}
catch (RuntimeBinderException error)
{
  error.Dump ("You cannot call explicit interface members via dynamic binding");
}
// A workaround is to use the Uncapsulator library (available on NuGet, and built into LINQPad)
dynamic u = f.Uncapsulate();
u.Test();

interface IFoo { void Test(); }
class Foo : IFoo { void IFoo.Test() => "Test".Dump(); }
Operator Overloading (see also CH6)

Operator Functions

// An operator is overloaded by declaring an operator function:

Note B = new Note (2);
Note CSharp = B + 2;
CSharp.SemitonesFromA.Dump();

CSharp += 2;
CSharp.SemitonesFromA.Dump();

public struct Note
{
  int value;
  public int SemitonesFromA => value;

  public Note (int semitonesFromA) { value = semitonesFromA; }

  public static Note operator + (Note x, int semitones)
    => new Note (x.value + semitones);

  // See the last example in "Equality Comparison", Chapter 6 for an example of overloading the == operator
}

Operator Functions - checked

// From C# 11, when you declare an operator function, you can also declare a checked version.
// The checked version will be called inside checked expressions or blocks.

Note B = new Note (2);
Note other = checked (B + int.MaxValue);  // throws OverflowException

public struct Note
{
  int value;
  public int SemitonesFromA => value;

  public Note (int semitonesFromA) { value = semitonesFromA; }

  public static Note operator + (Note x, int semitones)
    => new Note (x.value + semitones);

  public static Note operator checked + (Note x, int semitones)
    => checked (new Note (x.value + semitones));
}

Custom Implicit and Explicit Conversions

// Implicit and explicit conversions are overloadable operators:

Note n = (Note)554.37;  // explicit conversion
double x = n;           // implicit conversion
x.Dump();

public struct Note
{
  int value;
  public int SemitonesFromA { get { return value; } }

  public Note (int semitonesFromA) { value = semitonesFromA; }

  // Convert to hertz
  public static implicit operator double (Note x) => 440 * Math.Pow (2, (double)x.value / 12);

  // Convert from hertz (accurate to the nearest semitone)
  public static explicit operator Note (double x) =>
    new Note ((int)(0.5 + 12 * (Math.Log (x / 440) / Math.Log (2))));
}

Overloading true and false

// The true and false operators are overloaded in the extremely rare case of types that
// are boolean “in spirit”, but do not have a conversion to bool.

// An example is the System.Data.SqlTypes.SqlBoolean type which is defined as follows:

SqlBoolean a = SqlBoolean.Null;
if (a)
  Console.WriteLine ("True");
else if (!a)
  Console.WriteLine ("False");
else
  Console.WriteLine ("Null");

public struct SqlBoolean
{
  public static bool operator true (SqlBoolean x) => x.m_value == True.m_value;

  public static bool operator false (SqlBoolean x) => x.m_value == False.m_value;

  public static SqlBoolean operator ! (SqlBoolean x)
  {
    if (x.m_value == Null.m_value) return Null;
    if (x.m_value == False.m_value) return True;
    return False;
  }

  public static readonly SqlBoolean Null = new SqlBoolean (0);
  public static readonly SqlBoolean False = new SqlBoolean (1);
  public static readonly SqlBoolean True = new SqlBoolean (2);

  SqlBoolean (byte value) { m_value = value; }
  byte m_value;
}
Static Polymorphism

Static Polymorphism - example

// Consider the following interface, which defines a static method to create a random instance of some type T:
interface ICreateRandom<T>
{
  static abstract T CreateRandom();  // Create a random instance of T
}

// Let's implement that interface:
record Point (int X, int Y) : ICreateRandom<Point>
{
  static Random rnd = new();
  public static Point CreateRandom() => new Point (rnd.Next(), rnd.Next());
}

void Main()
{
  // To call this method via the interface, we use a constrained type parameter.
  // The following method creates an array of test data using this approach:

  T[] CreateTestData<T> (int count) where T : ICreateRandom<T>
  {
    T[] result = new T [count];
    for (int i = 0; i < count; i++)
      result [i] = T.CreateRandom();
    return result;
  }
  
  Point[] testData = CreateTestData<Point> (50);  // Create 50 random Points.
  testData.Dump();
  
  // Our call to the static CreateRandom method in CreateTestData is polymorphic because it works
  // not just with Point, but with any type that implements ICreateRandom<T>. This is different to
  // instance polymorphism, because we don’t need an instance of ICreateRandom<T> on which to call
  // CreateRandom; we call CreateRandom on the type itself.
}

Polymorphic operators

// Because operators are essentially static functions (see “Operator Overloading”),
// operators can also be declared as static virtual interface members:
interface IAddable<T> where T : IAddable<T>
{
  abstract static T operator + (T left, T right);
}

// Here’s how we can implement the interface:
record Point (int X, int Y) : IAddable<Point>
{
  public static Point operator + (Point left, Point right) =>
    new Point (left.X + right.X, left.Y + right.Y);
}

void Main()
{
  // With a constrained type parameter, we can then write a method that calls our 
  // addition operator polymorphically (with edge-case handling omitted for brevity):
  
  T Sum<T> (params T[] values) where T : IAddable<T>
  {
    T total = values [0];
    for (int i = 1; i < values.Length; i++)
      total += values [i];
    return total;
  }
  
  Sum (new Point (1, 2), new Point (3, 4)).Dump();
  
  //Our call to the + operator (via the += operator) is polymorphic because it binds to
  // IAddable<T>, not Point. Hence, our Sum method works with all types that implement IAddable<T>.
}

Generic Math

// .NET 7 introduced the INumber<TSelf> interface to unify arithmetic operations across 
// numeric types, allowing generic methods such as the following to be written:

T Sum<T> (params T[] numbers) where T : System.Numerics.INumber<T>
{
  T total = T.Zero;
  foreach (T n in numbers)
    total += n;      // Invokes addition operator for any numeric type
  return total;
}

int intSum = Sum (3, 5, 7);
double doubleSum = Sum (3.2, 5.3, 7.1);
decimal decimalSum = Sum (3.2m, 5.3m, 7.1m);

// The System.Numerics namespace also contains interfaces that are not part of INumber for
// operations specific to certain kinds of numbers (such as floating-point). To compute a
// root-mean-square, for instance, we can add the IRootFunctions<T> interface to the 
// constraint list to expose its static RootN method to T:

T RMS<T> (params T[] values) where T : System.Numerics.INumber<T>, System.Numerics.IRootFunctions<T>
{
  T total = T.Zero;
  for (int i = 0; i < values.Length; i++)
    total += values [i] * values [i];
  // Use T.CreateChecked to convert values.Length (type int) to T.
  T count = T.CreateChecked (values.Length);
  return T.RootN (total / count, 2);   // Calculate square root
}

float rms1 = RMS (5f, 10f, 20f).Dump();
double rms2 = RMS (5d, 10d, 20d).Dump();
Unsafe Code and Pointers (see also CH25)

Unsafe Code

// C# supports direct memory manipulation via pointers within blocks of code marked unsafe
// and compiled with the /unsafe compiler option. LINQPad implicitly compiles with this option.

// Here's how to use pointers to quickly process a bitmap:

int [,] bitmap = { { 0x101010, 0x808080, 0xFFFFFF }, { 0x101010, 0x808080, 0xFFFFFF } };
BlueFilter (bitmap);
bitmap.Dump();

unsafe static void BlueFilter (int [,] bitmap)
{
  int length = bitmap.Length;
  fixed (int* b = bitmap)
  {
    int* p = b;
    for (int i = 0; i < length; i++)
      *p++ &= 0xFF;
  }
}

Pinning variables with fixed

// Value types declared inline within reference types require the reference type to be pinned:

Test test = new Test();
unsafe
{
  fixed (int* p = &test.X)   // Pins test
  {
    *p = 9;
  }
  Console.WriteLine (test.X);
}

class Test
{
  public int X;
}

The Pointer-to-Member Operator

// In addition to the & and * operators, C# also provides the C++ style -> operator,
// which can be used on structs:

Test test = new Test();
Test * p = &test;
p->X = 9;
Console.WriteLine (test.X);

struct Test
{
  public int X;
}

The stackalloc keyword

// Memory can be allocated in a block on the stack explicitly using the stackalloc keyword:

unsafe
{
  int* a = stackalloc int [10];
  for (int i = 0; i < 10; ++i)
    Console.WriteLine (a[i]);   // Print raw memory
}

Fixed-Size Buffers

// Memory can be allocated in a block within a struct using the fixed keyword:

new UnsafeClass ("Christian Troy");

unsafe struct UnsafeUnicodeString
{
  public short Length;
  public fixed byte Buffer[30];
}

unsafe class UnsafeClass
{
  UnsafeUnicodeString uus;
  public UnsafeClass (string s)
  {
    uus.Length = (short)s.Length;
    fixed (byte* p = uus.Buffer)
    for (int i = 0; i < s.Length; i++)
      p[i] = (byte) s[i];
  }
}

void-star

// A void pointer (void*) makes no assumptions about the type of the underlying data and is
// useful for functions that deal with raw memory:

short[] a = { 1, 1, 2, 3, 5, 8, 13, 21, 34, 55 };
unsafe
{
  fixed (short* p = a)
  {
    //sizeof returns size of value-type in bytes
    Zap (p, a.Length * sizeof (short));
  }
}
foreach (short x in a)
  System.Console.WriteLine (x);   // Prints all zeros

unsafe void Zap (void* memory, int byteCount)
{
  byte* b = (byte*)memory;
  for (int i = 0; i < byteCount; i++)
    *b++ = 0;
}

Native-sized integers

// The nint and unint native-sized integer types are sized to match the address space
// of the processor and operating system at runtime (in practice, 32 or 64 bits).

// Native-sized integers behave much like standard integers, with support
// for arithmetic operations, overflow check operators, and so on:

nint x = 123;
nint y = 234;
checked
{
  nint sum = x + y, product = x * y;
  product.Dump();
}

// When working with pointers, native-sized integers can improve efficiency because the 
// result of subtracting two pointers in C# is always a 64-bit integer (long), which is 
// inefficient  on 32-bit platforms. By first casting the pointers to nint, the result 
// of a subtraction is also nint (which will be 32 bits on a 32-bit platform):

unsafe nint AddressDif (char* x, char* y) => (nint)x - (nint)y;

// When targeting .NET 7 or later, nint and nuint act as synonyms for the underlying
// .NET types System.IntPtr and System.UIntPtr.

// When .NET 6 or below (or.NET Standard), nint and nuint still use IntPtr and UIntPtr
// as their underlying types. However, because the legacy IntPtr and UIntPtr types lack
// support for most arithmetic operations, the compiler fills in the gaps, making the
// nint/nuint types behave as they would in .NET 7+ (including allowing checked operations).

nint native = 123;
Console.WriteLine (native * native);   // Works on .NET 6 (as well as later versions)

IntPtr ip = native;
Console.WriteLine (ip * ip);           // Requires .NET 7+

Native-sized integers - MemCopy

const int arrayLength = 16;

byte[] array1 = Enumerable.Range (0, arrayLength).Select (x => (byte)x).ToArray();
byte[] array2 = new byte[arrayLength];

fixed (byte* p1 = array1)
fixed (byte* p2 = array2)
{
  // Our simplified version of MemCopy:
  MemCopy (p1, p2, arrayLength);
  array2.Dump();

  // To see a real-world implementation of MemCopy, hit F12 on Buffer.MemoryCopy:
  Buffer.MemoryCopy (p1, p2, arrayLength, arrayLength);
  array2.Dump();
}

// Simplified implementation (assumes nicely aligned boundaries, numberOfBytes divisible by nint size)
void MemCopy (void* source, void* dest, nuint numberOfBytes)
{
  var nativeSource = (nint*)source;
  var nativeDest = (nint*)dest;

  // We want to copy memory one native word-length at a time - this is likely to be most efficient.
  // Hence we need to calculate the number of iterations = numberOfBytes / word-length.
  // nuint is the ideal type for this calculation.
  nuint iterations = numberOfBytes / (nuint) sizeof (nuint);  
  
  // Because nativeSource and nativeDest are nint*, we will enumerate the memory in increments
  // of the native word size, which is exactly our goal.
  for (nuint i = 0; i < iterations; i++)
    nativeDest[i] = nativeSource[i];
}

Function Pointers

delegate*<string, int> functionPointer = &GetLength;
int length = functionPointer ("Hello, world");
length.Dump();

((IntPtr)functionPointer).Dump ("Address in memory");

// Don't try this at home!
var pointer2 = (delegate*<string, decimal>)(IntPtr)functionPointer;
pointer2 ("Hello, unsafe world").Dump ("Some random memory!");

static int GetLength (string s) => s.Length;

[SkipLocalsInit]

void Main()
{
  for (int i = 0; i < 1000; i++)
    Foo();
}

// Uncomment the following attribute to disable automatic initialization of local variables
//[SkipLocalsInit]
unsafe void Foo()
{
  int local;
  int* ptr = &local;

  Console.Write (*ptr + " ");

  int* a = stackalloc int [10];
  for (int i = 0; i < 10; ++i) Console.Write (a [i] + " ");
  
  Console.WriteLine ();
}
C# 12 in a Nutshell
Buy from amazon.com Buy print or Kindle edition
Buy from ebooks.com Buy PDF edition
Buy from O'Reilly Read via O'Reilly subscription