C# Encapsulation Syntax
Encapsulation of a Circle Example
Encapsulation is one of the main OOP concepts. It is summarised here, using a Circle
class, which is kept simple from an algorithmic perspective, so that the logic does not obscure the syntax. We use it to illustrate many general object-oriented programming features and syntax, as found in most OO languages. We also illustrate some .NET & C# specialities, like properties and indexers (special kind of properties), which are relatively rare in other OO languages.
PREREQUISITES — You should already…
- have learned the fundamentals of C# syntax;
- understand
static
methods andstatic
classes; - understand static function definitions, returns and overloading;
- understand default parameters, named arguments and pass-by-reference;
- understand references, scope, namespaces;
- understand expressions, operators, precedence and operator association.
Encapsulation Concepts
The most fundamental concept in object-orientated programming (OOP), is encapsulation. This allows one to wrap” the functionality of some programmatic design, which may be concrete or abstract, into a type called a class. As analogy, a class acts like a blueprint from which many instances (more popularly called objects) of the class can be created — much like several houses may be built from the same plan. This analogy applies to any type. Encapsulation, together with inheritance and polymorphism, form the backbone of OOP. Most OOP languages also allow for composition and aggregation as alternatives, or supplementary, to inheritance.
Value Types and Reference Types
The .NET CLR (Common Language Runtime) and CTS (Common Type System), part of the CLI (Common Language Infrastructure), support the concepts of value types and reference types. This was a design choice to enhance efficiency and performance. Most types are reference types, created with the class
keyword, and inherit from System.Object
. In the .NET Framework, only a few types, in particular the numeric types, are implemented as value types. In C#, they can be created with the struct
keyword, and they automatically inherit from a special System.ValueType
class.
Value types can be wrapped in a Nullable<T>
type. This will allow one to assign null
to them, just like references types, for situations where convenience overrules efficiency.
General concepts that involve encapsulation in object-oriented programming, with particular reference to C# syntax and specifics, include:
a) the data an object manages (its state);
b) the manner in which it can be initialised (constructors);
c) the behaviours the object supplies (methods);
d) control of access to the various parts (access control); and
e) optionally, destruction of an object (finalisation).
Language elements within a class specification are collectively called members. Members are further categorised as fields, symbolic constants, static fields, constructors, static constructors, destructors or finalisers, instance methods, static methods, virtual methods, properties, indexers, and nested types.
Not all these features are implemented on all classes in production, which means the Circle
class used as vehicle here, becomes heavily over-complicated when measured against its conceptual simplicity. Remember, this example is about syntax, not good design.
State
The state of an object is effectively the set of values, managed by, and contained within an object, that represents the object's memory image at a given time. It is normally implemented as a series of data members, often also called fields. From a design perspective, we want some data members to be constant (read-only or symbolic constant), while some should not be replicated, i.e., exist as a single copy that ‘belongs’ to the class, and is ‘shared’ by all objects.
instance variables — Also called fields, which will only exist when a new object is created. Thus each object gets its own copy (or instance), like the radius of a circle. They are accessed via an object's reference, e.g.,
MyObj.MyField
. In practice, instance variables are almost always givenprivate
access to protect their integrity, and we write either accessor methods (sometimes called ‘getters and setters’) to get/set their values in a controlled fashion; or we write properties to do the same, except we provide the client code with the illusion that it is working with apublic
data member. For flexibility, we may write both accessors and properties.static variables — Also called static fields or shared variables, of which only one copy will ever exist. We can think of these as belonging to the class, and not to individual object instances. They are accessed via the class name, e.g.,
MyClass.StaticVar
. Static accessors methods (getters & setters) can be written to provide controlled access to them; orstatic
properties; or both.symbolic constants — Named read-only values created with the
const
modifier. They belong to the class, as opposed to the object (instance). In other words, they are accessed via the class name, not via the object variable, just likestatic
members. One common convention suggests that names of symbolic constants should contain only upper-case letters, with optional underscores for readability. Symbolic constant values must be known at compile-time.
Read-Only Instance Variables
C# has a specialised readonly
modifier, which protects instance variables from being written to after initialisation by a constructor. They are thus logically constant, but their values can be determined dynamically (at run-time) in a constructor.
Behaviour
The designed behaviour of an object is facilitated by functions defined within the class. These functions are most often called methods, or more specifically, instance methods. Methods can also be defined as static
, in which case we qualify them as static methods.
instance methods — Or simply instance member functions, are functions of which (conceptually) each object has its own copy. To avoid having to make physical copies of function code, all instance methods are automatically supplied with a first parameter, which has the name
this
, and has the type of the enclosing class.The object on which the instance method is called, is automatically passed as first argument, which programmers often in conversation refer to as the ‘current object’, instead of ‘this’. Inside instance methods, the
base
keyword can be used to refer to that part of the class, which it inherited from its immediate base class.static methods — Methods that are shared by all objects; they belong to the class itself, so objects do not have their own instances. Like
static
variables, these methods are accessed via the class name. Static methods can only access otherstatic
members, and have nothis
parameter. Thebase
keyword can also be used instatic
methods.properties & indexers — Special methods that allow users of the class to treat a property member like a variable, except that the compiler will automatically call
set
andget
methods. The indexer property allows one to apply the subscript operator on an object. More about this later.overloaded operators — Since operators are treated as
static
method calls, but allowed to use operator notation, we can overload operators in classes.virtual methods — Special instance methods marked as
virtual
, and used to implement polymorphism. These can be overridden in derived classes. These are not used in the exampleCircle
class, since only encapsulation is illustrated.
Properties
Properties are mentioned separately, but technically, they are also just methods with a special syntax. They just allow programmers to create the illusion that a member is a variable. Code that uses the class, can use them just like public
data members.
Initialisation & Destruction
Practically, client code might like to initialise a new object with particular values during instantiation. This is supported via special constructor methods, which may be overloaded. They are special in that a) their names must be the same as the class name, and b) they can have no return type specified.
instance constructors — Methods called with the
new
operator to initialise new instances (objects). Instance constructors can delegate work to another constructor, and a similar syntax allows control over which base class constructor☆ is called.☆ The latter case is not relevant in our
Circle
example, since it does not inherit from any class (except implicitly fromObject
, like all classes without an explicit base class).static constructor — One special, and optional,
static
method, which can be used to initialise onlystatic
data members. Allstatic
constructors of all classes used in a program, are automatically called beforeMain()
.WARNING — Static Variable Order of Initialisation
Although there are rules that govern the order of static member initialisation, you should not write code where one static variable's initialisation, depends on the value of another static variable.
destructor/finaliser — A single method, conceptually created to de-initialise an object, that is C# syntactic sugar for overriding a method called
Finalize
, and often called a finaliser. Because of CLR (Common Language Runtime) garbage collection, destructors are not as common in C#, as in C++, for example. It is not good practice to have a destructor with an empty body; rather omit it entirely.
Basic Destructor
The C# (optional) shortcut syntax for Finalize()
for an arbitrary class, here called MyClass
, looks as follows:
~MyClass() {
// de-initialisation code here
}
The above shortcut is translated by the compiler, to the following code:
protected override void Finalize() {
try {
// de-initialisation code here
}
finally {
base.Finalize();
}
}
The example Circle
class below incorporates a destructor example. It does not really need a destructor; it was only included as an example of the syntax.
Constructor Delegation
It is possible for one constructor to ‘call’ another, which is referred to as ‘constructor delegation’, or ‘constructor chaining’. This adds a new pattern to the possible syntax for constructors, which adds additional elements between the header and the body of the constructor method. This allows one to have all the validation in one constructor, while the others simply delegate the initialisation.
Constructor delegation example
class C {
private int x_, y_;
public C ()
: this(1) //← delegate to `C(int)` (signature).
{ } //← nothing to do here.
public C (int x)
: this(x, 2) //← delegate to `C(int,int)`.
{ } //← nothing to do here.
public C (int x, int y) { //← ‘main/super’ constructor.
if (x < 0 || y < 0) { //← all validation here.
throw new
InvalidArgumentException(
"Bad initialisers"
);
}
= x;
x_ = y;
y_ }
}//class
⋯var c1 = new C(); // `x_` ⇐ `1`, `y_` ⇐ `2`.
var c2 = new C(11); // `x_` ⇐ `11`, `y_` ⇐ `2`.
var c3 = new C(22,33); // `x_` ⇐ `22`, `y_` ⇐ `33`.
var c4 = new C(y:33); // `x_` ⇐ `1`, `y_` ⇐ `33`.
☆ The ⇐
arrow above means ‘…gets the value…’.
This is a very common arrangement, although for simpler classes, we could have created constructors with default arguments instead. However, constructor delegation is much more flexible, and less error prone. Although not necessary, the above example ‘chains‘ from the parameterless constructor to the constructor having one int
parameter to the one having two int
s as parameters.
Field Initialisers
C# allows for a special (although very natural-looking) syntax to initialise data members. It follows the syntax for normal local variable initialisation (hence the term: ‘natural-looking’). You should understand that a class is like a house plan: houses can be built from the plan, the plan itself has no space. Thus, the initialisation must take place during construction (run-time). The term field initialiser was chosen as a name for this rule and behaviour.
IMPORTANT — Field Initialisers
All field initialiser expressions are evaluated, without exception, only when a constructor is called, and execute before the code in the body of the constructor.
We do not use this syntax in the Circle
class, except for the contrived static
object_count
variable. But the explanation above does not apply then, since static
variables are initialised once, before Main()
is called.
Properties
In .NET, ‘property’ is not a generic term. For example, in Visual Basic, it is represented with the Property
keyword. In C#, it is implemented with a syntactic pattern, that distinguishes it from the pattern for defining methods, or the pattern for defining variables.
Properties are not essential; generally, they just provide simplified, but protected, access to private state variables. In other words, to the client code, a property is used, and acts like, a member variable, which may be readonly.
instance properties — Property syntax is simply ‘syntactic sugar’ for more commonly written,
set
property-name(
expr)
andget
property-name(
expr)
methods, which allows client code to logically treat the property as a value☆.☆ Like the
Radius
property in ourCircle
example — which behaves exactly likegetRadius()
andsetRadius()
, which were also supplied as an alternative access mechanism.static properties — Since instance properties are just disguised methods, and methods can be
static
, it implies that properties can also be static, and like static methods, only access static members.indexers — Special properties, which cannot be static, that allow client code to apply the subscript operator to objects of the class. This is not very common.
Read-Only Properties
It is legal to create readonly properties, by simply omitting the set
part of the property. This does not involve the readonly
modifier. Alternatively, you can override the access of the set
by prefixing it with private
or protected
, which means it can still be used inside the class, but client code does not have access.
automatic properties — This is a syntax whereby empty
get
andset
parts of a property cause the compiler to automatically create set and get code for an instance variable that it automatically generates. Automatic properties provide no integrity protection (value validation). We have not used automatic properties in theCircle
class below.public double Radius { get; set; } // automatic property
virtual properties — Since properties are specialised methods, they can be made
virtual
, just like other methods. This also applies to indexers.
Since the compiler automatically chooses the name of the ‘backing variable’ represented by the automatic property, we can only access it via the automatic property name.
Access Control
Encapsulation must allow for the concept of privacy, where (especially) data members are protected from client code, so that the integrity (legal range of values for each field) of an object can be controlled by the class code. Most languages implement this in the form of access control. C# implements this with the keywords private
, protected
, public
, and internal
.
C# allows for 5 different access control scenarios or levels:
private
— Only code inside the enclosing and nested classes has access.protected
— In addition to code inside the enclosing and nested classes, code in classes that inherit from these classes also has access.internal
— All code in the same assembly has access. This is the default for types defined on the outer level, likeclass
es.internal protected
— Similar tointernal
, but code in classes that inherit from this class, also has access.public
— The most permissive access level, which means all code, inside or outside the class, inside or outside the assembly, has access.
When access control is not explicitly specified, C# will treat it as having private
access for members and internal
for classes on the outer level. This applies to all members, not just data members. Although it is less likely to find methods with private
access, it is nevertheless perfectly legal. The same applies to properties, nested types, and even constructors.
Nested Types
For completeness, since it is not used in the example code, understand that inside a class, other classes can be created, hence the term nested. If a class or other type is not inside another, it is said to be on the ‘outer level’. That applies to any user-defined type: struct
, enum
, interface
, etc.
Access to the nested type from code outside the outer class, is just like static
members, and access permissions can be controlled with the normal access specifiers.
Overloaded Operators
Overloading an operator is entirely optional, and if not used carefully, can lead to very unmaintainable code. Java does not even allow this feature, and C# is more restrictive than C++.
Basic Concepts and Rules
The idea of overloading revolves around that concept that the behaviour of any operator can be modelled with a method (function). In other words: the operands can be thought of as arguments to a function, the operator behaviour is represented by the body of that function, while the result of the operator can be represented with a return statement in the function.
Conceptually then, when you write A + B
(where A
has type T
), then the C# compiler treats it as a static
function call: T.operator+(A, B)
. Since such a function does not exist for user-defined types, it will result in a compilation error, unless of course, you write the method. Here is an extract from the Circle
class:
Example addition operator overload
public static Circle operator+ (Circle lhs, Circle rhs) {
return new Circle(lhs.radius_ + rhs.radius_);
}
The above code allows us to add two circles, but instead of calling Circle.Add(A, B)
directly, for example, we indirectly call a similar method (Circle.operator+(A, B)
) by writing: A + B
, assuming A
and B
have type Circle
.
All operators eligible for overloading (not many), must be static
methods. You cannot create new operators, nor change the precedence or number of operands.
If A
and B
are different types, you have to implement commutativity yourself. In other words, if you would like A+B
to work like B+A
, you must overload two +
operators with different signatures. For example, if you would like:
circ1 = circ2 + 0.5;
to be equivalent to:
circ1 = 0.5 + circ2;
you will have to write the following two overloaded +
operators:
public static Circle operator+ (Circle lhs, double rhs) {
return new Circle(lhs.Radius + rhs);
}
public static Circle operator+ (double lhs, Circle rhs) {
return new Circle(lhs + rhs.Radius);
}
Now you will have commutativity for the addition operator, assuming it is desired.
Compound Assignment
Assuming +
is overloaded, C# will automatically supply +=
(compound assignment). So instead of writing: circ1 = circ1 + circ2;
, for example, you can write: circ1 += circ2;
.
Cast Operators
It is possible to overload the cast operator. For example, we may decide it would be useful to cast a Circle
to a double
and even the other way around. The operator
function must still be static
, but cannot have a return type:
public static implicit operator double (Circle c) {
return c.radius_;
}
public static explicit operator Circle (double d) {
return new Circle(d);
}
The implicit
and explicit
keywords control whether the compiler will automatically perform the cast when necessary (call the relevant operator function), or only allow it to be called explicitly with the cast operator.
For the above example, since the cast to double
is marked implicit
, it means the compiler will automatically create code to call the method when a Circle
is used, wherever a double
was expected. This can obviously be dangerous, so it should be given careful consideration. Rather use the explicit
designator when in any doubt.
Encapsulated Circle
Apart from nested types, we employ all these features in a Circle
class. We stress again, this is not necessarily a ‘good’ design for something as simple as the Circle
. We simply want to provide you with one example using all the features, instead of several examples using different features.
To avoid visual noise, we also did not write formal XML comment documentation, which production code should have.
Circle Client
This is an example of code using our Circle
class, i.e., it contains Main()
. It does not use all the properties and methods, since they follow the same pattern, e.g., getArea()
works just like the getCircum()
method, so we only illustrated one option.
CircleClient.cs
— Circle Client Program
/*!@file CircleClient.cs
* @brief Main() using Circle Class
*/
using System;
using System.Linq;
using System.Collections.Generic;
using static System.Console; //C#6 and up!
namespace Examples { ////
class AppCircleClient {
const int EXIT_SUCCESS = 0;
const int EXIT_FAILURE = 1;
static int Main (string[] args) {
double radius, area, circum;
Write("Radius?: ");
if (!Double.TryParse(ReadLine(), out radius)) {
Write("Not numeric input. Terminating.");
return EXIT_FAILURE;
}
// Call `Circle(double)` constructor to initialise new object.
//
= new Circle(radius);
Circle ci
// Console.WriteLine will call our overridden `ci.ToString()`.
//
WriteLine("ci = {0}", ci);
WriteLine($"ci = {ci}"); //← string interpolation works.
// Get & set the circle's area by using instance methods.
//
= ci.getArea();
area .setArea(32.1);
ci
// Get & set the circle's area by using the instance property.
//
= ci.Area;
area .Area = 32.1;
ci
// Get & set the circle's area by using the indexer property.
//
= ci["area"];
area ["area"] = radius * 2.0;
ci
// Just so we don't get a warning about not using `circum`
//
= ci.Circum;
circum WriteLine("Circumference = {0:N4}", circum);
WriteLine($"Circumference = {circum:N4}");
// Test addition and cast operators.
//
double dv = ci; //← implicit cast to double.
= ci + (Circle)dv; //← overloaded `+` and explicit cast.
ci += (Circle)dv; //← you get `+=` for free.
ci
// Use overridden `ToString()` to print new circle attributes.
//
WriteLine("ci = {0}", ci);
WriteLine($"ci = {ci}"); //← string interpolation works.
return EXIT_SUCCESS;
}
}//AppCircleClient
}////namespace Examples
One could go further and demonstrate arrays of Circle
s, functions taking a Circle
as argument, and functions returning a Circle
result.
Exception Handling
We did not use exception handling (try
⋯catch
statements) in Main
. The only reason for this is to keep the example code cleaner, so that it can better illustrate the use of Circle
. But in practice, such an omission in production code would be unforgivable.
Circle Class
Here is the design and implementation of the over-engineered Circle
class, which illustrates properties and indexers. It is longer than it needs to be, simply because we show both the set
prop()
and the get
prop()
instance methods, and the same behaviour using the corresponding properties (and indexers for good measure).
It also provides a static
field, and a static
property to provide read-only access to it. This field maintains a count of all Circle
objects currently not yet destructed (finalised). It is incremented in the constructors, and decremented in the destructor.
Circle.cs
— Circle Class
/*!@file Circle.cs
* @brief Encapsulation Features Employed in a Simple Circle Class.
*/
using System;
using System.Linq;
using System.Collections.Generic;
namespace Examples { ////
public class Circle {
private double radius_; // the only "state" member
// Superfluous static data member and associated static property.
// Property is readonly (only `get`). Use: as `Circle.Count` to
// see how many circle objects are still alive at a given time.
//
private static int object_count_ = 0;
public static int Count { get => object_count_; }
// Constructor(s). Destructor added for illustrative purposes, and
// to decrement the "alive" object count.
//
public Circle (double radius = 0.0) {
if (radius < 0.0)
throw new ArgumentException("Circle(): Negative radius!");
= radius;
radius_ ++object_count_;
}
~Circle () {
#if DEBUG
.WriteLine("A Circle object being destructed.");
Console#endif
--object_count_;
}
// Main ‘behaviour’ methods, i.e. providing the functionality. In
// less trivial classes, there are generally many more methods. The
// `this.radius_` in `getCircum()` is not necessary, but included
// to show that the compiler adds it automatically if not present,
// since it works without an explict `this.radius_` in `getArea()`
//
public double getArea () => Math.PI * radius_ * radius_;
public double getCircum () => 2.0 * Math.PI * this.radius_;
// The ‘set’ versions for `Area` and `Circum` simply use the
// corresponding properties, to show that they are not just for
// client code.
//
public void setArea (double area) => Area = area;
public void setCircum (double circum) => Circum = circum;
// ‘Accessor’ methods for the radius, AKA ‘getter & setter’ methods.
// This is just a common pattern, not a particular syntax.
//
public double getRadius () => radius_;
public void setRadius (double radius) {
if (radius < 0.0)
throw new ArgumentException("setRadius(): Negative radius!");
= radius;
radius_ }
// Alternative access to radius, using a property, which is a syntax,
// re-using the above accessor methods.
//
public double Radius {
=> getRadius();
get => setRadius(value); // `value` is automatic parameter name
set }
// ‘Fake’ Area and Circum properties. Setting the Area or Circum
// will simply calculate the Radius and set that instead.
//
public double Area {
=> getArea();
get => setRadius(Math.Sqrt(value / Math.PI));
set }
public double Circum {
=> getCircum();
get => setRadius(value / Math.PI / 2.0);
set }
// Totally superfluous indexer property for illustration. We could
// have used `return radius_;` instead for `case "RADIUS"`, but by
// design wanted to have only one "exit point" for the radius in
// all the code. Alternatively, we could have simply used the above
// property: `return Radius;`. The same applies to Area, and Circum:
// we could have used: `return Area;` or `return Circum;` instead.
// But those properties call `set/getArea()` and `set/getCircum()`
// anyway, so this is actually more efficient (by a small margin).
//
public double this[string index] {
get{
switch (index.ToUpper()) {
case "RADIUS": return getRadius();
case "CIRCUM": return getCircum();
case "AREA" : return getArea();
default:
throw new ArgumentOutOfRangeException(
"Circle[].get: Invalid index");
}
}
set{
switch (index.ToUpper()) {
case "RADIUS": setRadius(value); break;
case "CIRCUM": setCircum(value); break;
case "AREA" : setArea(value); break;
default:
throw new IndexOutOfRangeException(
"Circle[].set: Invalid index");
}
}
}
// Overloaded addition and cast operators
//
public static Circle operator+ (Circle lhs, Circle rhs) =>
new Circle(lhs.radius_ + rhs.radius_);
public static implicit operator double (Circle c) => c.radius_;
public static explicit operator Circle (double d) => new Circle(d);
// Bonus: overriding `ToString()`, which `Circle` inherits from
// `System.Object` (automatically), so it returns something useful.
//
public override string ToString() =>
.Format(
String"[R={0:N4},C={1:N4},A={2:N4}]", Radius, Circum, Area
);
}/*Circle*/
}////namespace Examples
TIP — String Formatting
All string formatting in the .NET Framework classes —not just conversion to a string
by virtue of override
'ing the inherited ToString
method from object
— is performed by one of the several overloaded Format
methods of the string
class. Should you want similar behaviour for your classes, you will have to implement the IFormattable
interface.
Summary
Encapsulation is useful for any program, and does not have to involve the other aspects of object-oriented programs, like inheritance and polymorphism (virtual
methods). Combining only encapsulation and modularisation (using static class
es with static
functions), much can be accomplished in a readable and maintainable way. For example, Visual Basic, until version 6, only offered modularisation and encapsulation, and many successful programs were written with it.
Overloading operators should be carefully considered. When in doubt, do not overload any.
This fact is often overlooked, and should thus be stressed. You can create useful code without it being full-blown object-oriented. If you add interface
s and simple generics to the list, it promotes even better re-use, and still does not require in-depth OOAD (Object-Oriented Analysis & Design).
Interface Implementation
The term implement an ‘interface’ refers to the programming concept of interfaces, and a syntax whereby a class can ‘implement an interface’. This is often used as an alternative to, or in addition to, inheritance in object-oriented languages. We could have implemented some minor interface in the Circle
class example, but decided that it was slightly out of scope, considering our intended audience, and would have increased the code even further, which may have obscured the syntax we wanted to highlight.
But for the curious, we provide some snippets you can add to Circle
. Let us assume you want to be able to write code like this, using a Circle
, where you want to choose which of the attributes: radius (:R
), circumference (:C
) or area (:A
) to format as a string:
// format the area of a `Circle` using `:A` as format specifier.
//
.Format("{0:A}", circ1); // or...
⋯stringWriteLine("{0:A}", circ1); // (`Write/Line` calls `Format`);
WriteLine($"{circ1:A}"); // same for string interpolation.
In such a situation, some overloaded versions of String.Format
will expect objects that implement the IFormattable
interface. This interface dictates overloading a specific ToString
method. This means Circle
must implement it, for the above code to work:
public class Circle : IFormattable { ⋯
That, however, is not enough, but does mean this class promises to implement the ‘interface’. For the class to uphold the promise, it must create a function that looks as follows:
public string ToString (string format, IFormatProvider formatProvider);
The code will not compile if this method is not defined (implemented). Our implementation will ignore the second parameter, for the sake of simplicity:
public class Circle : IFormattable {
⋯public string ToString (string fmt, IFormatProvider fp) {
switch (fmt) {
case "R" : return getRadius().ToString("N4");
case "C" : return getCircum().ToString("N4");
case "A" : return getArea() .ToString("N4");
}
return "*ERROR*";
}
⋯}//class
And that is it: code like Write("{0}:A", circ1)
will now return the area. Instead of the :A
, you can use :C
for circumference, or :R
for the radius. This is clearly not necessary, but like indexers, this is an option that you should at least be aware of. The decision whether it is legitimately useful or not, is another matter, and depends on the complexity of the class in question.
You now have an example of ‘implementing an interface’. This will always involve looking up, and implementing, the methods that the interface represents. This is about as far as you can take encapsulation, without resorting to inheritance and polymorphism.
2021-04-01: Links for .NET API 5.0 & using expression-bodied function syntax. [brx]
2020-03-19: Minor fixes; string interpolation, virtual properties. [brx]
2017-11-30: Editing. [brx;jjc]
2017-11-25: Additional topics. Rephrasing. Reorganisation. Editing. [brx;jjc]
2017-11-23: Rephrasing, editing, new admonition syntax and many links. [brx]
2016-12-01: Created. [brx]