14
chap03
Jason Zhu edited this page 2021-08-10 00:26:19 +10:00

Chapter 3. Creating Types in C#

3.1 Classes

3.1.1 Fields

3.1.2 Constants

3.1.3 Instance Constructors

3.1.4 Deconstructors

3.1.5 Object Initializers

3.1.6 The this Reference

  • this reference refers to the instance itself
  • this reference disambiguate a local variable or parameter from a field (of the instance)
public class Test
{
    string name;
    public Test (string name)
    {
        this.name = name;
    }
}

3.1.7 Properties

Properties look like fields from outside, but inside contain logic for geeter and setter

e.g. Property from outside

Stock msft = new Stock();
msft.CurrentPrice = 30; // looks like fileds
msft.CurrentPrice -= 3; // Contain logic

Declaration of Properties: similar as fields but with get/set blocks:

  • get & set are property accessors
  • set accessor has implicit parameter named value of the property type. So we can use value to assign to a private field
  • Typically has a dedicated backing field (i.e. private field)
  • Benefit of accessor:
    • Provide control that enable the implementer to choose internal representation without exposing internal details to user of property

e.g. CurrentPrice property

public class Stock
{
    // Field of the class
    decimal currentPrice; // private "backing" field

    // Property of the class
    public decimal CurrentPrice; // public property
    {
        get { return currentPrice; }
        set { currentPrice = value; } // can throw an exception if `value` was outside a valid range of values
    }
}

Read-only and calculated properties

  • Read-only property: property with only a get accessor
    • can be computed from other data
  • Write-only property (Rarely Used): with only a set accessor
public class Stock
{
    decimal currentPrice, sharesOwned;
    public decimal Worth
    {
        get { return currentPrice * sharesOwned; }
    }
}

Expression-bodied properties

Expression-bodied properties: shortened format of properties declaration.

  • Use a fat arrow to replace all {} and get/set/return keywords

e.g. expression-bodied property

public decimal Worth
{
    get => currentPrice * sharesOwned; // get accessor of expression-bodied property
    set => sharesOwned = value / currentPrice; // set accessor
}

Automatic properties (C# 3.0)

As most common implementation for a property is simple getter & setter, Automatic property declaration instructs compiler to generate a private backing field that cannot be referred to:

  • private or protected can be added on set for exposing the property as read-only to other types
public class Stock
{
    ...
    public decimal CurrentPrice { get; set; } // Automatic property
}

Property initializer

Property initializer can be added to automatic property like fileds:

public decimal CurrentPrice { get; set; } = 123; // gives CurrentPrice an initial value of 123

e.g. read-only property with initializer

public int Maximum { get; } = 999;

get and set accessibility

get/set can have different access levels

  • common usage: have a public property with an internal/private setter
public class Foo
{
    private decimal x;
    public decimal X
    {
        get { return x; }
        private set {x = Math.Round(value,2); } // setter is less accessible
    }
}

init-only setters (C# 9)

Declare a property accessor init instead of set:

  • The propety is read-only
  • It can be set via object initializer (e.g. below). After that, this property cannot be altered.
    • Init-only properties cannot even be set from inside their class, except initalizer, constructor
public class Note
{
    public int Pitch { get; init; } = 20; // "Init-only" property
    public int Duration { get; init } = 100;
}

var note = new Note { Pitch = 50 };

note.Pitch = 200; // Error init-only setter!

TODO: Not finished for init-only property

CLR property implementation

TODO: Omitted, may come back in future

Summary

Property is like field but with internal logic for getting/setting the backing field

public class RandomClass
{
    public Property1 { get; set; }
    public Property2_ReadOnly { get; }
}

3.1.8 Indexers

Indexer provide a natural syntax for accessing elements in a class or struct that has a list or dictionary of values

  • Access indexed value: via index argument (similar as use property name to access property). Index argument can be any type

e.g. string class use index to access char values

string s = "hello";
Console.WriteLine(s[0]); // 'h'
Console.WriteLine(s[3]); // 'l'

Implementing and indexer

Steps:

  1. Define a property called this (specified in [])
  2. Write get/set in the property this

Advaned Indexer:

  • A class/struct can have multiple indexer, each with parameter of different types
  • Read-only indexer: A indexer with get accessor only

e.g. define & use indexer

// Define indexer
class Sentence
{
    string[] words = "The quick brown forx".Split();

    public string this [int wordNum] // indexer
    {
        get { return words[wordNum]; }
        set { words[wordNum] = value; }
    }

    // Second indexer using multiple parameter
    public string this [int arg1, string arg2]
    {
        get { ... }
        set { ... }
    }

    // Read-only indexer (expression-bodied syntax version)
    public string this [int wordNum] = word[wordNum]; 
}

// Use indexer
Sentence s = new Sentence();
Console.WriteLine(s[3]); // fox
s[3] = "Kangaro";
Console.WriteLIne(s[3]); // Kangaroo

CLR indexer implementation

TODO: omitted

Using indices and ranges with indexers

Indexer can be created to support indices and rangers using Index or Range

e.g. Sentence class with indexers

// Define a class with indices and range
class Sentence
{
    public string this [Index index] => words[index];
    public string[] this [Range range] => words[range];
}

// use indices and range indexers
Sentence s = new Sentence();
Console.WriteLine(s[^1]); // fox
string[] firstTwoWords = s[..2]; // (The, quick)

3.1.9 Static Constructors

Define & Declare static constructor:

  • Constructor get executed once per type
  • Each type define only 1 static constructor
  • static constructor is parameterless and have the same name as type, as shown below

e.g. declare a static constructor

class Test
{
    // static constructor of the class
    static Test() { Console.WriteLine("Type Initialized"); }
}

When runtime will invoke static constructor invoked. 2 triggers:

  1. Instantiating the type
  2. Accessing a static member

Only 2 modifiers allowed: unsafe and extern

Static constructors and field initialization order

e.g. static field intiailizer

class Foo
{
    // Static field initializer run in order
    public static int X = Y; // 0
    public static int Y = 3; // 3
}

When static field/constructor initializer run:

  • Static field initializer run just before static constructor called.
  • If there is no static constructor, static field initializer executed before type being used in runtime

3.1.10 Static Classes

Static class means it only consists of static members and cannot be subclassed.

e.g. System.Console, System.Math

3.1.11 Finalizers

Finalizer = class-only methods executed before the garbage collector reclaims memory for unreferenced objects.

class Class1
{
    ~Class1() // C# syntax for overriding Object's Finilize method
    {
        ...
    }
}

Details of Finalizer is in Chap12

3.1.12 Partial Types and Methods

Partial Types (e.g. partial class, partial struct) allow definition to be split into multiple files.

  • e.g. a partial class is autogenerated using template; that class is enhanced with hand-authored content in another file

Syntax Required:

  • Each participant (i.e. partial types) require partial declaration
  • All participants cannot have conflicting member (i.e. same paramter, same return)
  • Partial types are resolved at compile time.

e.g. partial type in different files

// auto-generated file - PaymentFormGen.cs
partial class PaymentForm {...}

// hand-authored - PaymentForm.cs
partial class PaymentForm {...}

Partial methods

A partial type can contain partial methods. So autogenerated partial type can provide customizable hooks for manual change

Syntax of partial methods:

  • consists of 2 part: a definition (in auto-generated file) and an implementation (in hand-authored file)
  • If implementation is not provided, definition of the partial method is compiled anyway.
  • Partial methods is void, and implicitly private, cannot include out parameter
// In auto-generated file e.g. PartialFileGen.cs
partial class PaymentForm
{
    ...
    partial void ValidatePayment(decimal amount); // Definition
}

// In hand-authored file
partial class PaymentForm
{
    ...
    partial void ValidatePayment(decimal amount) // implementation
    {
        if (amount > 100)
        ...
    }
}

Extended partial methods (C# 9)

TODO: Omit

3.1.13 The nameof operator

  • nameof operator returns name of any symbol (type, memeber, variable, etc.) as a string
  • Advantage of nameof: static type checking, e.g. if rename the symbol, all references will be renamed
int count = 123;
string name = nameof(count); // name is "count"

3.2 Inheritance

Subclass inherit from Superclass

e.g. define class inheritance

// Superclass
public class Asset
{
    public string Name;
}

// Subclass
public class Stock : Asset
{
    public long SharesOwned;
}
public class House : Asset
{
    public decimal Mortgage;
}

e.g. use classes

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

3.2.1 Polymorphism

Polymorphism (多态): References (e.g. class) are polymorphic (i.e. a variable of type x can refer to an object that subclasses x)

  • As all subclasses have field/properties/methods of superclass, so it's safe
// Define a method that try to access superclass
public static void Display (Asset asset)
{
    System.Console.WriteLine(asset.Name)
}

// As Stock is subclass of Asset, Display can access it
Stock msft = new Stock ...;
House mansion = new House ...;

Display(msft); // msft is subclass of asset
Display(mansion); // mansion is subclass of asset

3.2.2 Casting and Reference Conversions

An object reference can be:

  • Implicitly upcast to a base class (superclass) reference
  • Explicitly downcast to a subclass reference

TODO: Omit remaining content

Upcasting

Downcasting

The as operator

The is operator

Introducing a pattern variable

3.2.3 Virtual Function Members

  • Virtual Functions: A function marked as virtual can be overriden by subclasses for more specific implementation (Note: it also has its own implementation)
  • Who be declared virtual? ANS: Methods, properties, indexers, events
// Define a virtual method to be overridden
public class Asset
{
    public string Name;
    public virtual decimal Liability => 0; // Expression-bodied properties
}

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

// Initialization of overridden field
House mansion = new House { Name = "McMansion", Mortgage = 250000 };
Asset a = mansion;
Console.WriteLine(a.Liability); // 250000

Covariant return types (C# 9)

TODO: Omit

3.2.4 Abstract Classes and Abstract Members

  • Abstract class: a class declared as abstract can never be instantiated. Only its concrete subclasses can be instantiated.
  • Abstract classes have abstract members. They are like virtual members (e.g. functions, properties), but without a default implementation.
    • Implementation is provided by its concrete subclasses, unless subclasses are also abstract
public abstract class Asset
{
    // Note empty implementation
    public abstract decimal NetValue { get; }
}

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

    // Override abstract
    public override decimal NetValue => CurrentPrice * SharesOwned;
}

3.2.5 Hiding Inherited Members

  • When base class and subclass are defined with identical members, member in subclass hide the member in superclass
  • When encountered, compiler generates a warning and then resolves the ambiguity as follow:
    • References to method Counter in superclass A (at compile time) bind to A.Counter
    • References to method Counter in subclass B (at compile time) bind to B.Counter
    • Counter in B is said to hide the Counter filed in class A
  • Solution for deliberate hiding, apply new modifier to member in subclass. It only suppress compiler warning, not fix problem
public class A { public int Counter = 1; }
public class B : A { public new int Counter = 2; }

new vs override

  • new only hide base class's method. You can still refer to using A.Method
  • override will change base class's method
public class BaseClass
{
    public virtual void Foo()
    // Will be overriden or hidden
    {
        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");
    }
}

// Show Difference
Overrider over = new Overrider();
BaseClass b1 = over;
over.Foo(); // Overrider.Foo
b1.Foo();   // Overrider.Foo, changed base's implementation

Hider h = new Hider();
BaseClass b2 = h;
h.Foo();    // Hider.Foo
b2.Foo();   // BaseClass.Foo

3.2.6 Sealing Functions and Classes

sealed keyword: used to seal implementation of an overriden function method to prevent it from being overriden in further subclasses.

  • Both functions and classes can be sealed
  • Sealing a class is more common
  • We cannot seal a hidden member
public class Asset
{
    public string Name;
    public virtual decimal Liability => 0;
}

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

3.2.7 The base Keyword

3.2.8 Constructors and Inheritance

3.2.9 Overloading and Resolution

3.3 The object Type

  • object (System.object) is the base class for all types
  • Any type can be upcast to object

e.g. designing a general-purpose stack (栈, a LIFO data structure) can store 10 objects

public class Stack
{
    int position;
    object[] data = new object[10];

    // We can push or pop any type to and from Stack
    public void Push (object obj)
    {
        data[position++] = obj;
    }
    public object Pop()
    {
        return data[--position];
    }
}
  • object is a reference type, as result of being a class.
  • type unification: Any subclass can be cast from and to object. e.g. below
stack.Push(3);
int three = (int) stack.Pop(); // cast from object using type unification

3.3.1 Boxing and Unboxing

Boxing & Unboxing:

  • Boxing = act of converting a value-type instance to a reference-type instance. Reference type can be object class or interface
  • Unboxing = (reverse operation) casting object instance back to original value type
    • Unboxing requires explicit cast: CLR check whether proposed value type match actual object type, and throw InvalidCastException if check fails.
int x = 9
object obj = x; // Box the int
int y = (int) obj; // Unbox the int

e.g. unboxing checking

object obj = 9;
long x = (long) obj;  // InvalidCastException

object obj = 3.5;
int x = (int) (double) obj; // (double) perform unboxing, (int) perfrom conversion

Copying semantic of boxing and unboxing

  • Boxing copies value-type instance into the new object (object or instance)
  • Unboxing copies content of object into a value type instance.
  • So, there is no reference copy, hence changing value after Boxing/Unboxing won't affect boxed/unboxed copy
int i = 3;
object boxed = i; // instance copied into new object
i = 5;
Console.WriteLine(boxed); // 3, as instanced already copied, no change

3.3.2 Static and Runtime Type Checking

C# program will be performed 2 kings of type checking:

  • Static type checking: enable compiler to verify correctness of program without running; (e.g. int x = "5")
  • Runtime type checking: performed by CLR when downcast via a reference conversion or unboxing
    • It's possible as each object on heap has a little type token, which can be retrieved by calling GetTyep method of object

e.g. Runtime type checking

object y = "5";
int z = (int) y; // Runtime error, downcast failed

3.3.3 The GetType Method and typeof Operator

All C# types/objects at runtime has a instance of System.Type. 2 ways to get:

  1. Call .GetType() on instance: evaluated at runtime
  2. Use typeof() on a type name: evaluated at compile time. hence, type information of a type is static

System.Type instance has many properties; e.g. name, assembly, base type, etc.

e.g. properties of System.Type instances

Point p = new Point();
Console.WriteLine(p.GetType().Name); // Point
Console.WriteLine(p.GetType() == typeof(Point));

Console.WriteLine(typeof(Point).Name); // Point
...

3.3.4 The ToString Method

Every C# object has ToString method to return default textual representation of type instance.

  • For all built-in types, ToString method is overridden accordingly
  • We should also override ToString method for our custom types

e.g. ToString method on built-in type

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

e.g. override ToString method of custome type

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

public class Panda
{
    public string Name;
    public override string ToString() => Name;
}

3.3.5 Object Member Listing

object class has many different members, not only GetType and ToString, here is all members

public class Object
{
    public Object();
    public extern Type GetType();
    public virtual bool Equal(object obj);
    public static bool Equals(object objA, object objB);
    public static bool ReferenceEquals(object objA, object objB);
    public virtual int GetHashCode();
    public virtual string ToString();
    protected virtual void Finalize();
    protected extern object MemberwiseClone();
}

3.4 Structs

3.5 Access Modifiers

3.6 Interfaces

Interface = Similar as class, but only specifies behaviour and does not hold state (data)

  • Interface only defines functions and has no fields
  • Interface members (i.e. functions) are implicitly abstract
  • Inheritance vs Interface:
    • A class (or struct) can implement multiple interfaces.
    • A class can only inherit from ONE class. Struct cannot inherit at all.
public interface IEnumerator
{
    bool MoveNext();
    object Current { get; }
    void Rest();
}

Declaration & Implementation of interface (e.g. shown above):

  • Interface declaration provides no implementation of method, as they are implicitly abstract.
  • Methods will be implemented by classes and structs
  • An interface can only contain function (i.e. methods, properties, events, indexers, detail in chap 3.1)
  • Interface members implicitly public and cannot declare an access modifier. Hence, class implementing an interface means all implemented methods are public (shown below)
internal class Countdown : IEnumerator
{
    int count = 11;
    public bool MoveNext() => count-- > 0;
    public object Current => count;
    public void Reset() { throw new NotSupportedExceptions(); }
}

// public methods within internal class CountData can be called by casting it to the interface
public static class Util
{
    public static object GetCountDown() => new CountDown();
}
IEnumerator e = (IEnumerator)Util.GetCountDown();
e.MoveNext(); // this won't work if IEnumerator is also internal

3.6.1 Extending an Interface

Interfaces can derive from other interfaces:

  • extended (child) interface "inherits" all members of parent (these members also needed to be implemented)

e.g. extending interface

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

3.6.2 Explicit Interface Implementation

There 2 usage of Explicit Implementing interface member:

  1. resolve collision when implementing multiple interfaces
  2. Hide members that are highly specialized not for normal use case

Explicitly implemented member can be called by cast to its interface

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

// Explicit implementing interface
public class Widget : I1, I2
{
    public void Foo()
    {
        Console.WriteLine("Widget's implementation of I1.Foo");
    }

    int I2.Foo()
    // Explicitly implement I2's Foo method, to let 2 methods coexist in one class.
    {
        Console.WriteLine("Widgets's implementation of I2.Foo")
        return 42;
    }
}

// Call explicitly implemented member by casting to its interface
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

3.6.3 Implementing Interface Member Virtually

When a class implement interface, the implemented (implicitly) member is by default sealed (refer chap 3.2.6). Hence the class member must be marked virtual or abstract to be further subclassed (overriden).

public interface IUndoable { void Undo(); }
public class TextBox : IUndaable
// Base class implementing interface
{
    // implicitly implemented interface member will be overridden later
    public virtual void Undo() => Console.WriteLine("TextBox.Undo");
}
public class RichTextBox : TextBox
{
    public override void Undo() => Console.WriteLine("RichTextBox.Undo");
}

// Calling interface member through base class or interface calls subclass'
RichTextBox r = new RichTextBox();
r.Undo();               // RichTextBox.Undo
((IUndoable)r).Undo();  // RichTextBox.Undo
((TextBox)r).Undo();    // RichTextBox.Undo

3.6.4 Reimplementing an Interface in a Subclass

  • Even if base class has explicitly implemented member sealed, the subclass can reimplement (any) interface member.
  • Reimplementation hijacks a member implementation. It works regardless member is virtual or not in base class.

e.g. Subclass reimplement interface

public interface IUndoable { void Undo(); }

public class TextBox : IUndoable
// Base class implicitly implement member
{
    void IUndoable.Undo() => Console.WriteLine("TextBox.Undo");
}

// Subclass reimplement interface through interface calls
public class RichTextBox : TextBox, IUndoable
{
    public void Undo() => Console.WriteLine("RichTextBox.Undo");
}

// Calling reimplemented member
RichTextBox r = new RichTextBox();
r.Undo();                   // RichTextBox.UndoCase
((IUndoable)r).Undo();      // RichTextBox.UndoCase

Usage of reimplementation (TODO: Further understand the reason):

  • Overriding explicitly implemnted interface members.

3.6.5 Interfaces and Boxing

3.6.6 Default Interface Members

3.7 Enums

3.8 Nested Types

3.9 Generics

C# has 2 separate mechanism for writing codes that reusable across different types:

  1. inheritance: express reusability with base type
  2. generic: express reusability with "template" that has "placeholder" types

Pro of Generic over inheritance:

  • increase type safety
  • reduce casting and boxing

3.9.1 Generic Types

How it works:

  1. Generic type is declared with type parameters (placeholder type to be filled in)
  2. Consumer of the generic type will supply the type argument

e.g. generic type Stack<T> designed to stack instances of type T

// Define generic type Stack with single type parameter T
public class Stack<T>
{
    int position;
    T[] data = new T[100];
    public void Push (T obj) => data[position++] = obj;
    public T Pop() => data[--position];
}

3.9.2 Why Generic Exist

Generics make code reusable across different types.

Without Generic

To implement Stack<T> for different types (int, string, etc) without generic, there are 2 ways:

  1. Hardcode separate version of class for every required element type. (i.e. create different concrete classes IntStack, StringStack, etc.): it will create code duplication
  2. Generalize by creating a concrete class using object as element type
// Generalized concrete class using object
public class ObjectStack
{
    int position;
    object[] data = new object[10];
    public void Push (object obj) => data[position++] = obj;
    public object Pop() => data[--position];
}

Problem of generalized class:

  • Does not work as well as other specific classes
  • Require boxing and downcasting which cannot be checked at compile tim
ObjectStack stack = new ObjectStack();

stackPush("s");                 // Wrong type, but no error at runtime until now
int i = (int)stack.Pop();       // Downcast, create runtime error at this point

3.9.3 Generic Methods

Generic method declaration has type paramter within its signature

e.g. declare & call generic method

static void Swap<T> (ref T a, ref T b)
//  type parameter within method signature
{
    T temp = a;
    a = b;
    b = temp;
}

// Call generic method
int x = 5; int y =  10;
Swap(ref x, ref y);

// For more specific (no need to use type argument)
Swap<int> (ref x, ref y);
  • Generally, there is no need to supply type arguments to a generic method, as compiler can implicitly infer the type.

How can a method be classed as generic?

  • It must introduce type parameter (e.g. <T>)
  • Hence Pop() method in ObjectStack is not a generic method

Which construct can introduce type parameter <T>?

  • Can: Only in Methods and Types
  • Cannot: Property, indexers, events, fields, constructor, operator, etc.
    • They can only partake (参与) in any type parameter already declared by enclosing type (detailed in 3.9.4)

3.9.4 Declaring Type Parameters

Who can use type parameter for declaration

  • Type parameters <T> can be introduced in declaration of classes, structs, interfaces, delegates, and methods.
  • Other construct (e.g. properties), cannot introduce/create a type parameter. They can use (partake) one.
public struct Nullable<T>
{
    public T Value { get; } // property Value use type parameter
}

Multiple type parameter

A generic type or method can have multiple parameters:

class Dictionary<TKey, TValue> {...}

// Instantiate
Dictionary<int,string> myDict = new Dictionary<int,string> ();
// or
var myDict = new Dictionary<int, string>();

Overloaded generic methods

Generic type names and method names can be overloaded as long as number of type parameters is different.

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

3.9.5 typeof and Unbound Generic Types

  • No open generic (e.g. A<>) at runtime: Open generic type do not exist at runtime, as they will be closed during compilation (e.g. A<int>)
  • unbound generic at runtime: allowed purely as Type object
    • Specified by typeof operator
    • Multiple type args can be indicated by ,
class A<T> {}
class A<T1,T2> {}

Type a1 = typeof(A<>); // Unbound type (no type arguments in A<>)
Type a2 = typeof(A<,>); // Use commas to indicate multiple type arguments

// typeof can also specify closed type
Type a3 = type(A<int,int>);

3.9.6 The default Generic Value

  • Keyword to get the default value for a generic type parameter: default
  • Default value for
    • reference type: null
    • value type: result of bitwise-zeroing the value types' fields
      • New feature in C#7.1, We can omit type argument for cases in which the compiler to infer
static void Zap<T> (T[] array)
{
    for (int i = 0; i < array.Length; i++)
    {
        array[i] = default(T);
        // Can be replaced by following
        array[i] = default; // New feature in C#7.1
    }
}

3.9.7 Generic Constraints

Constraint used to a type parameter (<T>) to require more specific type argument

  • Can be applied in definition or declaration

List of possible constraints:

  • Base-class constraint: where T : <base-class>
    • specifies that type parameter must subclass a particular class
    • Hence, implicitly convert the type to class
  • Interface constraint: where T : interface
    • specifies the type paramter must implement interface
    • Hence, implicitly convert the type to interface
  • Reference-type constranit: where T : class
    • T must be reference type
  • Nullable reference type: where T : class?
  • Value-type constraint: where T : struct
  • Unmanaged constraint (C# 7.3): where T : unmanaged
    • T must be a simple value type or a struct that is (recursively) free of any reference types.
  • Parameterless constructor constraint: where T : new()
    • T must have a public parameterless constructor
  • Naked type constraint: where U : T
  • Non-nullable value type or (from C#8): where T : notnull

e.g. Generic Constraints

class SomeClass {}
interface Interface1 {}

class GenericClass<T,U> where T : SomeClass, Interface
                        where U : new()
// requires T to derive from SomecClass and implement Interface1, and requires U to provide parameterless constructor
{
    ...
}

e.g. base-class contraint and interface contraint

static T Max<T> (T a, T b) where T : IComparable<T>
{
    return a.CompareTo (b) > 0 ? a : b;
}

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

e.g. parameterless constructor constraint

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

e.g. naked type constraint

class Stack<T>
// method FilteredStack returns another Stack, containing only the sub
{
    Stack<U> FilteredStack<U>() where U : T
    {
        ...
    } 
}

TODO: Understand Naked type constraint (Not understood)

3.9.8 Subclassing Generic Types

a generic class can be further subclassed:

  • type parameters in subclass can be closed or remain open
  • subclass can add new type parameter
class Stack<T> {...}

// leave the base class's type parameter open
class SpecialStack<T> : Stack<T> {...}

// close the generic type parameter with concrete type
class IntStack : Stack<int> {...}

// Add new type arguments
class List<T> {...}
class KeyedList<T,TKey> : List<T> {...}

3.9.9 Self-Referencing Generic Declarations

A type can name itself as concrete type when closing a type argument

public interface IEquatable<T> { bool Equals (T obj); }

public class Balloon : IEquatable<Ballon>
// Ballon name itself as concrete type by closing type argument in IEquatable<>
{
    public string Color { get; set; }
    public int CC { get; set; }

    public bool Equals(Ballon b)
    {
        if (b == null) return false;
        return b.Color == Color && b.CC == CC;
    }
}

3.9.10 Static Data

For each closed type, static data is unique

  • Because, when a closed type (class) is created, its static data is then created along the closed type
  • i.e. for the same closed type (i.e. T is int), the static data is shared.
class Bob<T> { public static int Count; }

// For same closed type int, static data is unique
Console.WriteLine(++Bob<int>.Count); // 1
Console.WriteLine(++Bob<int>.Count); // 2

// For different closed type, static data is different
Console.WriteLine(++Bob<string>.Count); // 1
Console.WriteLine(++Bob<object>.Count);

3.9.11 Type Parameters and Conversions

TODO: Subchapter skipped

3.9.12 Covariance

TODO: Subchapter skipped

3.9.13 Contravariance

TODO: Subchapter skipped

3.9.14 C# Generics vs. C++ Templates

Common between C# Generics & C++ Templates:

  • Placeholder type of producer filled by the consumer. (A synthesis relationship btw producer (generics & template) and consumer)

Diff:

  • C# generics, producer types (e.g. List<T>) can be compiled into a library (mscorlib.dll) standalone. Because synthesis btw producer & consumer happen in runtime, not during compilation. (i.e. comopiler does not close generics)
  • C++ templates, synthesis is performed at compile time. Hence, C++ don't deploy template libraries like .dlls. C++ template exists as source code.

e.g. Max method written in C++

template <clss T> T Max (T a, T b)
{
    return a > b ? a : b;
}
  • code will be compiled separately for each value of T
  • For a particular T it will take > that's available to the T
  • Compilation will fail if a T does not support > operator

e.g. Max method written in C#

static T Max<T> (T a, T b) where T : IComparable<T> => a.CompareTo(b) > 0 ? a : b;
  • Max need to be compiled oncwe and work for all possible value of C

e.g. Wrong implementation in C#

static T Max <T> (T a, T b) => (a > b ? a : b);
  • Compilation fail as there is no single meaning for > across all T, as not every T has > operator