Chapter 3 - Creating Types in C#

Classes

Fields

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

var o = new Octopus();
o.Age.Dump();

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

Fields - readonly

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

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

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

Constants - scoped to class

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

Test.Message.Dump();

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

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:

Foo1 (10).Dump();
Foo2 (10).Dump();

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

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

Local methods - with top-level statements

// When you use top-level statements, your methods are implicitly local.
// We can verify this by trying to access a variable outside the method:

int x = 3;
Foo();

// Foo is a local method
void Foo() => Console.WriteLine (x);    // We can access x

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

// We have to use a Main() method here to test this,
// because methods in top-level statements are *local methods*
// which cannot be overloaded.

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

void Main() {}

Constructors

Panda p = new Panda ("Petey");   // Call constructor

public class Panda
{
  string name;                   // Define field
  public Panda (string n)        // Define constructor
  {
    name = n;                    // Initialization code (set up field)
  }
}

Constructors - Overloading

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

new Wine (78).Dump();
new Wine (78, 2001).Dump();

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

Constructors - Nonpublic

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

Class1 c1 = Class1.Create();  // OK
Class1 c2 = new Class1();    // Error: Will not compile

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

Deconstructors

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

// The following mix-and-match is permitted, from C# 10:
double x1 = 0;
(x1, double y2) = rect;

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

Object Initializers

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

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

Bunny b = new Bunny (
    name: "Bo",
    likesCarrots: true);
  
  b.Dump();

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

The this Reference

// The this reference refers to the instance itself:

new Panda().Marry (new Panda());

public class Panda
{
  public Panda Mate;

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

Properties

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

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

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

Properties - calculated & read-only

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

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

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

Properties - expression-bodied

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

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

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

}

Automatic Properties

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

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

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

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

Property Initializers

var stock = new Stock();
stock.CurrentPrice.Dump();
stock.Maximum.Dump();

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

Properties - get & set accessibility

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

new Foo { X = 5 };    // Will not compile - X has a private set accessor.

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
}

Indexers

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

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)

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 Constructors

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

// Type is initialized only once
new Test();
new Test();
new Test();

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

Static Constructors & Field Initialization Order

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

Foo.X.Dump ("X");  // 0
Foo.Y.Dump ("Y");  // 3

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

Static Constructors & Field Initialization Order (Constructor Call)

// Another way to go awry:

Console.WriteLine (Foo.X);   // 3

Util.NewProcess = true;      // Force LINQPad to create new process on each run.

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

Partial Types

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

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

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

Partial Methods

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

var paymentForm = new PaymentForm (50);

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

Extended Partial Methods

// The presence of an accessiblity modifier on a partial method denotes an 'extended partial method'.
// Extended partial methods *must* have implementations:

public partial class Test
{
  public partial void M1();    // Extended partial method
  private partial void M2();   // Extended partial method

  public partial bool IsValid (string identifier);
  internal partial bool TryParse (string number, out int result);
}

public partial class Test
{
  public partial void M1() { }
  private partial void M2() {}

  public partial bool IsValid (string identifier) => !string.IsNullOrEmpty (identifier);
  internal partial bool TryParse (string number, out int result) => int.TryParse (number, out result);
}

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.

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.

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

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

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

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

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:

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.

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:

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

Covariant returns

// From C# 9, we can override a method such that it returns a more derived (subclased) type:

House mansion1 = new House { Name = "McMansion", Mortgage = 250000 };
House mansion2 = mansion1.Clone();

public class Asset
{
  public string Name;
  public virtual Asset Clone() => new Asset { Name = Name };
}

public class House : Asset
{
  public decimal Mortgage;
  
  // We can return House when overriding:
  public override House Clone() => new House { Name = Name, Mortgage = Mortgage };
}

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.

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:

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

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:

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:

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:

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:

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:

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

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:

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.

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:


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

int x = 1;
string s = x.ToString();     // s is "1"

Panda p = new Panda { Name = "Petey" };
Console.WriteLine (p.ToString());     // Petey

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:

Point p1 = new Point ();       // p1.x and p1.y will be 1
Point p2 = new Point (2, 2);   // p2.x and p2.y will be 2

p1.Dump();
p2.Dump();

struct Point
{
  public int x, y;
  public Point ()             { this.x = 1; this.y = 1; }
  public Point (int x, int y) { this.x = x; this.y = y; }
}

Structs - Illegal Construction Examples

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

Point p1 = new Point ();
Point p2 = new Point (2, 2);
Point p3 = new Point (2);

p1.Dump();
p2.Dump();
p3.Dump();

struct Point
{
  public int x, y;
  public Point ()             { this.x = 1; this.y = 1; }
  public Point (int x, int y) { this.x = x; this.y = y; }
  public Point (int x)        { this.x = x; }   // Illegal: y not assigned
}

Structs - default constructor

Point p1 = new Point();       // p1.x and p1.y will be 1
Point p2 = default;           // p2.x and p2.y will be 0
var points = new Point [10];  // Each point in the array will be (0,0)
var test = new Test();        // test.p will be (0,0);

p1.Dump();
p2.Dump();
points.Dump();
test.Dump();

struct Point
{
  public int x = 1;
  public int y;
  public Point() => y = 1;
}

class Test { public Point p; }

ref Structs

var points = new Point [100];    // Error: will not compile!

ref struct Point { public int X, Y; }

class MyClass { Point P; }         // 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
}

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
}

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

IEnumerator e = new Countdown();
  while (e.MoveNext())
    Console.Write (e.Current);      // 109876543210

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

Extending an Interface

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

IRedoable r = null;
IUndoable u = r;

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

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:

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

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


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

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

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

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

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

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

Reimplementing an Interface - Contrast

// Suppose that TextBox instead implemented Undo implicitly:

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

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

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:

IUndoable r = new RichTextBox();
r.Undo();    // RichTextBox.Undo

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

Interfaces and Boxing

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

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

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

Default interface members

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

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

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:

BorderSide topSide = BorderSide.Top;
bool isTop = (topSide == BorderSide.Top);  
isTop.Dump();

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

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

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

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

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

Flags Enums

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

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]
public enum BorderSides { None = 0, Left = 1, Right = 2, Top = 4, Bottom = 8 }

Flags Enums - Combinations

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

BorderSides.All.Dump();

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

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

Type-Safety Issues

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

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

Type-Safety Issues - Workaround

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

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

[Flags]
public enum BorderSides { Left = 1, Right = 2, Top = 4, Bottom = 8 }
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:

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

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

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:

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

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

Generic Methods

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

int x = 5;
int y = 10;
Swap (ref x, ref y);

x.Dump(); y.Dump();

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

Declaring Type Parameters

// Type parameters can be introduced in the declaration of classes, structs, interfaces,
// delegates (covered in Chapter 4), and methods:
// To instantiate:
Dictionary<int, string> myDic = new Dictionary<int, string>();

// Or:
var myDicEasy = new Dictionary<int, string>();

struct Nullable<T>
{
  public T Value { get; set; }
}

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


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

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

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


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

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

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

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

*/

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

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

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

class SomeClass { }
interface Interface1 { }

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

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:

var builders = new StringBuilder [100];
Initialize (builders);
builders [37].Dump();

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

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

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> { /*...*/ }

Self-Referencing Generic Declarations

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

var b1 = new Balloon { Color = "Red", CC = 123 };
var b2 = new Balloon { Color = "Red", CC = 123 };

b1.Equals (b2).Dump();

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 Data

// Static data is unique for each closed type:

Console.WriteLine (++Bob<int>.Count);     // 1
Console.WriteLine (++Bob<int>.Count);     // 2
Console.WriteLine (++Bob<string>.Count);  // 1
Console.WriteLine (++Bob<object>.Count);  // 1

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

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

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

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

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;

Covariance - Classes - Problem

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

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

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

Covariance - Classes - Hindering Reusability

// Lack of covariance with classes can hinder reusability. 

Stack<Bear> bears = new Stack<Bear>();
ZooCleaner.Wash (bears);        // Will not compile!

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) { /*...*/ }
}

Covariance - Classes - Workaround

// Lack of covariance with classes can hinder reusability. 

Stack<Bear> bears = new Stack<Bear>();
ZooCleaner.Wash (bears);        // Works!

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 { /*...*/ }
}

Covariance - Arrays

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

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

Covariance - Interfaces

// As of C# 4.0, generic interfaces support covariance for type parameters marked with the out modifier:
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();

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


// 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:
IPushable<Animal> animals = new Stack<Animal>();
IPushable<Bear> bears = animals;    // Legal
bears.Push (new Bear());

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

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

*/

var objectComparer = Comparer<object>.Default;
IComparer<string> stringComparer = objectComparer;
int result = stringComparer.Compare ("Brett", "Jemaine");
result.Dump();
C# 12 in a Nutshell
Buy from amazon.com Buy print or Kindle edition
Buy from ebooks.com Buy PDF edition
Buy from O'Reilly Read via O'Reilly subscription