Classes

There are types and type members.
Class is a reference type.

class YourClassName
{
}

Preceding the keyword class,

  • Attributes and Class Modifiers
  • The non-nested class modifiers are public, internal, abstract, sealed, static, unsafe, and partial.

Following YourClassName,

  • Generic type parameters and constraints, a base class and interfaces

Within the braces,

  • Class members - methods, properties, indexers, events, fields, constructors, overloaded operators, nested types, a finalizer

The following sections enumerate each of the class members,

  • Fields

    • A field is a variable that is a member of a class or struct
    • Fields allow the following modifiers - static, public, internal, private, protected, new (inheritance modifier), unsafe, readonly, volatile
    • popular naming conventions for private fields - firstName or _firstName
    • readonly prevents a field from being modified after construction. It can be assigned only in its declaration or within the enclosing type’s constructor.
    • Field initialization is optional. An uninitialized field has a default value (0, ‘\0’, null, false). Field initializers run before constructors. A field initializer can contain expressions and call methods.
    • You can declare multiple fields of the same type in a comma separated list. Convenient way for all the fields to share the same attributes and field modifiers.
  • Constants

    • A constant is evaluated statically at compile time, and the compiler literally substitutes its value whenever used (rather like a macro in C++)
    • A constant can be bool, char, string, any of the built-in numeric types, or an enum type
    • Declared with the const keyword and must be initialized with a value
    • A constant can serve a similar role to a static readonly field, but it is much more restrictive - both in the types you can use and in field initialization semantics.
    • Another difference is that the evaluation of the constant occurs at compile time. In contrast, a static readonly field’s value can potentially differ each time the program is run. (static readonly DateTime StartupTime = DateTime.Now;)
    • A static readonly field is also advantageous when exposing to other assemblies a value that might change in a later version. Constants are baked in until you compile again.
    • Constants can also be declared local to a method
    • Nonlocal constants allow the following modifiers - public, internal, private, protected, and new (inheritance modifier).
  • Methods

    • A method performs an action in a series of statements
    • It can input data from the caller by specifying parameters
    • It can output data back to the caller by specifying a return type, and also via ref/out parameters
    • A method’s signature must be unique within the type. Signature comprises its name and parameter types in order
    • Methods allow the following modifiers - static, public, internal, private, protected, new, virtual, abstract, override, sealed, partial, unsafe, extern, async
    • Expression-bodied methods - A fat arrow replaces the braces and return keyword
      • int Foo (int x) => x * 2;
      • Expression-bodied functions can also have a void return type
        • void Foo (int x) => Console.WriteLine (x);
    • Local methods - You can define a method within another method
      • Local methods can appear within other function kinds, such as property accessors, constructors, and so on. You can even put local methods inside other local methods, and inside lambda expressions that use a statement block
      • Local methods cannot be overloaded
    • Static local methods - Adding the static modifier to a local method (from C# 8) prevents it from seeing the local variables and parameters of the enclosing method
    • Any methods that you declare in top-level statements are treated as local methods. This means that (unless marked as static) they can access the variables in the top-level statements
    • A type can overload methods (define multiple methods with the same name) as long as the signatures are different. Whether a parameter is pass-by-value or pass-by-reference is also part of the signature, However, Foo(ref int) and Foo(out int) cannot coexist together.
  • Instance Constructors

    • Constructors run initialization code on a class or struct. A constructor is defined like a method, except that the method name and return type are reduced to the name of the enclosing type
    • Instance constructors allow the following modifiers - public, internal, private, protected, unsafe, extern
    • Single-statement constructors can also be written as expression-bodied members - public Panda (string n) => name = n;
      • If a parameter name (or any variable name, for that matter) conflicts with a field name, you can disambiguate by prefixing the field with a this reference - public Panda (string name) => this.name = name;
    • Overloading constructors - A class or struct may overload constructors. To avoid code duplication, one con structor can call another, using the this keyword
      public Wine (decimal price) => Price = price;
      public Wine (decimal price, int year) : this (price) => Year = year;
    • When one constructor calls another, the called constructor executes first
    • You can pass an expression into another constructor
      public Wine (decimal price, DateTime year) : this (price, year.Year) { }
      • The expression can access static members of the class but not instance members. This is enforced because the object has not been initialized by the constructor at this stage, so any methods that you call on it are likely to fail
    • Implicit parameterless constructors - For classes, the C# compiler automatically generates a parameterless public constructor if and only if you do not define any constructors. However, as soon as you define at least one constructor, the parameterless constructor is no longer automatically generated
    • Field initializations occur before the constructor is executed, and in the declaration order of the fields
    • Nonpublic constructors - Constructors need not be public. A common reason to have a nonpublic construc tor is to control instance creation via a static method call. The static method could be used to return an object from a pool rather than creating a new object, or to return various subclasses based on input arguments
  • Deconstructors

    • A deconstructor (also called a deconstructing method) acts as an approximate oppo site to a constructor - whereas a constructor typically takes a set of values (as parameters) and assigns them to fields, a deconstructor does the reverse and assigns fields back to a set of variables
    • A deconstruction method must be called Deconstruct and must have one or more out parameters
    • 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;
          }
      }
    • The following special syntax calls the deconstructor
      var rect = new Rectangle(3, 4);
      (float width, float height) = rect; // Deconstruction
      Console.WriteLine(width + " " + height); // 3 4
    • The second line is the deconstructing call. It creates two local variables and then calls the Deconstruct method. Our deconstructing call is equivalent to the following
      float width, height; 
      rect.Deconstruct (out width, out height);
      //or
      rect.Deconstruct (out var width, out var height);
    • Deconstructing calls allow implicit typing, so we could shorten our call to this
      (var width, var height) = rect;
      //or simply
      var (width, height) = rect;
    • We can use C#’s discard symbol _ if uninterested in one or more variables
      csharp var (_, height) = rect;
    • If the variables into which you’re deconstructing are already defined, omit the types altogether
      float width, height; 
      (width, height) = rect;
      This is called a deconstructing assignment. You can use a deconstructing assignment to simplify your class’s constructor
      public Rectangle (float width, float height) => (Width, Height) = (width, height);
    • You can offer the caller a range of deconstruction options by overloading the Deconstruct method
    • The Deconstruct method can be an extension method. This is a useful trick if you want to deconstruct types that you did not author
    • From C# 10, you can mix and match existing and new variables when deconstructing
      double x1 = 0; 
      (x1, double y2) = rect;
  • Object Initializers

    • To simplify object initialization, any accessible fields or properties of an object can be set via an object initializer directly after construction
      public class Bunny
      {
          public string Name;
          public bool LikesCarrots, LikesHumans;
          public Bunny () {}
          public Bunny (string n) => Name = n;
      }
       
      //Using object initializers, you can instantiate Bunny objects as follows
      //Note parameterless constructors can omit empty parentheses 
      Bunny b1 = new Bunny { Name="Bo", LikesCarrots=true, LikesHumans=false }; 
      Bunny b2 = new Bunny ("Bo") { LikesCarrots=true, LikesHumans=false };
    • The code to construct b1 and b2 is precisely equivalent to the following
      Bunny temp1 = new Bunny();    // temp1 is a compiler-generated name
      temp1.Name = "Bo";
      temp1.LikesCarrots = true;
      temp1.LikesHumans = false;
      Bunny b1 = temp1;
       
      Bunny temp2 = new Bunny ("Bo");
      temp2.LikesCarrots = true;
      temp2.LikesHumans = false;
      Bunny b2 = temp2;
       
      //The temporary variables are to ensure that if an exception 
      //is thrown during initialization, you can’t end up with 
      //a half-initialized object.
    • Object Initializers Versus Optional Parameters
      • Optional parameters have two drawbacks. The first is that while their use in constructors allows for read-only types, they don’t (easily) allow for nondestructive mutation
      • The second drawback of optional parameters is that when used in public libraries, they hinder backward compatibility. This is because the act of adding an optional parameter at a later date breaks the assembly’s binary compatibility with existing consumers.
      • The difficulty is that each optional parameter value is baked into the calling site
      • A final consideration is the effect of constructors on subclassing. Having multiple constructors with long param eter lists makes subclassing cumbersome; therefore, it can help to keep constructors to a minimum in number and complexity and use object initializers to fill in the details
  • The this reference

    • The this reference refers to the instance itself
    • In the following example, the Marry method uses this to set the partner’s mate field
      public class Panda
      {
          public Panda Mate;
          public void Marry (Panda partner)
          {
            Mate = partner;
            partner.Mate = this;
          }
      }
    • The this reference also disambiguates a local variable or parameter from a field
      public class Test
      {
         string name;
         public Test (string name) => this.name = name;
      }
    • The this reference is valid only within nonstatic members of a class or struct
  • Properties

    • Properties look like fields from the outside, but internally they contain logic, like methods do. A property is declared like a field but with a get/set block added.
    • get and set denote property accessors. The get accessor runs when the property is read. It must return a value of the property’s type. The set accessor runs when the property is assigned. It has an implicit parameter named value of the property’s type that you typically assign to a private field
    • public class Stock
      {
          decimal currentPrice;           // The private "backing" field
          public decimal CurrentPrice     // The public property
          {
        	get { return currentPrice; }
        	set { currentPrice = value; }
          }
      }
    • Although properties are accessed in the same way as fields, they differ in that they give the implementer complete control over getting and setting its value. This control enables the implementer to choose whatever internal representation is needed without exposing the internal details to the user of the property. They promote encapsulation.
    • Properties allow the following modifiers - static, public, internal, private, protected, new, virtual, abstract, override, sealed, unsafe, extern
    • A property is read-only if it specifies only a get accessor, and it is write-only if it specifies only a set accessor. Write-only properties are rarely used.
    • A property can also be computed from other data
        decimal currentPrice, sharesOwned;
         public decimal Worth
         {
           get { return currentPrice * sharesOwned; }
         }
      • Expression-bodied properties
        public decimal Worth => currentPrice * sharesOwned; //read-only
         
        public decimal Worth
         {
        	get => currentPrice * sharesOwned;
        	set => sharesOwned = value / currentPrice;
         }
    • Automatic properties - The most common implementation for a property is a getter and/or setter that sim ply reads and writes to a private field of the same type as the property. An automatic property declaration instructs the compiler to provide this implementation. The compiler automatically generates a private backing field of a compiler generated name that cannot be referred to. The set accessor can be marked private or protected if you want to expose the property as read-only to other types.
      public class Stock
        {
        	  ...
        	  public decimal CurrentPrice { get; set; }
        }
      • Property initializers
        • You can add a property initializer to automatic properties, just as with fields - public decimal CurrentPrice { get; set; } = 123;. This gives CurrentPrice an initial value of 123.
        • Properties with an initializer can be read-only - public int Maximum { get; } = 999;
        • Just as with read-only fields, read-only automatic properties can also be assigned in the type’s constructor. This is useful in creating immutable (read-only) types
    • Init-only setters
      • From C# 9, you can declare a property accessor with init instead of set
        public class Note
         {
           public int Pitch    { get; init; } = 20;
           public int Duration { get; init; } = 100;
         }
        • These init-only properties act like read-only properties, except that they can also be set via an object initializer
          var note = new Note { Pitch = 50 };
        • After that, the property cannot be altered
          note.Pitch = 200; // Error – init-only setter!
        • Init-only properties cannot even be set from inside their class, except via their property initializer, the constructor, or another init-only accessor
        • The alternative to init-only properties is to have read-only properties that you populate via a constructor
          public class Note
             {
             	  public int Pitch    { get; }
             	  public int Duration { get; }
             	  
             	  public Note (int pitch = 20, int duration = 100)
             	  {
             			Pitch = pitch; Duration = duration;
             	  }
             }	  
      • Should the class be part of a public library, this approach makes versioning difficult, in that adding an optional parameter to the constructor at a later date breaks binary compatibility with consumers (whereas adding a new init-only property breaks nothing)
      • Init-only properties have another significant advantage, which is that they allow for nondestructive mutation when used in conjunction with records
      • Just as with ordinary set accessors, init-only accessors can provide an implementation
        public class Note
          {
          	readonly int _pitch;
          	public int Pitch { get => _pitch; init => _pitch = value; }
          	...
        • Notice that the _pitch field is read-only - init-only setters are permitted to modify readonly fields in their own class. (Without this feature, _pitch would need to be writable, and the class would fail at being internally immutable.)
        • Changing a property’s accessor from init to set (or vice versa) is a binary breaking change - anyone that references your assembly will need to recompile their assembly. This should not be an issue when creating wholly immutable types, in that your type will never require properties with a (writable) set accessor.
    • CLR property implementation
      • C# property accessors internally compile to methods called get_XXX and set_XXX
            public decimal get_CurrentPrice {...} 
            public void set_CurrentPrice (decimal value) {...}
        • An init accessor is processed like a set accessor, but with an extra flag encoded into the set accessor’s “modreq” metadata
  • Indexers

    • Indexers provide a natural syntax for accessing elements in a class or struct that encapsulate a list or dictionary of values. Indexers are similar to properties but are accessed via an index argument rather than a property name.
    • The string class has an indexer that lets you access each of its char values via an int index
            string s = "hello";
        	Console.WriteLine (s[0]); // 'h'
        	Console.WriteLine (s[3]); // 'l'
    • The syntax for using indexers is like that for using arrays, except that the index argument(s) can be of any type(s)
    • Indexers have the same modifiers as properties and can be called null-conditionally by inserting a question mark before the square bracket
        	  string s = null; 
        	  Console.WriteLine (s?[0]); // Writes nothing; no error.
    • Implementing an indexer
      • To write an indexer, define a property called this, specifying the arguments in square brackets
        class Sentence
          {
          	string[] words = "The quick brown fox".Split();
          	
          	public string this [int wordNum] // indexer
          	{
          		get { return words [wordNum];  }
          		set { words [wordNum] = value; }
          	}
          }
      • Here’s how we could use this indexer
            Sentence s = new Sentence(); 
            Console.WriteLine (s[3]); // fox 
            s[3] = "kangaroo"; 
            Console.WriteLine (s[3]); // kangaroo
      • A type can declare multiple indexers, each with parameters of different types. An indexer can also take more than one parameter
        public string this [int arg1, string arg2] 
        {
          	get { ... }  set { ... }
        }
      • If you omit the set accessor, an indexer becomes read-only, and you can use expression-bodied syntax to shorten its definition
            public string this [int wordNum] => words [wordNum];
    • Indexers internally compile to methods called get_Item and set_Item
          public string get_Item (int wordNum) {...} 
          public void set_Item (int wordNum, string value) {...}
    • Using indices and ranges with indexers
      • You can support indices and ranges in your own classes by defining an indexer with a parameter type of Index or Range
            public string this [Index index] => words [index]; 
            public string[] this [Range range] => words [range];
      • This then enables the following
            Sentence s = new Sentence(); 
            Console.WriteLine (s [^1]); // fox 
            string[] firstTwoWords = s [..2]; // (The, quick)
  • Primary Constructors (C#12)

    • From C# 12, you can include a parameter list directly after a class (or struct) declaration
      class Person (string firstName, string lastName)
        {
        	public void Print() => Console.WriteLine (firstName + " " + lastName);
        }
    • We can instantiate our class as follows
          Person p = new Person ("Alice", "Jones"); 
          p.Print(); // Alice Jones
    • Primary constructors are useful for prototyping and other simple scenarios. The alternative would be to define fields and write a constructor explicitly
    • The constructor that C# builds is called primary because any additional construc tors that you choose to (explicitly) write must invoke it. This ensures that primary constructor parameters are always populated.
    • C# also provides records, records also support primary constructors; however, the compiler takes an extra step with records and generates (by default) a public init-only property for each primary constructor parameter.
    • Primary constructors displace the default parameterless constructor that C# would otherwise generate.
    • A primary constructor’s parameters do not disappear out of scope and can be subsequently accessed from anywhere within the class, for the life of the object
    • (Tbh, not really worth it) It’s mainly for reducing boilerplate
  • Static Constructors

    • A static constructor executes once per type rather than once per instance. A type can define only one static constructor, and it must be parameterless and have the same name as the type
      class Test
        {
        	  static Test() { Console.WriteLine ("Type Initialized"); }
        }
    • The runtime automatically invokes a static constructor just prior to the type being used. Two things trigger this - Instantiating the type and Accessing a static member in the type
    • The only modifiers allowed by static constructors are unsafe and extern
    • If a static constructor throws an unhandled exception, that type becomes unusable for the life of the application
    • From C# 9, you can also define module initializers, which execute once per assembly (when the assembly is first loaded). To define a module initializer, write a static void method and then apply the [ModuleInitializer] attribute to that method
    • Static field initializers run just before the static constructor is called. If a type has no static constructor, static field initializers will execute just prior to the type being used - or anytime earlier at the whim of the runtime
    • Static field initializers run in the order in which the fields are declared
  • Static Classes - A class marked static cannot be instantiated or subclassed, and must be composed solely of static members. The System.Console and System.Math classes are good examples of static classes

  • Finalizers

    • Finalizers are class-only methods that execute before the garbage collector reclaims the memory for an unreferenced object. The syntax for a finalizer is the name of the class prefixed with the ~ symbol
      class Class1
        {
        	  ~Class1()
        	  {
        		    ...
        	  }
        }
    • This is actually C# syntax for overriding Object’s Finalize method, and the compiler expands it into the following method declaration
        protected override void Finalize()
        {
        	  ...
        	  base.Finalize();
        }
    • You can write single-statement finalizers using expression-bodied syntax
          ~Class1() => Console.WriteLine ("Finalizing");
  • Partial Types and Methods

    • Partial types allow a type definition to be split - typically across multiple files. A common scenario is for a partial class to be autogenerated from some other source (such as a Visual Studio template or designer), and for that class to be augmented with additional hand-authored methods
    • Each participant must have the partial declaration
    • Participants cannot have conflicting members. A constructor with the same param eters, for instance, cannot be repeated. Partial types are resolved entirely by the compiler, which means that each participant must be available at compile time and must reside in the same assembly
    • You can specify a base class on one or more partial class declarations, as long as the base class, if specified, is the same. In addition, each participant can inde pendently specify interfaces to implement
    • The compiler makes no guarantees with regard to field initialization order between partial type declarations
    • A partial type can contain partial methods. These let an autogenerated partial type provide customizable hooks for manual authoring
    • A partial method consists of two parts - a definition and an implementation. The definition is typically written by a code generator, and the implementation is typically manually authored
    • Partial methods must be void and are implicitly private. They cannot include out parameters
    • Extended partial methods (from C# 9) are designed for the reverse code generation scenario, where a programmer defines hooks that a code generator implements. An example of where this might occur is with source generators, a Roslyn feature that lets you feed the compiler an assembly that automatically generates portions of your code. A partial method declaration is extended if it begins with an accessibility modifier. Extended partial methods must have implementations. Because they cannot melt away, extended partial methods can return any type and can include out parameters
  • The nameof operator

    • The nameof operator returns the name of any symbol (type, member, variable, and so on) as a string
          int count = 123; 
          string name = nameof (count); // name is "count"
    • Its advantage over simply specifying a string is that of static type checking. Tools such as Visual Studio can understand the symbol reference, so if you rename the symbol in question, all of its references will be renamed, too.
    • To specify the name of a type member such as a field or property, include the type as well. This works with both static and instance members
          string name = nameof (StringBuilder.Length);
    • This evaluates to Length. To return StringBuilder.Length, you would do this
          nameof (StringBuilder) + "." + nameof (StringBuilder.Length);

Inheritance

A class can inherit from another class to extend or customize the original class. Inheriting from a class lets you reuse the functionality in that class instead of building it from scratch. A class can inherit from only a single class but can itself be inherited by many classes.

	public class Asset
	{
	  public string Name;
	}
	
	public class Stock : Asset
	{
		public long SharesOwned;
	}
	
	public class House : Asset
	{
		public decimal Mortgage;
	}
	
	//The derived classes, Stock and House, inherit the `Name` field from the base class, Asset.
  • Polymorphism - References are polymorphic. This means a variable of type x can refer to an object that subclasses x

  • Casting and Reference Conversions

    • An object reference can be
      • Implicitly upcast to a base class reference
      • Explicitly downcast to a subclass reference
    • Upcasting and downcasting between compatible reference types performs reference conversions - a new reference is (logically) created that points to the same object. An upcast always succeeds; a downcast succeeds only if the object is suitably typed
    • Upcasting
      • An upcast operation creates a base class reference from a subclass reference
          	Stock msft = new Stock();
          	Asset a = msft;              // Upcast
      • After the upcast, variable a still references the same Stock object as variable msft. The object being referenced is not itself altered or converted
      • Although a and msft refer to the identical object, a has a more restrictive view on that object. Trying to access will give compile-time error
    • 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
      • As with an upcast, only references are affected - not the underlying object. A down cast requires an explicit cast because it can potentially fail at runtime
      • If a downcast fails, an InvalidCastException is thrown
    • 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
        • This is useful when you’re going to subsequently test whether the result is null
              if (s != null) Console.WriteLine (s.SharesOwned);
        • Without such a test, a cast is advantageous, because if it fails, a more helpful exception is thrown
    • The is operator
      • The is operator tests whether a variable matches a pattern. C# supports several kinds of patterns, the most important being a type pattern, where a type name follows the is keyword
          	  if (a is Stock) 
          		  Console.WriteLine (((Stock)a).SharesOwned);
      • Introducing a pattern variable
        • You can introduce a variable while using the is operator
              if (a is Stock s) 
            	  Console.WriteLine (s.SharesOwned);
        • This is equivalent to the following
              Stock s; 
              if (a is Stock) 
              { 
            	  s = (Stock) a; 
            	  Console.WriteLine (s.SharesOwned); 
              }
        • The variable that you introduce is available for “immediate” consumption, so the following is legal
          if (a is Stock s && s.SharesOwned > 100000) 
              Console.WriteLine ("Wealthy");
        • And it remains in scope outside the is expression, allowing this
          if (a is Stock s && s.SharesOwned > 100000)
              Console.WriteLine ("Wealthy");
          else
              s = new Stock();   // s is in scope
           
            Console.WriteLine (s.SharesOwned);  // Still in scope
  • Virtual Function Members

  • Abstract Classes and Abstract Members

  • Hiding Inherited Members

  • Sealing Functions and Classes

  • The base Keyword

  • Constructors and Inheritance

  • Overloading and Resolution


The object Type

  • Boxing and Unboxing
  • Static and Runtime Type Checking
  • The GetType Method and typeof Operator
  • The ToString Method
  • Object Member Listing

Structs

  • Struct Construction Semantics
  • Read-Only Structs and Functions
  • Ref Structs

Access Modifiers

To promote encapsulation, a type or type member can limit its accessibility to other types and other assemblies by adding an access modifier to the declaration,

publicFully accessible. This is the implicit accessibility for members of an enum or interface.
internalAccessible only within the containing assembly or friend assemblies. This is the default accessibility for non-nested types
privateAccessible only within the containing type. This is the default accessibility for members of a class or struct
protectedAccessible only within the containing type or subclasses
protected internalThe union of protected and internal accessibility. A member that is protected internal is accessible in two ways
private protectedThe intersection of protected and internal accessibility. A member that is private protected is accessible only within the containing type, or from subclasses that reside in the same assembly (making it less accessible than protected or internal alone)
file (from C# 11)Accessible only from within the same file. Intended for use by source generators. This modifier can be applied only to type declarations
  • Friend Assemblies
  • Accessibility Capping
  • Restrictions on Access Modifiers

Interfaces

  • Extending an Interface
  • Explicit Interface Implementation
  • Implementing Interface Members Virtually
  • Reimplementing an Interface in a Subclass
  • Interfaces and Boxing
  • Default Interface Members
  • Static Interface Members
  • Writing a Class Versus an Interface

Enums

  • Enum Conversions
  • Flags Enums
  • Enum Operators
  • Type-Safety Issues

Nested Types


Generics

  • Generic Types
  • Why Generics Exist
  • Generic Methods
  • Declaring Type Parameters
  • typeof and Unbound Generic Types
  • The default Generic Value
  • Generic Constraints
  • Subclassing Generic Types
  • Self-Referencing Generic Declarations
  • Static Data
  • Type Parameters and Conversions
  • Covariance
  • Contravariance
  • C# Generics Versus C++ Templates

Delegates


Events


Lambda Expressions


Anonymous Methods


try Statements and Exceptions


Enumeration and Iterators


Nullable Value Types


Nullable Reference Types


Extension Methods


Anonymous Types


Tuples


Records


Patterns


Attributes


Caller Info Attributes


Dynamic Binding


Operator Overloading


Static Polymorphism


Unsafe Code and Pointers


Preprocessor Directives


XML Documentation


Enumeration


The ICollection and IList Interfaces


The Array Class


Lists, Queues, Stacks, and Sets


Dictionaries


Customizable Collections and Proxies


Immutable Collections


Frozen Collections


Plugging in Equality and Order


IDisposable, Dispose, and Close


Automatic Garbage Collection


Finalizers


How the GC Works


Managed Memory Leaks


Weak References


Threading


Tasks


Principles of Asynchrony


Asynchronous Functions in C#


Asynchronous Patterns


Obsolete Patterns