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