日期:2020-06-06  浏览次数:3228 次

Chapter 4 - Advanced C#

Delegates

Delegates

// A delegate type declaration is like an abstract method declaration, prefixed with the delegate keyword:
delegate int Transformer (int x);

static void Main()
{
  // 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
}

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

Delegates - longhand

delegate int Transformer (int x);

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

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

Delegates - Writing Plug-in Methods

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

delegate int Transformer (int x);

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

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

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

Multicast Delegates

// All delegate instances have multicast capability:

delegate void SomeDelegate();

static void Main()
{
  SomeDelegate d = SomeMethod1;
  d += SomeMethod2;

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

static void SomeMethod1 () { "SomeMethod1".Dump(); }
static void SomeMethod2 () { "SomeMethod2".Dump(); }

Multicast Delegates - ProgressReporter

public delegate void ProgressReporter (int percentComplete);

public 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
    }
  }
}

static void Main()
{
  ProgressReporter p = WriteProgressToConsole;
  p += WriteProgressToFile;
  Util.HardWork (p);
}

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

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

Instance vs Static Methods

// 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:

public delegate void ProgressReporter (int percentComplete);

static void Main()
{
  X x = new X();
  ProgressReporter p = x.InstanceProgress;
  p(99);                                 // 99
  Console.WriteLine (p.Target == x);     // True
  Console.WriteLine (p.Method);          // Void InstanceProgress(Int32)
}

class X
{
  public void InstanceProgress (int percentComplete) => Console.WriteLine (percentComplete);
}

Generic Delegate Types

// A delegate type may contain generic type parameters:
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]);
  }
}

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

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

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:

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]);
  }
}

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

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

Delegates vs Interfaces

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

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;
}

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

Delegates vs Interfaces - Clumsiness

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

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;
}

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

Delegate Type Incompatibility

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

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

static void Main()
{
  D1 d1 = Method1;
  D2 d2 = d1;            // Compile-time error
}

static void Method1() { }

Delegate Type Incompatibility - Workaround

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

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

static void Main()
{
  D1 d1 = Method1;
  D2 d2 = new D2 (d1);  // Legal
}

static void Method1() { }

Delegate Equality

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

delegate void D();

static void Main()
{
  D d1 = Method1;
  D d2 = Method1;
  Console.WriteLine (d1 == d2);         // True
}

static void Method1() { }

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:

delegate object ObjectRetriever();

static void Main()
{
  ObjectRetriever o = new ObjectRetriever (RetriveString);
  object result = o();
  Console.WriteLine (result);      // hello
}

static string RetriveString() => "hello";

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.

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.
    }
  }
}

static void Main()
{
  var stock = new Stock ("MSFT");
  stock.PriceChanged += ReportPriceChange;
  stock.Price = 123;
  stock.Price = 456;
}

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

Standard Event Pattern

// The .NET Framework defines a standard pattern for writing events. The pattern provides
// consistency across both Framework and user code.

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));
    }
  }
}

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

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

Standard Event Pattern - Simple EventHandler

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

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);
    }
  }
}

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

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

Event Accessors

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

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);
    }
  }
}

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

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

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; }
  }
}

static void Main() { }
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

delegate int Transformer (int i);

static void Main()
{
  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));
  
  // 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));
}

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
}

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: 

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

static void Main()
{
  int y = Calc (0);
  Console.WriteLine (y);
}

DivideByZeroException handled

// We can catch the DivideByZeroException as follows:

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

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

The catch Clause

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

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

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

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

Exception Filters

try
{
  new WebClient().DownloadString ("http://thisDoesNotExist");
}
catch (WebException ex) when (ex.Status == WebExceptionStatus.Timeout)
{
  "Timeout!".Dump();
}
catch (WebException ex) when (ex.Status == WebExceptionStatus.NameResolutionFailure)
{
  "Name resolution failure!".Dump();
}
catch (WebException ex) 
{
  $"Some other failure: {ex.Status}".Dump();
}

The finally Block

// finally blocks are typically used for cleanup code:

static 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();
  }
}

static void Main()
{
  File.WriteAllText ("file.txt", "test");
  ReadFile ();
}

The using Statement

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

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

static void Main()
{
  File.WriteAllText ("file.txt", "test");
  ReadFile ();
}

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:
  
static void Display (string name)
{
  if (name == null)
    throw new ArgumentNullException (nameof (name));

  Console.WriteLine (name);
}

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

throw Expressions

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

public 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);

void Main()
{
  ProperCase ("test").Dump();
  ProperCase (null).Dump();     // throws an ArgumentException
}

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;

using (WebClient wc = new WebClient())
  try { s = wc.DownloadString ("http://www.albahari.com/nutshell/");  }
  catch (WebException ex)
  {
    if (ex.Status == WebExceptionStatus.NameResolutionFailure)
      Console.WriteLine ("Bad domain name");
    else
      throw;     // Can’t handle other sorts of WebException, 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);
}
catch (FormatException ex)
{
    throw new XmlException ("Invalid DateTime", ex);
}

The TryXXX Pattern

static void Main()
{
  bool result;
  TryToBoolean ("Bad", out result).Dump ("Successful");
  result = ToBoolean ("Bad");    // throws Exception
}

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

public static 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

static void Main()
{
  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 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:

static void Main()
{
  foreach (int fib in Fibs(6))
    Console.Write (fib + "  ");
}

static 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:

static void Main()
{
  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:

static void Main()
{
  foreach (string s in Foo())
    Console.WriteLine(s);         // Prints "One","Two","Three"
}

static 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:

static void Main()
{
  foreach (string s in Foo())
    Console.WriteLine(s);
}

static 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:

static void Main()
{
  foreach (string s in Foo()) s.Dump();
  
  Console.WriteLine();
    
  foreach (string s in Foo())
  {
    ("First element is " + s).Dump();
    break;
  }
}

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

Composing Iterators

// Iterators are highly composable:

static void Main()
{
  foreach (int fib in EvenNumbersOnly (Fibs(6)))
    Console.WriteLine (fib);
}

static 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;
  }
}

static 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; }
}

void Main() { }

Nullable Reference Types

#nullable enable
void Main()
{
  // Number and Photo must be assigned non-null values because we're
  // in an enabled nullable annotation context.
  var myLicense = new DriversLicense()
  {
    Number = "C12345",
    Photo = new Bitmap (512, 512) // A real system might load from a database or camera attached to the computer    
  };

  // Both of these can safely be derenced:
  Console.WriteLine (myLicense.Number.Length);   
  Console.WriteLine (myLicense.Photo.Height);
  // This violates CS8602 and will generate either a warning or error
  // depending on whether nullable annotation context related warnings
  // are configured to produce errors instead:
  Console.WriteLine (myLicense.Restrictions.Length);
  // By using the null forgiveness operator (!), we tell the compiler to
  // ignore its static flow analysis. We state we know better (and if 
  // we're wrong, a NullReferenceException results).
  Console.WriteLine (myLicense.Restrictions!.Length);
  myLicense.Restrictions = "None";
  // No warning/error since the compiler can prove a non-null assignment
  Console.WriteLine (myLicense.Restrictions.Length);
}

public class DriversLicense
{
  public string Number { get; set; }
  public Image Photo { get; set;}
  public string? Restrictions { get; set; }
}

Null Forgiveness Operator

#nullable enable

string? foo = SomeMethodReturningNonnullString(); 
Console.WriteLine(foo!.Length);

string? SomeMethodReturningNonnullString()
{
  return "Bar";
}
Nullable Reference Types

Nullable Reference Types

#nullable enable    // Enable nullable reference types

void Main()
{
  string s1 = null;   // Generates a compiler warning!
  string? s2 = null;  // OK: s2 is nullable reference type
}

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

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);
}

void Main() 
{
  
}

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:

// (Note that these examples will not work in older versions of LINQPad)

static void Main()
{
  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:

static void Main()
{
  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:

static void Main()
{
  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 StringHelper
{
  public static void Foo (this UserQuery.Test 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:

static void Main()
{
  "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:

static void Main()
{
  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

void Main()
{
  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
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

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

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

Naming tuple elements

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

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

Naming tuple elements - types

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

static void Main()
{
  var person = GetPerson();
  Console.WriteLine (person.Name);    // Bob
  Console.WriteLine (person.Age);     // 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

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

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

static void Main()
{
  var (name, age, sex) = GetBob();
  Console.WriteLine (name);        // Bob
  Console.WriteLine (age);         // 23
  Console.WriteLine (sex);         // 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!");
Attributes (see also CH19)

Attaching Attributes

void Main()
{
  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()
{
}

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 {}

void Main()
{
}

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

void Main()
{
  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 { return customerName; }
    set
    {
      if (value == customerName) return;
      customerName = value;
      RaisePropertyChanged();
      // The compiler converts the above line to:
      // RaisePropertyChanged ("CustomerName");
    }
  }
}
Dynamic Binding (see also CH20)

Custom Binding

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

static void Main()
{
  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:

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

static void Main()
{
  int x = 3, y = 4;
  Console.WriteLine (Mean (x, y));
}

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

interface IFoo   { void Test();        }
class Foo : IFoo { void IFoo.Test() {} }

static void Main()
{
  IFoo f = new Foo();
  dynamic d = f;
  d.Test();             // Exception thrown
}
Operator Overloading (see also CH6)

Operator Functions

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

public struct Note
{
  int value;
  public int SemitonesFromA => value;
  
  public Note (int semitonesFromA) { value = semitonesFromA; }

  public static Note operator + (Note x, int semitones)
  {
    return new Note (x.value + semitones);
  }
  
  // Or more tersely:
  // 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
}

static void Main()
{
  Note B = new Note (2);
  Note CSharp = B + 2;  
  CSharp.SemitonesFromA.Dump();
  
  CSharp += 2;  
  CSharp.SemitonesFromA.Dump();
}

Custom Implicit and Explicit Conversions

// Implicit and explicit conversions are overloadable operators:

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) ) ));
}

static void Main()
{
  Note n = (Note)554.37;  // explicit conversion
  double x = n;           // implicit conversion
  x.Dump();  
}

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:

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 void Main()
{
  SqlBoolean a = SqlBoolean.Null;
  if (a)
    Console.WriteLine ("True");
  else if (!a)
    Console.WriteLine ("False");
  else
    Console.WriteLine ("Null");  
}
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:

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;
  }
}

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

Pinning variables with fixed

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

class Test
{
  public int X;
}

static void Main()
{
  Test test = new Test();
  unsafe
  {
     fixed (int* p = &test.X)   // Pins test
     {
     *p = 9;
     }
     Console.WriteLine (test.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:

struct Test
{
  public int X;
}

unsafe static void Main()
{
  Test test = new Test();
  Test* p = &test;
  p->X = 9;
  Console.WriteLine (test.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:

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];
  }
}

static void Main() { new UnsafeClass ("Christian Troy"); }

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:

unsafe static void Main()
{
  short[] a = {1,1,2,3,5,8,13,21,34,55};
  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 static void Zap (void* memory, int byteCount)
{
  byte* b = (byte*) memory;
    for (int i = 0; i < byteCount; i++)
      *b++ = 0;
}
Patterns

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

void Main()
{
  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

void Main()
{
  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: string { Length: 4 }, Port: 80 } => true,
  _ => false
};

Property pattern with switch - with when clause

void Main()
{
  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 } when uri.Host.Length < 1000 => true,
  _ => false
};

Property pattern with type pattern

void Main()
{
  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

void Main()
{
  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

void Main()
{
  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
};

Tuple patterns

void Main()
{
  AverageCelsiusTemperature (Season.Spring, true).Dump();
}

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

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")
  };

Positional patterns

void Main()
{
  Print (new Point (0, 0)).Dump();
}

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

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

var pattern

void Main()
{
  Test (3, 3).Dump();
  Test (4, 4).Dump();
  Test (10, 10).Dump();
}

bool Test (int x, int y) =>
   x * y is var product && product > 10 && product < 100;

Constant pattern

void Main()
{
  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");
}
Switch Expressions

Positional Pattern

void Main()
{
  var jax = new GameCharacter()
  {
    Role = GameRole.Figher,
    Level = 3
  };
  
  Console.WriteLine(GetTitle(jax));
}

enum GameRole { Figher, Rogue, Mage };
class GameCharacter
{
  public GameRole Role;
  public int Level;
  public void Deconstruct(out GameRole role, out int level) => 
    (role, level) = (Role, Level);
}

string GetTitle(GameCharacter ch) => ch switch
{
  var (role, level) when role == GameRole.Figher && level > 9 => "Knight",
  var (role, level) when role == GameRole.Figher && level > 2 => "Warrior",
  var (role, level) when role == GameRole.Figher => "Squire",
  var (role, level) when role == GameRole.Rogue && level > 9 => "Master",
  var (role, level) when role == GameRole.Rogue && level > 2 => "Footpad",
  var (role, level) when role == GameRole.Rogue => "Recuit",
  var (role, level) when role == GameRole.Mage && level > 9 => "Archmage",
  var (role, level) when role == GameRole.Mage && level > 2 => "Caster",
  var (role, level) when role == GameRole.Mage => "Apprentice",
  _ => throw new Exception("Unexpected values.")
};

Property Patterns

void Main()
{
  var jax = new GameCharacter()
  {
    Role = GameRole.Figher,
    Level = 3,
    Rested = true
  };

  RollDamageIncludingWellRested (jax).Dump();
  RollDamageIncludingWellRested (jax).Dump();
  RollDamageIncludingWellRested (jax).Dump();
}

Random rnd = new Random();

int RollDamage(GameCharacter ch) => 
  ch switch
  {
    { Role: GameRole.Figher } => ch.Level * rnd.Next (10),
    { Role: GameRole.Rogue } => ch.Level * rnd.Next (6),
    { Role: GameRole.Mage } => ch.Level * rnd.Next (4),
    _ => throw new Exception("Unexpected condition.")
  };
  
int RollDamageIncludingWellRested (GameCharacter ch) =>
  ch switch
  {
    { Role: GameRole.Figher, Rested: true } => ch.Level * rnd.Next (12),
    { Role: GameRole.Figher, Rested: false } => ch.Level * rnd.Next (10),
    { Role: GameRole.Rogue, Rested: true } => ch.Level * rnd.Next (7),
    { Role: GameRole.Rogue, Rested: false } => ch.Level * rnd.Next (6),
    // Well-rested doesn't affect mages
    { Role: GameRole.Mage } => ch.Level * rnd.Next (4), 
    _ => throw new Exception ("Unexpected condition.")
  };

enum GameRole { Figher, Rogue, Mage };
class GameCharacter
{
  public GameRole Role;
  public int Level;
  public bool Rested;
}

Switch Expressions

int cardNumber = 12; string suite = "spades";

string cardRank = cardNumber switch
{
  13 => "King",
  12 => "Queen",
  11 => "Jack",
  _ => "Pip card"   // equivalent to 'default'
};

cardRank.Dump();

string cardName = (cardNumber, suite) switch
{
  (13, "spades") => "King of spades",
  (13, "clubs") => "King of clubs",
  (13, "hearts") => "King of hearts",
  (13, "diamonds") => "King of diamonds",
  (12, "spades") => "Queen of spades",
  (12, "clubs") => "Queen of clubs",
  (12, "hearts") => "Queen of hearts",
  (12, "diamonds") => "Queen of diamonds",
  _ => "Something else" // The discard is also used for a tuple
};

cardName.Dump();

Tuple Patterns

void Main()
{
  var temp = AverageCelsiusTemperature(Season.Spring, daytime: true);
  Console.WriteLine(temp); // 20
}

enum Season { Spring, Summer, Fall, Winter };
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")
};