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

Chapter 3 - Creating Types in C#

Classes

Fields

// A field is a variable that is a member of a class or struct.

class Octopus
{
  string name;
  public int Age = 10;
  static readonly int legs = 8, eyes = 1;
}

static void Main()
{
  var o = new Octopus();
  o.Age.Dump();  
}

Fields - readonly

// Readonly fields let you create *immutable* classes.

class Octopus
{
  public readonly string Name;
  public readonly int Legs = 8;
  
  public Octopus (string name)
  {
    Name = name;
  }
}

static void Main()
{
  var o = new Octopus ("Jack");
  o.Name.Dump();  
  o.Legs = 20;  // Compile-time error
}

Constants - scoped to class

// Constants are factored out at compile-time and baked into the calling site.

public class Test
{
  public const string Message = "Hello World";
}

static void Main()
{    
  Test.Message.Dump();
}

Constants - scoped to method

// Here, the calculation is performed at compile-time:

const double twoPI  = 2 * System.Math.PI;
twoPI.Dump();

Methods - Expression-bodied

// Foo1 and Foo2 are equivalent:

int Foo1 (int x) { return x * 2; }
int Foo2 (int x) => x * 2;

void Main()
{
  Foo1 (10).Dump();
  Foo2 (10).Dump();
}

Methods - Overloading

// We can overload Foo as follows:

void Foo (int x)      { "int".Dump(); }
void Foo (double x)      { "double".Dump(); }
void Foo (int x, float y)  { "int, float".Dump(); }
void Foo (float x, int y)  { "float, int".Dump(); }

void Main()
{
  Foo (123);      // int
  Foo (123.0);    // double
  Foo (123, 123F);  // int, float
  Foo (123F, 123);  // float, int
}

Methods - Illegal Overloading

// The following overloads are prohibited:

void  Foo (int x);
float Foo (int x);           // Compile-time error

void  Goo (int[] x);
void  Goo (params int[] x);  // Compile-time error

void Hoo (int x);
void Hoo (ref int x);      // OK so far
void Hoo (out int x);      // Compile-time error

Local methods

void Main()
{
  WriteCubes();
}

void WriteCubes()
{
  Console.WriteLine (Cube (3));
  Console.WriteLine (Cube (4));
  Console.WriteLine (Cube (5));

  int Cube (int value) => value * value * value;
}

Constructors - Overloading

// You can also overload constructors.
// Note the use of the "this" keyword to call another constructor:

public class Wine
{
  public decimal Price;
  public int Year;
  public Wine (decimal price) { Price = price; }
  public Wine (decimal price, int year) : this (price) { Year = year; }
}

static void Main()
{
  new Wine (78).Dump();
  new Wine (78, 2001).Dump();
}

Constructors - Nonpublic

// A common reason to have a nonpublic constructor is to control instance creation via a
// static method call:

public class Class1
{
  Class1() { }    // Private constructor
  
  public static Class1 Create()
  {
    // Perform custom logic here to create & configure an instance of Class1
    /* ... */
    return new Class1();
  }
}

static void Main()
{
  Class1 c1 = Class1.Create();  // OK
  Class1 c2 = new Class1();    // Error: Will not compile
}

Deconstructors

class Rectangle
{
  public readonly float Width, Height;

  public Rectangle (float width, float height)
  {
    Width = width;
    Height = height;
  }

  public void Deconstruct (out float width, out float height)
  {
    width = Width;
    height = Height;
  }
}

static void Main()
{
  // To call the deconstructor, we use the following special syntax:
  var rect = new Rectangle (3, 4);
  (float width, float height) = rect;          // Deconstruction
  Console.WriteLine (width + " " + height);    // 3 4
  
  // We can also use implicit typing:  
  var (x, y) = rect;          // Deconstruction
  Console.WriteLine (x + " " + y);

  // If the variables already exist, we can do a *deconstructing assignment*:
  (x, y) = rect; 
  Console.WriteLine (x + " " + y);
}

Object Initializers

// Fields or properties can be initialized in a single statement directly after construction:

static void Main()
{
  // Object initialization syntax. Note that we can still specify constructor arguments:
  
  Bunny b1 = new Bunny { Name="Bo", LikesCarrots=true, LikesHumans=false };
  Bunny b2 = new Bunny ("Bo")     { LikesCarrots=true, LikesHumans=false };
  
  b1.Dump(); b2.Dump();
}

public class Bunny
{
  public string Name;
  public bool LikesCarrots;
  public bool LikesHumans;
  
  public Bunny () {}
  public Bunny (string n) { Name = n; }
}

Object Initializer Alternative - Optional Parameters

// Instead of using object initializers, we could make Bunny’s constructor accept optional parameters.
// This has both pros and cons (see book):

public class Bunny
{
  public string Name;
  public bool LikesCarrots;
  public bool LikesHumans;

  public Bunny (
    string name,
    bool likesCarrots = false,
    bool likesHumans = false)
  {
    Name = name;
    LikesCarrots = likesCarrots;
    LikesHumans = likesHumans; 
  }
}

static void Main()
{    
  Bunny b = new Bunny (
    name: "Bo",
    likesCarrots: true);
  
  b.Dump();
}

The this Reference

// The this reference refers to the instance itself:

public class Panda
{
  public Panda Mate;

  public void Marry (Panda partner)
  {
    Mate = partner;
    partner.Mate = this;
  }
}

static void Main()
{    
  new Panda().Marry (new Panda());
}

Properties

// Properties look like fields from the outside but internally, they contain logic, like methods:

public class Stock
{
  decimal currentPrice;           // The private "backing" field
  
  public decimal CurrentPrice     // The public property
  {
    get { return currentPrice; } set { currentPrice = value; }
  }
}

static void Main()
{    
  var stock = new Stock();
  stock.CurrentPrice = 123.45M;
  stock.CurrentPrice.Dump();
  
  var stock2 = new Stock { CurrentPrice = 83.12M };
  stock2.CurrentPrice.Dump();
}

Properties - calculated & read-only

// The Worth Property is a read-only calculated property.

public class Stock
{
  decimal currentPrice;           // The private "backing" field
  public decimal CurrentPrice     // The public property
  {
    get { return currentPrice; } set { currentPrice = value; }
  }

  decimal sharesOwned;           // The private "backing" field
  public decimal SharesOwned     // The public property
  {
    get { return sharesOwned; } set { sharesOwned = value; }
  }

  public decimal Worth
  {
    get { return currentPrice * sharesOwned; }
  }
}

static void Main()
{    
  var stock = new Stock { CurrentPrice = 50, SharesOwned = 100 };
  stock.Worth.Dump();
}

Properties - expression-bodied

// The Worth Property is now an expression-bodied property.

public class Stock
{
  decimal currentPrice;           // The private "backing" field
  public decimal CurrentPrice     // The public property
  {
    get { return currentPrice; } set { currentPrice = value; }
  }

  decimal sharesOwned;           // The private "backing" field
  public decimal SharesOwned     // The public property
  {
    get { return sharesOwned; } set { sharesOwned = value; }
  }

  public decimal Worth => currentPrice * sharesOwned;    // Expression-bodied property

  // From C# 7, we can take this further, and write both the get and set accessors in
  // expression-bodied syntax:
  public decimal Worth2
  {
    get => currentPrice * sharesOwned;
    set => sharesOwned = value / currentPrice;
  }

}

static void Main()
{    
  var stock = new Stock { CurrentPrice = 50, SharesOwned = 100 };
  stock.Worth.Dump();
}

Automatic Properties

// Here's the preceding example rewritten with two automatic properties:

public class Stock
{
  public decimal CurrentPrice { get; set; }  // Automatic property
  public decimal SharesOwned { get; set; }    // Automatic property

  public decimal Worth
  {
    get { return CurrentPrice * SharesOwned; }
  }
}

static void Main()
{    
  var stock = new Stock { CurrentPrice = 50, SharesOwned = 100 };
  stock.Worth.Dump();
}

Property Initializers

public decimal CurrentPrice { get; set; } = 123;
public int Maximum { get; } = 999;

void Main()
{
  CurrentPrice.Dump();
  Maximum.Dump();
}

Properties - get & set accessibility

// In this example, the set accessors are private while the get accessors are public:

public class Foo
{
  private decimal x;
  public decimal X
  {
    get         { return x;  }
    private set { x = Math.Round (value, 2); }
  }
  
  public int Auto { get; private set; }  // Automatic property
}


static void Main()
{    
  new Foo { X = 5 };    // Will not compile - X has a private set accessor.
}

Indexers

// You can implement custom indexers with the this keyword:

class Sentence
{
  string[] words = "The quick brown fox".Split();
  
  public string this [int wordNum]      // indexer
  { 
    get { return words [wordNum];  }
    set { words [wordNum] = value; }
  }

  // In C# 8, we can also define indexers that use the Index & Range types:
  public string this [Index index] => words [index];
  public string[] this [Range range] => words [range];

}

static void Main()
{    
  Sentence s = new Sentence();
  Console.WriteLine (s[3]);       // fox
  s[3] = "kangaroo";
  Console.WriteLine (s[3]);       // kangaroo

  // Test the indexers that use C#'s Indices and Ranges:

  Console.WriteLine (s [^1]);                // fox  
  string[] firstTwoWords = s [..2].Dump();   // (The, quick)
}

Static Constructors

// A static constructor executes once per type, rather than once per instance:

class Test
{
  static Test()
  {
    Console.WriteLine ("Type Initialized");
  }
}

static void Main()
{
  // Type is initialized only once
  new Test();
  new Test();
  new Test();
}

Static Constructors & Field Initialization Order

// Static field initializers run just before the static constructor is called:

class Foo
{
  public static int X = Y;    // 0
  public static int Y = 3;    // 3
}

static void Main()
{
  Foo.X.Dump ("X");  // 0
  Foo.Y.Dump ("Y");  // 3
}

Static Constructors & Field Initialization Order (Constructor Call)

// Another way to go awry:

class Foo
{
  public static Foo Instance = new Foo();
  public static int X = 3;
  
  Foo() { Console.WriteLine (X); }   // 0
}

static void Main()
{
  Console.WriteLine (Foo.X);   // 3
}

Partial Types

// Partial types allow a type definition to be split—typically across multiple files:

partial class PaymentForm { public int X; }
partial class PaymentForm { public int Y; }

static void Main()
{
  new PaymentForm { X = 3, Y = 4 }.Dump();
}

Partial Methods

// A partial type may contain partial methods. These let an auto-generated partial type
// provide customizable hooks for manual authoring.

partial class PaymentForm    // In auto-generated file
{  
  public PaymentForm (decimal amount)
  {
    ValidatePayment (amount);
    // ...
  }

  partial void ValidatePayment (decimal amount);
}

partial class PaymentForm    // In hand-authored file
{
  partial void ValidatePayment (decimal amount)
  {
    if (amount < 100) 
      throw new ArgumentOutOfRangeException ("amount", "Amount too low!");
  }
}

static void Main()
{
  var paymentForm = new PaymentForm (50);
}

The nameof operator

int count = 123;
nameof (count).Dump ("count");

nameof (StringBuilder.Length).Dump ("Length property on StringBuilder");

(nameof (StringBuilder) + "." + nameof (StringBuilder.Length)).Dump ("StringBuilder.Length");
Inheritance

Inheritance

// A class can inherit from another class to extend or customize the original class.

static void Main()
{
  Stock msft = new Stock { Name="MSFT", SharesOwned=1000 };

  Console.WriteLine (msft.Name);         // MSFT
  Console.WriteLine (msft.SharesOwned);  // 1000
  
  House mansion = new House { Name="Mansion", Mortgage=250000 };
  
  Console.WriteLine (mansion.Name);      // Mansion
  Console.WriteLine (mansion.Mortgage);  // 250000
}

public class Asset
{
  public string Name;
}

public class Stock : Asset   // inherits from Asset
{
  public long SharesOwned;
}

public class House : Asset   // inherits from Asset
{
  public decimal Mortgage;
}

Polymorphism

// A variable of type x can refer to an object that subclasses x.

static void Main()
{
  // The Display method below accepts an Asset. This means means we can pass it any subtype:
  Display (new Stock { Name="MSFT", SharesOwned=1000 });
  Display (new House { Name="Mansion", Mortgage=100000 });
}

public static void Display (Asset asset)
{
  Console.WriteLine (asset.Name);
}

public class Asset
{
  public string Name;
}

public class Stock : Asset   // inherits from Asset
{
  public long SharesOwned;
}

public class House : Asset   // inherits from Asset
{
  public decimal Mortgage;
}

Reference Conversions - Upcasting

static void Main()
{
  // An upcast creates a base class reference from a subclass reference:
  
  Stock msft = new Stock();
  Asset a = msft;               // Upcast
  
  // After the upcast, the two variables still references the same Stock object:
  
  Console.WriteLine (a == msft);  // True
}

public class Asset
{
  public string Name;
}

public class Stock : Asset   // inherits from Asset
{
  public long SharesOwned;
}

public class House : Asset   // inherits from Asset
{
  public decimal Mortgage;
}

Reference Conversions - Downcasting

static void Main()
{
  // A downcast operation creates a subclass reference from a base class reference.
  
  Stock msft = new Stock();
  Asset a = msft;                      // Upcast
  Stock s = (Stock)a;                  // Downcast
  Console.WriteLine (s.SharesOwned);   // <No error>
  Console.WriteLine (s == a);          // True
  Console.WriteLine (s == msft);       // True
  
  // A downcast requires an explicit cast because it can potentially fail at runtime:
  
  House h = new House();
  Asset a2 = h;               // Upcast always succeeds
  Stock s2 = (Stock)a2;       // ERROR: Downcast fails: a is not a Stock
}

public class Asset
{
  public string Name;
}

public class Stock : Asset   // inherits from Asset
{
  public long SharesOwned;
}

public class House : Asset   // inherits from Asset
{
  public decimal Mortgage;
}

The is operator

// The is operator tests whether a reference conversion (or unboxing conversion) would succeed:

static void Main()
{
  Asset a = new Asset();
  
  if (a is Stock)
      Console.WriteLine (((Stock)a).SharesOwned);
}

public class Asset
{
  public string Name;
}

public class Stock : Asset   // inherits from Asset
{
  public long SharesOwned;
}

public class House : Asset   // inherits from Asset
{
  public decimal Mortgage;
}

The is operator and pattern variables

// The is operator tests whether a reference conversion (or unboxing conversion) would succeed:

static void Main()
{
  Asset a = new Stock { SharesOwned = 3 };

  if (a is Stock s)
    Console.WriteLine (s.SharesOwned);
    
  // We can take this further:

  if (a is Stock s2 && s2.SharesOwned > 100000)
    Console.WriteLine ("Wealthy");
  else
    s2 = new Stock();   // s is in scope

  Console.WriteLine (s2.SharesOwned);  // Still in scope
}

public class Asset
{
  public string Name;
}

public class Stock : Asset   // inherits from Asset
{
  public long SharesOwned;
}

public class House : Asset   // inherits from Asset
{
  public decimal Mortgage;
}

The as operator

// The as operator performs a downcast that evaluates to null (rather than throwing an exception)
// if the downcast fails.

static void Main()
{
  Asset a = new Asset();
  Stock s = a as Stock;       // s is null; no exception thrown

  if (s != null) Console.WriteLine (s.SharesOwned);  // Nothing written
}

public class Asset
{
  public string Name;
}

public class Stock : Asset   // inherits from Asset
{
  public long SharesOwned;
}

public class House : Asset   // inherits from Asset
{
  public decimal Mortgage;
}

Virtual Function Members

// A function marked as virtual can be overridden by subclasses wanting to provide a
// specialized implementation:

static void Main()
{
  House mansion = new House { Name="McMansion", Mortgage=250000 };
  Console.WriteLine (mansion.Liability);      // 250000
}

public class Asset
{
  public string Name;
  public virtual decimal Liability => 0;    // Virtual
}

public class House : Asset
{
  public decimal Mortgage;
  public override decimal Liability => Mortgage;   // Overridden
}

public class Stock : Asset
{
  public long SharesOwned;
  // We won't override Liability here, because the default implementation will do.
}

Abstract Classes & Members

// A class declared as abstract can never be instantiated. Instead, only its concrete subclasses
// can be instantiated. Abstract classes are able to define abstract members.

static void Main()
{
  new Stock { SharesOwned = 200, CurrentPrice = 123.45M }.NetValue.Dump();
}

public abstract class Asset    // Note abstract keyword
{
    public abstract decimal NetValue { get; }  // Note empty implementation
}

public class Stock : Asset
{
  public long SharesOwned;
  public decimal CurrentPrice;

  // Override like a virtual method.
  public override decimal NetValue => CurrentPrice * SharesOwned; 
}

Hiding Inherited Members with new

// A base class and a subclass may define identical members. This usually happens by accident:

static void Main()
{
  B b = new B();
  b.Counter.Dump();    // 2
  
  // Notice the non-virtual behavior in the code below:
  
  A referenceConvertedB = b;
  referenceConvertedB.Counter.Dump();    // 1  
}

public class A      { public int Counter = 1; }
public class B : A  { public int Counter = 2; }

// Occasionally, you want to hide a member deliberately, in which case you can apply the new  
// modifier to the member in the subclass, to avoid the compiler warning. The behavior is the same:

public class X      { public     int Counter = 1; }
public class Y : X  { public new int Counter = 2; }

new vs virtual

static void Main()
{
  Overrider over = new Overrider();
  BaseClass b1 = over;
  over.Foo();                         // Overrider.Foo
  b1.Foo();                           // Overrider.Foo
  
  Hider h = new Hider();
  BaseClass b2 = h;
  h.Foo();                           // Hider.Foo
  b2.Foo();                          // BaseClass.Foo
}

public class BaseClass
{
  public virtual void Foo()  { Console.WriteLine ("BaseClass.Foo"); }
}

public class Overrider : BaseClass
{
   public override void Foo() { Console.WriteLine ("Overrider.Foo"); }
}

public class Hider : BaseClass
{
  public new void Foo()      { Console.WriteLine ("Hider.Foo"); }
}

Sealing Functions & Classes

// An overridden function member may seal its implementation with the sealed keyword to prevent it
// from being overridden by further subclasses:

static void Main()
{
  House mansion = new House { Name="McMansion", Mortgage=250000 };
  Console.WriteLine (mansion.Liability);      // 250000
}

public class Asset
{
  public string Name;
  public virtual decimal Liability => 0;    // Virtual
}

public class House : Asset
{
  public decimal Mortgage;
  public sealed override decimal Liability => Mortgage;   // Overridden + sealed
}

// You can also seal the class itself, implicitly sealing all the virtual functions:

public sealed class Stock : Asset { /* ... */ }

Constructors & Inheritance

// A subclass must declare its own constructors. In doing so, it can call any of the
// base class’s constructors with the base keyword:

static void Main()
{
  new Subclass (123);
}

public class Baseclass
{
  public int X;
  public Baseclass () { }
  public Baseclass (int x) { this.X = x; }
}

public class Subclass : Baseclass
{
  public Subclass (int x) : base (x) { }
}

Implicit Calling of the Parameterless Base Class Constructor

// If a constructor in a subclass omits the base keyword, the base type’s parameterless
// constructor is implicitly called:

static void Main()
{
  new Subclass();
}

public class BaseClass
{
  public int X;
  public BaseClass() { X = 1; }
}

public class Subclass : BaseClass
{
  public Subclass() { Console.WriteLine (X); }  // 1
}

Overloading and Resolution

// When calling an overload method, the method with the most specific 
// parameter type match has precedence, based on the *compile-time* variable type:

static void Main()
{
  Foo (new House());    // Calls Foo (House)
  
  Asset a = new House();
  Foo (a);        // Calls Foo (Asset)
}

static void Foo (Asset a) { "Foo Asset".Dump(); }
static void Foo (House h) { "Foo House".Dump(); }

public class Asset
{
  public string Name;
}

public class Stock : Asset   // inherits from Asset
{
  public long SharesOwned;
}

public class House : Asset   // inherits from Asset
{
  public decimal Mortgage;
}
The object Type

The object Type

// object (System.Object) is the ultimate base class for all types. Any type can be
// implicitly converted to object; we can leverage this to write a general-purpose Stack:

public class Stack
{
  int position;
  object[] data = new object[10];
  public void Push (object obj)   { data[position++] = obj;  }
  public object Pop()             { return data[--position]; }
}

// Because Stack works with the object type, we can Push and Pop instances of any type
// to and from the Stack:

static void Main()
{
  Stack stack = new Stack();
  stack.Push ("sausage");
  string s = (string) stack.Pop();   // Downcast, so explicit cast is needed
  Console.WriteLine (s);             // sausage
  
  // You can even push value types:
  stack.Push (3);
  int three = (int) stack.Pop();
}

Boxing & Unboxing

// Boxing is the act of casting a value-type instance to a reference-type instance;
// unboxing is the reverse.

int x = 9;
object obj = x;           // Box the int
int y = (int)obj;         // Unbox the int

y.Dump();

Unboxing to Wrong Type

// When unboxing, the types must match exactly:

object obj = 9;           // 9 is inferred to be of type int
long x = (long) obj;      // InvalidCastException

Unboxing to Wrong Type - Fix

object obj = 9;

// First, unbox to the correct type (int), then implicitly convert to long:

long x = (int) obj;
x.Dump();

// This also works:

object obj2 = 3.5;              // 3.5 is inferred to be of type double
int y = (int) (double) obj2;    // x is now 3
y.Dump();

Copying Semantics of Boxing & Unboxing

// Boxing copies the value-type instance into the new object, and unboxing copies
// the contents of the object back into a value-type instance.

int i = 3;
object boxed = i;
i = 5;
Console.WriteLine (boxed);    // 3

GetType and typeof

// All types in C# are represented at runtime with an instance of System.Type.
// There are two basic ways to get a System.Type object:
//  ? Call GetType on the instance.
//  ? Use the typeof operator on a type name.

static void Main()
{
  Point p = new Point();
  Console.WriteLine (p.GetType().Name);             // Point
  Console.WriteLine (typeof (Point).Name);          // Point
  Console.WriteLine (p.GetType() == typeof(Point)); // True
  Console.WriteLine (p.X.GetType().Name);           // Int32
  Console.WriteLine (p.Y.GetType().FullName);       // System.Int32
}

public class Point { public int X, Y; }

The ToString Method

// The ToString method is defined on System.Object and returns the default textual representation
// of a type instance:

static void Main()
{
  int x = 1;
  string s = x.ToString();     // s is "1"
  
  Panda p = new Panda { Name = "Petey" };
  Console.WriteLine (p.ToString());     // Petey
}

// You can override the ToString method on custom types:

public class Panda
{
  public string Name;
  public override string ToString() { return Name; }
}
Structs

Structs

// A struct is similar to a class, with several key differences (as described in the book).
// In particular, a struct is a value type rather than a reference type.

// The construction semantics are different, too:

public struct Point
{
  public int X, Y;
  public Point (int x, int y) { X = x; Y = y; }
  // The parameterless constructor is implicit.
}

static void Main()
{
  Point p1 = new Point ();       // p1.x and p1.y will be 0
  p1.Dump();
  
  Point p2 = new Point (1, 1);   // p1.x and p1.y will be 1
  p2.Dump();
}

Structs - Illegal Construction Examples

// Changing the following struct to a class makes the type legal:

public struct Point
{
  int x = 1;                // Illegal: cannot initialize field
  int y;
  public Point() { }            // Illegal: cannot have parameterless constructor  
  public Point (int x) { this.x = x; }  // Illegal: must assign field y
}

static void Main() { }

ref Structs

ref struct Point { public int X, Y; }

class MyClass { Point P; }         // Error: will not compile!

static void Main()
{
  var points = new Point [100];    // Error: will not compile!
}
Access Modifiers

Access Modifiers - Examples

// The access modifiers are public, internal, protected and private.
//
// public is the default for members of an enum or interface.
// internal is the default for nonnested types.
// private is the default for everything else.

class Class1 {}     // Class1 is internal (default) - visible to other types in same assembly
public class Class2 {}  // Class2 is visible to everything, including types in other assemblies

class ClassA
{
  int x;        // x is private (default) - cannot be accessed from other types
}

class ClassB
{
  internal int x;    // x can be accessed from other types in same assembly
}

class BaseClass
{
  void Foo()           {}    // Foo is private (default)
  protected void Bar() {}    // Foo is accessible to subclasses
}

class Subclass : BaseClass
{
   void Test1() { Foo(); }     // Error - cannot access Foo
   void Test2() { Bar(); }     // OK
}

static void Main() { }

Friend Assemblies

// Unsigned friend:
//    [assembly: InternalsVisibleTo ("Friend")]

// Signed friend:
//    [assembly: InternalsVisibleTo ("StrongFriend, PublicKey=0024f000048c...")]

// To obtain an assembly's public key, hit F5 to run the following code:

using (var dialog = new OpenFileDialog())
{
  dialog.Title = "Locate assembly";
  dialog.Filter = "Assembly files|*.dll;*.exe";
  dialog.DefaultExt = ".dll";
  
  if (dialog.ShowDialog() != DialogResult.OK) return;
  if (!File.Exists (dialog.FileName)) return;
  
  var aName = Assembly.LoadFile (dialog.FileName).GetName();

  string key = string.Join ("", 
    aName.GetPublicKey().Select (b => b.ToString ("x2")).ToArray());
    
  string assemAttrib = "[assembly: InternalsVisibleTo (\"" 
    + aName.Name
    + ", PublicKey=" + key.Dump ("Full Key")
    + "\")]";
    
  assemAttrib.Dump ("Assembly Attribute");
  
  Clipboard.SetText (assemAttrib);
}

Accessibility Capping

// A type caps the accessibility of its declared members:

class C            // Class C is implicitly internal
{
  public void Foo() {}  // Foo's accessibility is capped at internal
}

static void Main() { }

Restrictions on Access Modifiers

// When overriding a base class function, accessibility must be identical on the overridden function:

class BaseClass             { protected virtual  void Foo() {} }
class Subclass1 : BaseClass { protected override void Foo() {} }  // OK
class Subclass2 : BaseClass { public    override void Foo() {} }  // Error

// A subclass itself can be less accessible than a base class, but not more:

internal class A { }
public class B : A { }          // Error

static void Main() { }
Interfaces

Interfaces

// The IEnumerator interface is part of the .NET Framework, defined in System.Collections.
// We can define our own version of this as follows:

public interface IEnumerator
{
  bool MoveNext();
  object Current { get; }
  void Reset();
}

// Here's a class that implements this interface:

class Countdown : IEnumerator
{
  int count = 11;
  public bool MoveNext () => count-- > 0;
  public object Current   => count;
  public void Reset()     { throw new NotSupportedException(); }
}

static void Main()
{
  IEnumerator e = new Countdown();
  while (e.MoveNext())
    Console.Write (e.Current);      // 109876543210  
}

Extending an Interface

// We can extend interfaces - just like extending classes:

public interface IUndoable             { void Undo(); }
public interface IRedoable : IUndoable { void Redo(); }

static void Main()
{
  IRedoable r = null;
  IUndoable u = r;
}

Explicit Interface Implementation

// Implementing multiple interfaces can sometimes result in a collision between member signatures.
// You can resolve such collisions by explicitly implementing an interface member:

interface I1 { void Foo(); }
interface I2 { int Foo(); }

public class Widget : I1, I2
{
  public void Foo ()
  {
    Console.WriteLine ("Widget's implementation of I1.Foo");
  }
  
  int I2.Foo()
  {
    Console.WriteLine ("Widget's implementation of I2.Foo");
    return 42;
  }
}

static void Main()
{
  Widget w = new Widget();
  w.Foo();                      // Widget's implementation of I1.Foo
  ((I1)w).Foo();                // Widget's implementation of I1.Foo
  ((I2)w).Foo();                // Widget's implementation of I2.Foo
}

// Another reason to explicitly implement interface members is to hide members that are
// highly specialized and distracting to a type’s normal use case.

Implementing Interface Members Virtually

// An implicitly implemented interface member is, by default, sealed. It must be marked
// virtual or abstract in the base class in order to be overridden:

public interface IUndoable { void Undo(); }

public class TextBox : IUndoable
{
  public virtual void Undo() => Console.WriteLine ("TextBox.Undo");
}

public class RichTextBox : TextBox
{
  public override void Undo() => Console.WriteLine ("RichTextBox.Undo");
}

static void Main()
{
  // Calling the interface member through either the base class or the interface
  // calls the subclass’s implementation:
  RichTextBox r = new RichTextBox();
  r.Undo();                          // RichTextBox.Undo
  ((IUndoable)r).Undo();             // RichTextBox.Undo
  ((TextBox)r).Undo();               // RichTextBox.Undo
}

Reimplementing an Interface in a Subclass

// A subclass can reimplement any interface member already implemented by a base class.
// Reimplementation hijacks a member implementation (when called through the interface):

public interface IUndoable { void Undo(); }

public class TextBox : IUndoable
{
  void IUndoable.Undo() => Console.WriteLine ("TextBox.Undo");
}

public class RichTextBox : TextBox, IUndoable
{
  public new void Undo() => Console.WriteLine ("RichTextBox.Undo");
}

static void Main()
{
  // Calling the reimplemented member through the interface calls the subclass’s implementation:
  RichTextBox r = new RichTextBox();
  r.Undo();                 // RichTextBox.Undo      Case 1
  ((IUndoable)r).Undo();    // RichTextBox.Undo      Case 2
}

Reimplementing an Interface - Contrast

// Suppose that TextBox instead implemented Undo implicitly:

public interface IUndoable { void Undo(); }

public class TextBox : IUndoable
{
  public void Undo() => Console.WriteLine ("TextBox.Undo");
}

public class RichTextBox : TextBox, IUndoable
{
  public new void Undo() => Console.WriteLine ("RichTextBox.Undo");
}

static void Main()
{
  // This would give us another way to call Undo, which would “break” the system, as shown in Case 3:
  
  RichTextBox r = new RichTextBox();
  r.Undo();                 // RichTextBox.Undo      Case 1
  ((IUndoable)r).Undo();    // RichTextBox.Undo      Case 2
  ((TextBox)r).Undo();      // TextBox.Undo          Case 3
}

Alternatives to interface reimplementation

// Even with explicit member implementation, interface reimplementation is problematic for a
// couple of reasons.

// The following pattern is a good alternative if you need explicit interface implementation:

public interface IUndoable { void Undo(); }

public class TextBox : IUndoable
{
  void IUndoable.Undo()         => Undo();    // Calls method below
  protected virtual void Undo() => Console.WriteLine ("TextBox.Undo");
}

public class RichTextBox : TextBox
{
  protected override void Undo() => Console.WriteLine ("RichTextBox.Undo");
}

static void Main()
{
  IUndoable r = new RichTextBox();
  r.Undo();    // RichTextBox.Undo
}

Interfaces and Boxing

// Casting a struct to an interface causes boxing. Calling an implicitly implemented
// member on a struct does not cause boxing:

interface  I { void Foo();          }
struct S : I { public void Foo() {} }

static void Main()
{
  S s = new S();
  s.Foo();         // No boxing.
  
  I i = s;         // Box occurs when casting to interface.
  i.Foo();
}

Default interface members

void Main()
{
  var logger = new Logger();
  
  // We can't call the Log method directly:
  // foo.Log ("message")   // Won't compile
  
  // But we can call it via the interface:
  ((ILogger)logger).Log ("message");  
}

interface ILogger
{
  void Log (string text) => Console.WriteLine (text);
}

class Logger : ILogger
{
  // We don't need to implement anything
}

Default interface members - static members

void Main()
{
  ILogger.Prefix = "File log: ";

  var logger = new Logger();  
  ((ILogger)logger).Log ("message");  
}

interface ILogger
{
  void Log (string text) =>
    Console.WriteLine (Prefix + text);

  static string Prefix = "";
}


class Logger : ILogger
{
  // We don't need to implement anything
}

Default interface members - scenario

void Main()
{
  ILogger foo = new Logger();
  foo.Log (new Exception ("test"));  
}

class Logger : ILogger
{  
  public void Log (string message) => Console.WriteLine (message);
}

interface ILogger
{
  // Let's suppose the interface as always defined this method:
  void Log (string message);  
  
  // Adding a new member to an interface need not break implementors:
  public void Log (Exception ex) => Log (ExceptionHeader + ex.Message);

  static string ExceptionHeader = "Exception: ";
}
Enums

Enums

// An enum is a special value type that lets you specify a group of named numeric constants:

public enum BorderSide { Left, Right, Top, Bottom }

static void Main()
{
  BorderSide topSide = BorderSide.Top;
  bool isTop = (topSide == BorderSide.Top);  
  isTop.Dump();
}

// You may specify an alternative integral type:
public enum BorderSideByte : byte { Left, Right, Top, Bottom }

// You may also specify an explicit underlying value for each enum member:
public enum BorderSideExplicit : byte { Left=1, Right=2, Top=10, Bottom=11 }

public enum BorderSidePartiallyExplicit : byte { Left=1, Right, Top=10, Bottom }

Enum Conversions

public enum BorderSide { Left, Right, Top, Bottom }

public enum HorizontalAlignment
{
  Left = BorderSide.Left,
  Right = BorderSide.Right,
  Center
}

static void Main()
{
  // You can convert an enum instance to and from its underlying integral value with an explicit cast:
  
  int i = (int) BorderSide.Left;
  i.Dump ("i");
  
  BorderSide side = (BorderSide) i;
  side.Dump ("side");
  
  bool leftOrRight = (int) side <= 2;
  leftOrRight.Dump ("leftOrRight");
  
  HorizontalAlignment h = (HorizontalAlignment) BorderSide.Right;
  h.Dump ("h");
  
  BorderSide b = 0;    // No cast required with the 0 literal.
  b.Dump ("b");
}

Flags Enums

// You can combine enum members. To prevent ambiguities, members of a combinable enum require
// explicitly assigned values, typically in powers of two:

[Flags]
public enum BorderSides { None=0, Left=1, Right=2, Top=4, Bottom=8 }

static void Main()
{
  BorderSides leftRight = BorderSides.Left | BorderSides.Right;
  
  if ((leftRight & BorderSides.Left) != 0)
    Console.WriteLine ("Includes Left");   // Includes Left
  
  string formatted = leftRight.ToString();   // "Left, Right"
  
  BorderSides s = BorderSides.Left;
  s |= BorderSides.Right;
  Console.WriteLine (s == leftRight);   // True
  
  s ^= BorderSides.Right;               // Toggles BorderSides.Right
  Console.WriteLine (s);                // Left
}

Flags Enums - Combinations

// For convenience, you can include combination members within an enum declaration itself:

[Flags]
public enum BorderSides
{
  None=0,
  Left=1, Right=2, Top=4, Bottom=8,
  LeftRight = Left | Right, 
  TopBottom = Top  | Bottom,
  All       = LeftRight | TopBottom
}

static void Main()
{
  BorderSides.All.Dump();
  
  // The bitwise, arithmetic, and comparison operators return the result of processing
  // the underlying integral values:  
  (BorderSides.All ^ BorderSides.LeftRight).Dump();
}

Type-Safety Issues

public enum BorderSide { Left, Right, Top, Bottom }

static void Main()
{
  // Since an enum can be cast to and from its underlying integral type, the actual value
  // it may have may fall outside the bounds of a legal enum member:
  BorderSide b = (BorderSide) 12345;
  Console.WriteLine (b);                // 12345
  
  BorderSide b2 = BorderSide.Bottom;
  b2++;                  // No errors
  Console.WriteLine (b2);          // 4 (illegal value)
}

// An invalid BorderSide would break the following method:

void Draw (BorderSide side)
{
  if      (side == BorderSide.Left)  { /*...*/ }
  else if (side == BorderSide.Right) { /*...*/ }
  else if (side == BorderSide.Top)   { /*...*/ }
  else                               { /*...*/ }  // Assume BorderSide.Bottom
}

Type-Safety Issues - Workaround

[Flags]
public enum BorderSides { Left=1, Right=2, Top=4, Bottom=8 }

static bool IsFlagDefined (Enum e)
{
  decimal d;
  return !decimal.TryParse (e.ToString(), out d);
}

static void Main()
{
  for (int i = 0; i <= 16; i++)
  {
    BorderSides side = (BorderSides)i;
    Console.WriteLine (IsFlagDefined (side) + " " + side);
  }
}
Nested Types

Nested Types

// A nested type is declared within the scope of another type. For example:

public class TopLevel
{
  public class Nested { }               // Nested class
  public enum Color { Red, Blue, Tan }  // Nested enum
}

static void Main()
{
  TopLevel.Color color = TopLevel.Color.Red;  
}

Nested Types - Private Member Visibility

public class TopLevel
{
  static int x;
  public class Nested
  {
    public static void Foo() { Console.WriteLine (TopLevel.x); }
  }
}

static void Main()
{
  TopLevel.Nested.Foo();
}

Nested Types - Protected Member Visibility

public class TopLevel
{
  protected class Nested { }
}

public class SubTopLevel : TopLevel
{
  static void Foo() { new TopLevel.Nested(); }
}

static void Main()
{
}
Generics

Generic Types

// A generic type declares type parameters—placeholder types to be filled in by the consumer
// of the generic type, which supplies the type arguments:

public class Stack<T>
{
  int position;
  T[] data = new T[100];
  public void Push (T obj) => data[position++] = obj;
  public T Pop()           => data[--position];
}

static void Main()
{
  var stack = new Stack<int>();
  stack.Push(5);
  stack.Push(10);
  int x = stack.Pop();        // x is 10
  int y = stack.Pop();        // y is 5
  
  x.Dump(); y.Dump();
}

Why Generics Exist

// Generics exist to write code that is reusable across different types. Without generic types,
// writing a general-purpose stack would require a solution such as this:

public class ObjectStack
{
  int position;
  object[] data = new object[10];
  public void Push (object obj) => data[position++] = obj;
  public object Pop()           => data[--position];
}

static void Main()
{
  // Now suppose we want a stack that stores just integers:
  ObjectStack stack = new ObjectStack();
  
  // It's easy to make mistakes:
  stack.Push ("s");          // Wrong type, but no error!
  int i = (int)stack.Pop();  // Downcast - runtime error!
}

Generic Methods

// A generic method declares type parameters within the signature of a method.

static void Swap<T> (ref T a, ref T b)
{
  T temp = a;
  a = b;
  b = temp;
}

static void Main()
{
  int x = 5;
  int y = 10;
  Swap (ref x, ref y);
  
  x.Dump(); y.Dump();
}

Declaring Type Parameters

// Type parameters can be introduced in the declaration of classes, structs, interfaces,
// delegates (covered in Chapter 4), and methods:
struct Nullable<T>
{
  public T Value { get; set; }
}

// A generic type or method can have multiple parameters:
class Dictionary <TKey, TValue> { /*...*/ }

static void Main()
{
  // To instantiate:
  Dictionary<int,string> myDic = new Dictionary<int,string>();
  
  // Or:
  var myDicEasy = new Dictionary<int,string>();
}

// Generic type names and method names can be overloaded as long as the number of type
// parameters is different:
class A        { }
class A<T>     { }
class A<T1,T2> { }

Typeof and Unbound Generic Types

// It's possible for an unbound generic type to exist at runtime—purely as a Type object.

class A<T> {}
class A<T1,T2> {}

static void Main()
{
  // The only way to specify an unbound generic type in C# is with the typeof operator:
  Type a1 = typeof (A<>);   // Unbound type (notice no type arguments).
  Type a2 = typeof (A<,>);  // Use commas to indicate multiple type args.
  
  // You can also use the typeof operator to specify a closed type:
  Type a3 = typeof (A<int,int>);

}

// or an open type (which is closed at runtime):
class B<T> 
{
  void X() { Type t = typeof (T); }
}

The default Generic Value

// The default keyword can be used to get the default value given a generic type parameter:

static void Zap<T> (T[] array)
{
  for (int i = 0; i < array.Length; i++)
    array[i] = default(T);
}

static void Main()
{
  int[] numbers = { 1, 2, 3 };
  Zap (numbers);
  numbers.Dump();
}

Generic Constraints

/* Constraints can be applied to a type parameter restrict the type arguments.

where T : base-class   // Base class constraint
where T : interface    // Interface constraint
where T : class        // Reference-type constraint
where T : struct       // Value-type constraint (excludes Nullable types)
where T : new()        // Parameterless constructor constraint
where U : T            // Naked type constraint

*/

class     SomeClass {}
interface Interface1 {}

class GenericClass<T> where T : SomeClass, Interface1 {}  // Class & interface constraint

static T Max <T> (T a, T b) where T : IComparable<T>  // Self-referencing interface constraint
{
  return a.CompareTo (b) > 0 ? a : b;
}

static void Main()
{
  int z = Max (5, 10);               // 10
  string last = Max ("ant", "zoo");  // zoo

  z.Dump(); last.Dump();
}

Parameterless Constructor Constraint

// The parameterless constructor constraint requires T to have a public parameterless constructor.
// If this constraint is defined, you can call new() on T:

static void Initialize<T> (T[] array) where T : new()
{
  for (int i = 0; i < array.Length; i++)
    array[i] = new T();
}

static void Main()
{
  var builders = new StringBuilder[100];
  Initialize (builders);
  builders[37].Dump();
}

Naked Type Constraint

// The naked type constraint requires one type parameter to derive from another type parameter:

class Stack<T>
{
  Stack<U> FilteredStack<U>() where U : T
  {
    /* ... */
    return default(Stack<U>);
  }
}

static void Main() { }

Subclassing Generic Typest

// A generic class can be subclassed just like a nongeneric class.
// The subclass can leave the base class’s type parameters open:

class Stack<T>                   { /*...*/ }
class SpecialStack<T> : Stack<T> { /*...*/ }

// Or the subclass can close the generic type parameters with a concrete type:

class IntStack : Stack<int>  { /*...*/ }

// A subtype can also introduce fresh type arguments:

class List<T>                     { /*...*/ }
class KeyedList<T,TKey> : List<T> { /*...*/ }

static void Main() { }

Self-Referencing Generic Declarations

// A type can name itself as the concrete type when closing a type argument:

public class Balloon : IEquatable<Balloon>
{
  public string Color { get; set; }
  public int CC { get; set; }
  
  public bool Equals (Balloon b)
  {
    if (b == null) return false;
    return b.Color == Color && b.CC == CC;
  }
  
  // In real life, we would override object.Equals / GetHashCode as well - see Chapter 6.
}

static void Main()
{
  var b1 = new Balloon { Color = "Red", CC = 123 };
  var b2 = new Balloon { Color = "Red", CC = 123 };
  
  b1.Equals (b2).Dump();
}

Static Data

// Static data is unique for each closed type:

class Bob<T> { public static int Count; }

static void Main()
{
  Console.WriteLine (++Bob<int>.Count);     // 1
  Console.WriteLine (++Bob<int>.Count);     // 2
  Console.WriteLine (++Bob<string>.Count);  // 1
  Console.WriteLine (++Bob<object>.Count);  // 1
}

Type Parameters & Conversions - Problem

// The most common scenario is when you want to perform a reference conversion:

StringBuilder Foo<T> (T arg)
{
  if (arg is StringBuilder)
    return (StringBuilder) arg;   // Will not compile: Cannot convert T to StringBuilder
  
  /*...*/
  return null;
}

static void Main()
{
}

Type Parameters & Conversions - Solution #1

// The simplest solution is to instead use the as operator, which is unambiguous because
// it cannot perform custom conversions:

StringBuilder Foo<T> (T arg)
{
  StringBuilder sb = arg as StringBuilder;
  if (sb != null) return sb;
  
  /*...*/
  return null;
}

static void Main()
{
}

Type Parameters & Conversions - Solution #2

// A more general solution is to first cast to object:

StringBuilder Foo<T> (T arg)
{
  if (arg is StringBuilder)
    return (StringBuilder) (object) arg;
  
  /*...*/
  return null;
}

static void Main()
{
}

Type Parameters & Conversions - Unboxing

// Unboxing conversions can also introduce ambiguities; again the solution is to first cast to object:

int Foo<T> (T x) => (int) (object) x;

static void Main()
{
}

Covariance - Classes - Problem

// Generic classes are not covariant, to ensure static type safety. Consider the following:

class Animal {}
class Bear : Animal {}
class Camel : Animal {}

public class Stack<T>   // A simple Stack implementation
{
  int position;
  T[] data = new T[100];
  public void Push (T obj) => data[position++] = obj;
  public T Pop()           => data[--position];
}

static void Main() 
{
  // The following fails to compile:
  Stack<Bear> bears = new Stack<Bear>();
  Stack<Animal> animals = bears;      // Compile-time error
  
  // That restriction prevents the possibility of runtime failure with the following code:
  animals.Push (new Camel());          // Trying to add Camel to bears
}

Covariance - Classes - Hindering Reusability

// Lack of covariance with classes can hinder reusability. 

class Animal {}
class Bear : Animal {}
class Camel : Animal {}

public class Stack<T>   // A simple Stack implementation
{
  int position;
  T[] data = new T[100];
  public void Push (T obj) => data[position++] = obj;
  public T Pop()           => data [--position];
}

static class ZooCleaner
{
  public static void Wash (Stack<Animal> animals) { /*...*/ }
}

static void Main()
{
  Stack<Bear> bears = new Stack<Bear>();
  ZooCleaner.Wash (bears);        // Will not compile!
}

Covariance - Classes - Workaround

// Lack of covariance with classes can hinder reusability. 

class Animal {}
class Bear : Animal {}
class Camel : Animal {}

public class Stack<T>   // A simple Stack implementation
{
  int position;
  T[] data = new T[100];
  public void Push (T obj) => data[position++] = obj;
  public T Pop()           => data[--position];

}

static class ZooCleaner
{
  public static void Wash<T>(Stack<T> animals) where T : Animal { /*...*/ }
}

static void Main() 
{
  Stack<Bear> bears = new Stack<Bear>();
  ZooCleaner.Wash (bears);        // Works!
}

Covariance - Arrays

class Animal {}
class Bear : Animal {}
class Camel : Animal {}

static void Main() 
{
  // For historical reasons, array types are covariant.
  Bear[] bears = new Bear[3];
  Animal[] animals = bears;     // OK

  // The downside of this reusability is that element assignments can fail at runtime:
  animals[0] = new Camel();     // Runtime error
}

Covariance - Interfaces

// As of C# 4.0, generic interfaces support covariance for type parameters marked with the out modifier:
public interface IPoppable<out T> { T Pop(); }

class Animal {}
class Bear : Animal {}
class Camel : Animal {}

public class Stack<T> : IPoppable<T>
{
  int position;
  T[] data = new T [100];
  public void Push (T obj) => data [position++] = obj;
  public T Pop()           => data [--position];
}

static void Main() 
{
  var bears = new Stack<Bear>();
  bears.Push (new Bear());
  
  // Bears implements IPoppable<Bear>. We can convert to IPoppable<Animal>:
  IPoppable<Animal> animals = bears;       // Legal
  Animal a = animals.Pop();
}

// This is also now legal:
class ZooCleaner
{
  public static void Wash (IPoppable<Animal> animals) { /*...*/ }
}

Contravariance - Interfaces

// Type parameters marked with the in modifier indicate contravariance:
public interface IPoppable<out T> { T Pop(); }
public interface IPushable<in T> { void Push (T obj); }

class Animal {}
class Bear : Animal {}
class Camel : Animal {}

// Note that Stack<T> can implement both IPoppable<T> and IPushable<T>:
public class Stack<T> : IPoppable<T>, IPushable<T>
{
  int position;
  T[] data = new T[100];
  public void Push (T obj) => data[position++] = obj;
  public T Pop()           => data[--position];
}

static void Main() 
{
  IPushable<Animal> animals = new Stack<Animal>();
  IPushable<Bear> bears = animals;    // Legal
  bears.Push (new Bear());
}

Contravariance - More Examples

/* The following interface is defined as part of the .NET Framework:

public interface IComparer<in T>
{
  // Returns a value indicating the relative ordering of a and b
  int Compare (T a, T b);
}

*/

static void Main() 
{
  var objectComparer = Comparer<object>.Default;
  IComparer<string> stringComparer = objectComparer;
  int result = stringComparer.Compare ("Brett", "Jemaine");
  result.Dump();
}