7. High-level Code: Assemblies etc

This chapter provides an overview of the features of C# that lead to increasingly greater levels of abstraction until, at the assembly level, the program itself is capable of becoming a service for other .NET programs.

Custom Objects

We have met with objects — they are hard to avoid in modern computing. This section describes custom objects created by the programmer.

The pillars of objects are inheritance, encapsulation and polymorphism. Which is a grand way of obfuscating the fact that objects actually fairly easy to understand at a basic level.

In an earlier chapter, we looked at objects in an abstract way and talked about hierarchies, giving as an illustration a hierarchy proceeding from Animal to Mammal to Cat. We said that in this hierarchy Mammal inherits from Animal and Cat inherits from Mammal.

In C#, such a hierarchy can be expressed like this :

class Animal {} // note: a class has a code block (empty here)
class Mammal {} : Animal
class Cat {} : Mammal

This is quite useless to anyone, so let’s add the ability to give an animal a name :

class Animal
{
     string Name;
}

The variable ‘Name’ is called a field. We can now create an animal object (as defined in the Animal class) and assign a value to the name field, like so :

Animal a = new Animal();
a.Name = "Lion"; // members of a class are denoted by a dot

Here ‘a‘ is an object. This object instantiates the class Animal. We use the word new to create an object defined by a class. Here we set the ‘Name’ field to ‘Lion’. Members of a class are denoted with a dot: { class } dot { member }.

With the line new Animal(), we meet with a class constructor. This is a special method called when an object is created :

// all classes have a constructor, so this class
// has one even though it has not been coded for
class Animal {}

// this declaration provides an empty constructor
class Animal
{
     Animal() 
     {
          // initialisation code here!
     }
}

The code below illustrates that an object is very specific about how it can be used. An object is defined by its class and anything not defined there will lead to an error :

a.Car = "Ferrari"; // ERROR!!! No field 'Car' defined in class

The code below provides an example of inheritance. It shows how classes can re-use what is defined in their parent. Here, the ‘Name’ field defined in ‘Animal’ is re-used :

mammal.Name = "Goat"; // Name << Animal
cat.Name = "Felix"; // Name << Mammal << Animal

A more difficult concept to understand is polymorphism. To help here, first consider that nature has produced no less than three sets of flying creatures: birds, insects and bats. Classes can express this fact via polymorphism :

class FlyingAnimal : Animal
{
     void Fly() {}
}

class Bird : FlyingAnimal
{
     override void Fly() {} // polymorphic 'Fly'
}

class Insect : FlyingAnimal
{
     override void Fly() {} // polymorphic 'Fly'
}

class Bat : FlyingAnimal
{
     override void Fly() {} // polymorphic 'Fly'
}

We create a ‘FlyingAnimal’ class that defines code for flying. We then create classes for the three types of flying animal and we override the parent class flying code. Each child class implements its own ‘flying code’, just as nature intended.

Methods (Actors)

A programming language can be said to consist of things (variables, constants, objects) and actors. In OOP, methods are the actors (not to be confused with method actors). Methods belong to objects and are defined in classes.

Compare C# to C, which does not have objects. In C, actors are called ‘functions’. A function can be thought of as a ‘free’ actor. In C# a method is an actor ‘tied’ to an object :

/*
     In C, all actors are 'free' because there are no objects
     to tie them to. Free actors are called FUNCTIONS.
*/
void login(char[] user, char[] pwd) {
     // implementation here
}

/*
     In C#, all actors are 'tied' to an OBJECT. Tied actors
     are called METHODS.
*/
// 'Login' is a METHOD of the 'user' OBJECT
user.Login("me", "password");

If we want the animals of our Animal object to be able to start performing actions, we must give the class some methods.

class Animal
{
     string Name;
     void Sleep() {}
     void Watch() {}
}

class Predator : Animal
{
     void Eat(Prey prey) {}
}

class Prey : Animal
{
     bool Escape() { return true; } 
}

// usage

// here we create two OBJECTS from the CLASS definitions above
Prey fly = new(); // shortcut for 'new Prey()'
Predator spider = new();

// call the METHODS (actors) of the 'Prey' and 'Predator' classes
// note: 'call' is the most commonly-used terminology for using
//        methods, so we say we 'call' Escape(), we 'call' Eat()
if (!fly.Escape())
{
     spider.Eat(fly);
}

Let us pick apart these three lines to understand more about how methods work :

void Eat() {}
bool Escape() { return true; }
if (!fly.Escape()) 

The first line contains the curious term ‘void’. This implies to the unenlightened that there is something up. In fact, the term is an old C one that simply means ‘nothing’ or more specifically ‘has no value’.

The line ‘void Eat() {}’ contains three elements.

  • The curly braces we are familiar with, so we know that they form the block of the method’s code (here empty).
  • Eat() is the name of the method and the parentheses (here empty) show that it is a method. In between the parentheses is where we put any parameters.
  • void is the return value of the method. That is the point of ‘void’ (‘has no value’). Void allows a method to act without responding.

What all this means will become clearer by examining the line bool Escape() { return true; }.

  • Escape() is as per Eat(). There are no parameters here either.
  • void has been replaced by bool. This is a value. It means that Escape() is expected to respond. In C (and C#) terminology this is called returning a value. This makes the method act like a sort of variable. So just as we have bool test = false we also have bool Test(). The method though is what we might term an active value whereas a variable is effectively a passive value. The Escape() method, far kinder than Nature, responds with the line return true.

In the line if (!fly.Escape()), the exclamation mark is the C# boolean symbol for ‘not’, as we saw in the previous chapter. If its response decides the fly can escape, it will return true, which after applying the ! (‘not’) will evaluate to false. If the fly can’t escape, the return value will be false and the if will evaluate to true. (Our code here is kind and the fly will always escape.)

The last line to examine is void Eat(Prey prey) {}, which treads mostly familiar territory. The method is void and not expected to respond, so it has no return statement. The new thing here though is the parameter, which is the poor prey. Fortunately this code too is kind, for the empty code block means this predator can do nothing to its prey.

Methods, then, allow objects to act. The things of an object are termed properties, which we need to examine next.

Properties and Access

Our animals can keep a secret if we add the following field to the class definition :

string Secret = "secret!";

But say your ‘Animal’ class has been released into the wild and coders are busy coding away with it. Given this design of the class, a mischievous coder might write :

animal.Secret = "Not any more!"; // goodbye, secret

That is not good. The class design has made the secret public. There is no encapsulation. The ‘Secret’ property is simply thrown open to any code using the ‘animal’ object. So rather than a field, it is usually best practice to employ a property, for properties enable encapsulation.

In the latest versions of C#, properties can be defined in multiple ways :

// simplest type of declaration, more or less
// equivalent to a field . . .
public string Foo { get; set; }
// . . . except we can also have . . .
public string Foo { get; }
// . . . which cannot be expressed with field syntax.

// wordy property declaration (older syntax)
private string _foo;
public string Foo
{
     get { return _foo; } 
     set { _foo= value; }
}

// concise property declaration (new syntax)
private string _foo;
public string Foo
{
     get => _foo; 
     set => _foo= value;
}

The most important new thing here is the keywords private and public that have so far been avoided in the code examples. These are access modifiers. They describe what can and cannot ‘see’ the value. A ‘public’ value is accessible outside an object but a ‘private’ one is only accessible within the class code. The secret can now be kept :

// DEFINITION
private string _secret = "secret!";

. . .

// USAGE
animal.Secret = "Not any more!"; // ERROR! read only

Now note the keywords get and set. These belong to properties and are logically enough called a getter and a setter. At this point, it should be clear what the ‘_foo’ variable is doing. It effectively holds the actual value of the public-facing ‘Foo’ property. The getter simply returns the value of ‘_foo’ while the setter assigns the special variable value to it.

This allows us to encapsulate our class because we can now define which properties can be seen by outside classes. If there is no setter, the property is read-only.

private float _pi = 3.14;
public float Pi
{
     get { return _pi; } 
}

. . .

numbers.Pi = 1.43; // ERROR! readonly! no setter!
float pi = numbers.Pi; // OK! getter exists!

Methods can also easily be encapsulated using access modifiers :

private void GenerateSecret() {}

. . .

// secret stays secret here
myclass.GenerateSecret(); // ERROR! defined as private

Another important access modifier worth mentioning here is protected. Say you have this class :

public class Tale
{
     private string[] _characters;
}

public class FairyTale : Tale
{
     public void EnumerateCharacters()
     {
          for (int i = 0; i <= _characters.Length - 1; i++)
          {
               Console.WriteLine(_characters[i]); // ERROR! private!
          }
     }
}

Having just private and public access modifiers clearly isn’t enough. A private variable or method is inaccessible to descendant classes. That is the purpose of the protected modifier. A protected item is visible within the class hierarchy but not to external code (as per private items). So to fix the code above we can make the following change :

// [Tale parent class]
protected string[] _characters; // visible to all descendant classes

. . .

// [FairyTale descendant class]
Console.WriteLine(_characters[i]); // no error now!

I will finish this section with a simple example illustrating the three key concepts of classes (and objects): inheritance, encapsulation and polymorphism.


public class Trickster
{
     public void Deceive() {}
}

public class Spy : Trickster // inheritance !
{
     public override void Deceive() {} // polymorphism !
}

public class Magician : Trickster // inheritance !
{
     protected Device _wand;
     public Device Wand { get => _wand; } // encapsulation !
     public override void Deceive() {} // polymorphism !
}

Generics

Songs like ‘Strawberry Fields Forever’ or ‘Like A Rolling Stone’ are unique. A lot of music though — say Merseybeat or K-Pop or Disco — tends to sound generic. The Mills & Boon publishing company deliberately publishes generic books that ensure its readers make a safe purchase every time. Sometimes the unique is good, but generic is also often the best option. If you want the Mona Lisa hanging in your living room, a generic reprint is your only option.

We have already met with arrays that can store a list of things. In modern programming environments, a list class is much easier to handle than the old array, for it can iterate through its member via a foreach loop without needing an indexer :

// old style array : 'for'
for (int i = 0; i <= myarray.Length -1; i++)
{
     /* do something with */ myarray[i];
}

// ArrayList : 'foreach'
// (this is an old pre-generics .NET Framework class)
foreach (int i in myarray)
{
     /* do something with */ i;
}

/*
     // NOTE!
     // arrays in the latest .NET releases *do* allow iteration!
     array[] a // fill
     foreach (int i in a) {}
*/

So the ArrayList class was an improvement on the old-style array. If we observe a pre-generics ArrayList being initialised we can see though that it is not ideal :

ArrayList names = new ArrayList();
names.Add("Truman");
names.Add("Eisenhower");
names.Add("Kennedy");
// etc

ArrayList numbers = new ArrayList();
numbers.Add(1);
numbers.Add(2);
numbers.Add(3);
// etc

This code works because the ArrayList is being filled with a collection of object variables and everything in C#, as we have seen, inherits from object. This is error-prone because there is no built-in way of checking the type of an ArrayList member.

Enter generics. Here is the equivalent generic code :

List<string> names = []; // minimal way of declaring an array
names.Add("Truman");
// . . .

List<int> numbers = [];
numbers.Add(1);
// . . .

This might not look much at first, or seem much different to the ArrayList version. The big deal lies within the angular brackets, for this is the generic stuff.

Notice the <string> attached to the first list and <int> to the second. Here is the definition of a generic list :

public class List<T> : ICollection<T>

T here stands for ‘Type’. Translated further, it means ‘of any type’. That is, ‘T’ can be replaced by the name of a type such as ‘string’ or ‘int’. You can write your own class and use it with the list: List<MyClass>.

What about the ICollection<T> bit on the right? We have seen that classes inherit and that to express this we write Mammal : Animal. The ‘I’ in front of ‘Collection’ tells us this is an interface. If the item to right is an interface (not a class) the class being defined must implement the interface. Note that the interface like the class has those angular brackets enclosing a ‘T’. ‘ICollection’ is thus a generic interface. Note too how the <T> is a match for the class declaration.

So what is an interface?

Interfaces

In C# an interface can be thought of as a codeless definition (though the latest iteration of C# has for good or ill added the ability to add code to an interface definition, a fact we will conveniently ignore here).

Just as a class defines the form of an object, an interface defines the form of a fragment of a class. Any class that implements any interface must implement all of the interface. This means that, however large and complex the implementing classes are, they all implement the same members that the interface defines. This is illustrated below :

// create an interface, and note the 'I' prefix
public interface IPlay
{
     void Play();
}

// implementation 1
public class FootballMatch : IPlay
{
     // play a game of football
     public void Play() {} // MUST have a Play() method!
}

// implementation 2
public class Child : IPlay
{
     // childsplay
     public void Play() {} // MUST have a Play() method!
}

// implementation 3
public class Stereo : IPlay
{
     // play a tune
     public void Play() {} // MUST have a Play() method!
}

// create a 'consumer class' for the interface
// all classes implementing IPlay look identical
// so any implementation of the IPlay interface
// is ok here
/*
     Note: 'Player' has a 'primary contructor' (ctor). The
     parameter is passed in at the very top of the class
     definition, so we do not need to create a default
     ctor within the class body.
*/
public class Player(IPlay item) // primary ctor
{
     private IPlay _item = item; // 'item' passed in via ctor

     public void Play()
     {
          // play football or childsplay or play music
          _item.Play();
     }
}

// run the player with each IPlay implementation
Player player = new(new FootballMatch());
player.Play(); // nil-nil draw

player = new(new Child()); // note this replaces the old player
player.Play(); // lot of mess

player = new(new Stereo());
player.Play(); // tuneless warblings

Here an IPlay interface is declared and implemented by three classes. A consumer class ‘Player’ is created that can run any ‘IPlay’ object. This means that player.Play() will perform very different actions depending on whether it is a match or a child or a stereo doing the playing. The only thing it knows is that something is playing.

An important point to emphasise is that an interface has absolute tunnel vision. As stated above, no matter how many properties and methods the ‘Stereo’ class actually defines, as an implementer of IPlay and as an instance of IPlay there is only the one ‘Play’ method. So,

  • a class may have many methods;
  • an interface at most a few;
  • a class masquerading as an interface has these few only

A well thought-out interface is worth its weight in IGold. The corollary being that a bad interface is certifiably ILead.

public interface IWhy
{
     void LeafThrough(Book book);
     void Move(int amountX, int amountY);
     void Tidy(Room room);
     object Search(string text);
     void Wait();
     bool IsPerpendicular { get; }
}

I mean, why?

Components

With components we are going up a notch in the hierarchy of complexity :

string > array > object (class) > component

A component is easiest to understand if we consider user interfaces. UI is buttons and tab panels and check boxes and text boxes etc. To build a UI, we add these to our interface, whether Web or Windows. In fact, a text box is an example of a component. What is a text box? A rectangular region that both displays text and allows text to be entered. The point about a text box is twofold. First, a text box is a text box is a text box. It is a component. It is entirely predictable. Every text box is exactly the same. However, a text box has properties. These properties can be set. Background colour for example. So to revise our bold statement, ‘Every text box has exactly the same properties’. It is the same but it may not look or act the same, which is of course what makes it useful.

That is a component. The text box itself is a built-in component. But you the programmer can create a component too. What about a ‘Quacker’? A quacker has a button that when pressed emits a quacking sound. It also has an image of a duck. The two basic properties of a Quacker component are therefore the image and the quack. What more does a quacker need?

The Quacker is a custom component. Once you have have written such a component, other applications can use it and add it to their UI.

A component is, therefore, a runnable piece code designed for use in any .NET program. It is created in one .NET project and consumed in other .NET projects. The general principle being that a component is universal. A component is available to all of .NET.

Assemblies

The last thing to look at is the assembly, the end result of all .NET projects. The assembly is the next step up from the component.

The assembly is a synonym for ‘.NET program’, of which there are two types. In Windows terms, an .exe file is an executable. This means that it entitled to start up a process, a running Windows program. The other type is a DLL (‘Dynamic Link Library’). A DLL is basically an executable that cannot start a process. An exe is a primary program that can execute, a DLL is a secondary program that can execute inside an exe. The DLL is linked to the exe and executes inside it.

The latest versions of .NET in fact greatly blur the difference between EXE and DLL and always output an .exe file. But the principle remains that an EXE runs itself and a DLL runs inside.

The difference between a component and an assembly is that a component is an emphatically discrete thing. If Quacker is a component, a related assembly might be Calls.dll and contain a set of components (Quacker itself, of course, but also Woofer, Meower, Brayer, Snorter, Bleater, Mooer, Rivetriveter and many more).

Assemblies though are far to big to provide one-line examples for, as are components. We have therefore advanced as far as we can in our guide to ‘what is code’. What is an assembly can only be explained in general terms.

As we have gone as far as we can with C# and code that does, we can now move onto the topic of data and code that is done to.


Posted

in

by