Code Listings

Chapter 4: Advanced C#

Writing plug-in methods with delegates:

public delegate int Transformer (int x);

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

class Test
{
  static void Main()
  {
    int[] values = new int[] {1, 2, 3};
    Util.Transform(values, Square);      // dynamically hook in Square
    foreach (int i in values)
      Console.Write (i + "  ");           // 1   4   9
  }
 
  static int Square (int x) { return x * x; }
}

Multicast delegate example:

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
    }
  }
}
class Test
{
  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 Method Targets:

public delegate void ProgressReporter (int percentComplete);

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

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

Generic Delegate Types:

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

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

class Test
{
  static void Main()
  {
    int[] values = new int[] {1, 2, 3};
    Util.Transform(values, Square);      // dynamically hook in Square
    foreach (int i in values)
      Console.Write (i + "  ");           // 1   4   9
  }
 
  static int Square (int x) { return x * x; }
}

Delegates vs. Interfaces:

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 Test : ITransformer
{
 static void Main()
 {
   int[] values = new int[] {1, 2, 3};
   Util.TransformAll(values, new Test());
   foreach (int i in values)
     Console.WriteLine (i);
 }

 public int Transform (int x) { return x * x; }
}
class Test
{
 static void Main()
 {
   int[] values = new int[] {1, 2, 3};
   Util.TransformAll(values, new Cuber());
   foreach (int i in values)
     Console.WriteLine (i);
 }

 class Squarer : ITransformer
 {
   public int Transform (int x) { return x * x; }
 }
 class Cuber : ITransformer
 {
   public int Transform (int x) {return x * x * x; }
 }
}

Delegate Parameter Compatibility (Contravariance):

delegate void SpecificDelegate (SpecificClass s);

class SpecificClass {}

class Test
{
  static void Main()
  {
    SpecificDelegate specificDelegate = GeneralHandler;
    specificDelegate (new SpecificClass());
  }

  static void GeneralHandler(object o)
  {
    Console.WriteLine(o.GetType()); // SpecificClass
  }
}

Delegate Return Type Compatibility (Covariance):

delegate Asset DebtCollector();

class Asset {}

class House : Asset {}

class Test
{
  static void Main()
  {
     DebtCollector d = new DebtCollector (GetHomeSweetHome);
     Asset a = d();
     Console.WriteLine(a.GetType()); // House
  }
  static House GetHomeSweetHome() {return new House(); }
}

Events:

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
      if (PriceChanged != null)        // if invocation list not empty
        PriceChanged (price, value);   // fire event
      price = value;
    }  
  }
}

Standard Event Pattern:

using System;

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)
  {
    if (PriceChanged != null) PriceChanged (this, e);
  }
  
  public decimal Price
  {
    get { return price; }
    set
    {
      if (price == value) return;
      OnPriceChanged (new PriceChangedEventArgs (price, value));
      price = value;
    }  
  }
}

class Test
{
  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!");
  }
}

Using EventArgs.Empty:

public class Stock
{
  string symbol;
  decimal price;

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

  public event EventHandler PriceChanged;

  protected virtual void OnPriceChanged (EventArgs e)
  {
    if (PriceChanged != null) PriceChanged (this, e);
  }
  
  public decimal Price
  {
    get { return price; }
    set
    {
      if (price == value) return;
      price = value;
      OnPriceChanged (EventArgs.Empty);      
    }  
  }
}

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:

delegate int Transformer (int i);
  
class Test
{
  static void Main()
  {
    Transformer square = x => x * x;
    Console.WriteLine (square(3));    // 9
  }
}

Capturing Outer Variables (Closures):

delegate int NumericSequence ();
  
class Test
{
  static void Main()
  {
    int seed = 0;
    NumericSequence natural = () => seed++;
    Console.WriteLine (natural());           // 0
    Console.WriteLine (natural());           // 1
  }
}
delegate int NumericSequence ();
  
class Test
{
  static NumericSequence Natural ()
  {
    int seed = 0;         // executes once  (per call to Natural())
    return () => seed++;  // executes twice (per call to delegate instance
                          //                 returned by Natural())
  }

  static void Main()
  {
    NumericSequence natural = Natural ();
    Console.WriteLine (natural());           // 0
    Console.WriteLine (natural());           // 1
  }
}
delegate int NumericSequence ();
  
class Test
{
  static NumericSequence Natural ()
  {    
    return () => {int seed = 0; return seed++; };
  }

  static void Main()
  {
    NumericSequence natural = Natural ();
    Console.WriteLine (natural());                 // 0
    Console.WriteLine (natural());                 // 0
  }
}

Anonymous Methods:

delegate int Transformer (int i);
  
class Test
{
  static void Main()
  {
    Transformer square = delegate (int x) {return x * x;};
    Console.WriteLine (square(3));    // 9
  }
}

Try Statements and Exceptions:

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

Multiple Catch Clauses:

class Test
{
  static void Main (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!");
    }
  }
}

Finally Block:

using System;
using System.IO;

class Test
{
  static void Main ()
  {
    StreamReader reader = null;
    try
    {
      reader = File.OpenText ("file.txt");
      if (reader.EndOfStream) return;
      Console.WriteLine (reader.ReadToEnd ());
    }
    finally
    {
      if (reader != null) reader.Dispose ();
    }
  }

Using Statement:

StreamReader reader = File.OpenText ("file.txt");
try
{
  // ...
}
finally
{
  if (reader != null)
   ((IDisposable)reader).Dispose();
}

Throwing Exceptions:

class Test
{
  static void Display (string name)
  {
    if (name == null)
      throw new ArgumentNullException ("name");

    Console.WriteLine (name);
  }

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

Rethrowing:

string s;

using (WebClient wc = new WebClient())
  try { s = wc.DownloadString ("http://albahari.com/");  }
  catch (WebException ex)
  {
    if (ex.Status == WebExceptionStatus.NameResolutionFailure)
      Console.WriteLine ("Bad domain name");
    else
      throw;     // Can't handle other sorts of WebException, so rethrow
  }

Atomicity Pattern:

class Test
{
  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; 

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

Iterators:

using System;
using System.Collections.Generic;

class Test
{
  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;
    }
  }
}

Multiple yield statements:

class Test
{
  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";
  }
}

Yield break:

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

  if (breakEarly)
    yield break;

  yield return "Three";
}

Composing Sequences:

using System;
using System.Collections.Generic;

class Test
{
  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;
  }
}

Nullable types: Operator Lifting:

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 (prints empty line)

bool? semantics:

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)

Ambient Property:

public class Row
{
  // ...
  Grid parent;
  Color? backColor;

  public Color BackColor
  {
    get { return backColor ?? parent.BackColor; }
    set { backColor = backColor == parent.BackColor ? null : value; }
  }
}

Operator Overloading:

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

  public static Note operator + (Note x, int semitones)
  {
    return new Note (x.value + semitones);
  }
}

Custom Conversions:

// Convert to hertz
public static implicit operator double(Note x)
{
  return 440 * Math.Pow (2,(double) x.value / 12 );
}
  
// Convert from hertz (only accurate to nearest semitone)
public static explicit operator Note(double x)
{
  return new Note ((int) (0.5 + 12 * (Math.Log(x/440) / Math.Log(2)) ));
}

Overloading true and false:

class Test
{
  static void Main()
  {
    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)
  {
    return x.m_value == True.m_value;
  }

  public static bool operator false (SqlBoolean x)
  {
    return 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);

  private SqlBoolean (byte value) {m_value = value;}
  private byte m_value;
}

Extension Methods:

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

Extension Methods on Interfaces:

using System;
using System.Collections.Generic;

static class Test
{
  static void Main()
  {
    var strings = new string[] { "a", "b", null, "c"};
    foreach (string s in strings.StripNulls())
      Console.WriteLine(s);
  }

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

Unsafe Code:

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

The fixed statement:

class Test
{
  int x;
  static void Main()
  {
    Test test = new Test ();
    unsafe
    {
       fixed(int* p = &test.x)  // pins test
       {
         *p = 9;
       }
       System.Console.WriteLine(test.x);
    }
  }
}

Member-to-Pointer operator

struct Test
{
  int x;
  unsafe static void Main()
  {
    Test test = new Test();
    Test* p = &test;
    p->x = 9;
    System.Console.WriteLine(test.x);
  }
}

stackalloc:

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

Fixed-size buffers:

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

unsafe class UnsafeClass
{
  private 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];
  }
}

class Test
{
  

void*:

class Test
{
  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;
  }
}

Preprocessor directives:

#define DEBUG
class MyClass
{
  int x;
  void Foo()
  {
    # if DEBUG
    Console.WriteLine("Testing: x = {0}", x);
    # endif
  }
  ...
}

Conditional attributes:

// file1.cs
#define DEBUG
using System;
using System.Diagnostics;

[Conditional("DEBUG")]
public class TestAttribute : Attribute {}

// file2.cs
#define DEBUG
[Test]
class Foo
{
  [Test]
  private string s;
}

#pragma warning:

public class Foo
{
  static void Main() { }

  #pragma warning disable 414
  static string Message = "Hello";
  #pragma warning restore 414
}

XML Documentation:

using System;

class Test
{
  /// <summary>
  /// The Foo method is called from
  ///   <see cref="Main">Main</see>
  /// </summary>
  /// <mytag>user defined tag info</mytag>
  /// <param name="s">Description for s</param>
  static void Foo(string s) { Console.WriteLine(s); }

  static void Main() { Foo("42"); }
}
namespace NS
{
  /// T:NS.MyClass
  class MyClass
  {
    /// F:NS.MyClass.aField
    string aField;

    /// P:NS.MyClass.aProperty
    short aProperty {get {...} set {...}}

    /// T:NS.MyClass.NestedType
    class NestedType {...};

    /// M:NS.MyClass.X()
    void X() {...}

    /// M:NS.MyClass.Y(System.Int32,System.Double@,System.Decimal@)
    void Y(int p1, ref double p2, out decimal p3) {...}

    /// M:NS.MyClass.Z(System.Char[ ],System.Single[0:,0:])
    void Z(char[ ] 1, float[,] p2) {...}

    /// M:NS.MyClass.op_Addition(NS.MyClass,NS.MyClass)
    public static MyClass operator+(MyClass c1, MyClass c2) {...}

    /// M:NS.MyClass.op_Implicit(NS.MyClass)~System.Int32
    public static implicit operator int(MyClass c) {...}

    /// M:NS.MyClass.#ctor
    MyClass() {...}

    /// M:NS.MyClass.Finalize
    ~MyClass() {...}

    /// M:NS.MyClass.#cctor
    static MyClass() {...}
  }
}

 

© 2007, O'Reilly Media, Inc. All rights reserved

C# 3.0 in a Nutshell
Buy from amazon.com Available now