C# Program Organisation
Fundamental C# Program Structures with Examples
PREREQUISITES — You should already…
- have some programming experience, preferably in C/C++/D/Objective-C/Java/Pascal;
- be conversant with the fundamentals of Object-Oriented Programming (OOP);
- understand the role of types in a statically typed language;
- understand expressions, operators and precedence in general.
Flat, C-like Version
The absolute simplest (flat) organization involves having a number of static
functions in the same class, which call each other. We use static class…
simply because it is the right thing to do, if we want the compiler to check that we comply with our own intentions (have all functions as static
). Although C# does not have a module
keyword, a static class
is just about 100% equivalent.
For simpler programs, you can use this program organisation with one file. You can easily add more source files. By convention, each source file contains one static class
, giving you a collection of modules. From a design perspective, this is equivalent to C-like code, except C does not have proper modules either (it treats source files as an effective module).
Inside such a “module”, you can only have static
members, further simplifying the number of rules required to do something useful. Additionally, const
creates symbolic constants, which are effectively automatically static
.
Another aspect that affects organisation, is that you do not have to define functions before you use them in a file. That means we can write our Main
as the first function, even if it calls functions which we define later in the file.
CircApp01.cs
— Simple Circle
/*!@file CircApp01.cs
* @brief Simplest Circle Calculator (01)
*/
using System;
using System.Linq;
using System.Collections.Generic;
using static System.Console;//C#6+
namespace Examples { ////
static class AppCircle {
const int EXIT_SUCCESS = 0;
const int EXIT_FAILURE = 1;
static int Main (string[] args) {
Write("Radius?: ");
string input = ReadLine();
double radius;
if (!Double.TryParse(input, out radius)) {
Write("Not numeric input [{0}]. Terminating.", input);
return EXIT_FAILURE;
}
double area = CircArea(radius);
double circum = CircCircum(radius);
WriteLine("Area : {0:N4}", area);
WriteLine("Circum.: {0:N4}", circum);
return EXIT_SUCCESS;
}
static double CircArea (double radius) {
return Math.PI * radius * radius;
}
static double CircCircum (double radius) {
return 2.0 * Math.PI * radius;
}
}//AppCircle
}////namespace Examples
Apart from the namespace and the class, and some minor syntax, this is very much the organisation of a C program, with some advantages (such as not having to declare functions before using them).
This structure allows you to think in terms of:
- “global data”, which are contants and variables at class level,
- “global user-defined types”, inside or outside the main class, and
- “global functions” which call each other.
Such a program organisation is really only useful for smaller programs, but it is certainly better than having all the code in Main()
. You can increase its utility by adding more source files, each with a module (static class
). You will then have larger, but still manageable, programs, without resorting to OOP, as we illustrate in the next section.
Modularised Static Version
There are two variations possible here, but the differences are so small that we won’t show both, only discuss them.
C# files may contain more than one class. We could “modularise” the original code, by placing the circle-related functions in a separate, “global” static
class (not nested inside the class containing Main()
). Whether this class is in the same file as the Main()
function’s class, or in a separate file in the same project, is inconsequential, i.e., it does not affect the syntax, as long as both are in the same namespace. Here is a version split into two files.
CircApp02.cs
— Modular Circle
/*!@file CircApp02.cs
* @brief Modularized Circle Calculator (02)
*/
using System;
using System.Linq;
using System.Collections.Generic;
using static System.Console;//C#6+
namespace Examples { ////
static class AppCircle {
const int EXIT_SUCCESS = 0;
const int EXIT_FAILURE = 1;
static int Main (string[] args) {
Write("Radius?: ");
string input = ReadLine();
double radius;
if (!Double.TryParse(input, out radius)) {
Write("Not numeric input [{0}]. Terminating.", input);
return EXIT_FAILURE;
}
double area = Circle.Area(radius);
double circum = Circle.Circum(radius);
WriteLine("Area : {0:N4}", area);
WriteLine("Circum.: {0:N4}", circum);
return EXIT_SUCCESS;
}
}//AppCircle
}////namespace Examples
The code for the circle calculations is placed in another class, which in this case, we also a) put in another file, and b) to keep it simple, do not put inside a different namespace.
CircUtils02.cs
— Modular Circle
/*!@file CircUtils01.cs
* @brief Circle Calculator Utilities
*/
using System;
using System.Linq;
using System.Collections.Generic;
using static System.Console;//C#6+
namespace Examples { ////
static class Circle {
public static double Area (double radius) {
return Math.PI * radius * radius;
}
public static double Circum (double radius) {
return 2.0 * Math.PI * radius;
}
}//Circle
}////namespace Example
As you can see, the differences compared to the first version, are minor — you simply have to qualify the calls to the Area()
and Circum()
functions, by prefixing them with the name of their class. And since it would be redundant to use Circle.CircArea(…)
, we changed the function names, so it can be written as Circle.Area(…)
.
Static Class is Equivalent of Module
By making the Circle
class static
, we ensure that the C# compiler will check that everything inside the class is static
, so that we cannot accidentally forget. It is not a requirement, but simply good coding convention in this instance, where it is our intent to only have static functions and variables.
We should also note, before considering the OOP version, that many C# topics can be mastered with only this organisation. You can learn and use all the following topics, without an object-oriented design or even deep OOP knowledge:
- The use of types to define variables.
- Operators and expressions.
- Arrays and generic lists.
- Function signatures and overloading.
- Default parameters, optional parameters, pass-by-reference.
- Iteration, selection and execution transfer statements.
- Exception handling syntax and patterns.
- Conditional compilation, debugging and attributes.
- Anonymous delegates and/or lambdas.
Of course, you have to at least learn how to use the classes in the .NET Framework, but that is a long way from learning how to efficiently design and write object-oriented code. The more OOP you learn, however, the more you will understand how some of the features that are so easy to use, actually work.
OO Encapsulation Version
Encapsulation is one of the key features of OOP (Object-Oriented Programming). We employ it in this version to encapsulate the concept of a circle as an object — i.e., an abstraction that suits the program’s requirements. We again use a class, but instead of static
members, we use instance members (methods). As before, whether the class is in the same or another file, makes no difference to the code in Main()
using it.
CircApp03.cs
— Encapsulated Circle
/*!@file CircApp03.cs
* @brief OO Encapsulated Circle Calculator (03)
*/
using System;
using System.Linq;
using System.Collections.Generic;
using static System.Console;//C#6+
namespace Examples { ////
static class AppCircle {
const int EXIT_SUCCESS = 0;
const int EXIT_FAILURE = 1;
static int Main (string[] args) {
Write("Radius?: ");
string input = ReadLine();
double radius;
if (!Double.TryParse(input, out radius)) {
Write("Not numeric input [{0}]. Terminating.", input);
return EXIT_FAILURE;
}
Circle circ = new Circle(radius);
double area = circ.Area();
double circum = circ.Circum();
WriteLine("Area : {0:N4}", area);
WriteLine("Circum.: {0:N4}", circum);
return EXIT_SUCCESS;
}
}//AppCircle
}////namespace Examples
The code in Main()
is not much more complicated than the previous versions, but now we have a Circle
object, which remembers its own radius, and can calculate its own area and circumference:
Circle.cs
— Encapsulated Circle
//[Circle.cs] Class to Encapsulate a Simple Circle
//
using System;
namespace Examples { ////
public class Circle {
private double radius_; // each Circle obj. gets own copy
public Circle (double radius) { // call with new
radius_ = radius;
}
public double Area () {
return Math.PI * radius_ * radius_;
}
public double Circum () {
return 2.0 * Math.PI * radius_;
}
} // Circle
} //// namespace Examples
Although such a simple example might not convince you, this promotes re-use and, ultimately, greater productivity. The other advantage is that we can have more than one Circle
variable, even arrays of Circle
objects, each having their own copies, or instances of the radius_
member. Conceptually, they also each have their own copies of the instance methods. This is why many OO texts define an object as “an instance of a class”, and many OOP languages use a syntax like: new SomeClass…
, to create a new object from the type SomeClass
(remember that a class is a type).
Circle as a Value Type
For such a simple class, we could have implemented it as a struct
(value type), instead of a class
(reference type), to make it even more like C. But we would not have gained much benefit from it, other than not having to call new
to create a new instance.
Variables of a value type stores all the data comprising the state, while variables of a reference type store only the address of (reference to) the data of the object, which must be allocated with new
. It can be called explicitly, or called indirectly by using various shorthand syntax variations. For example, these two lines are equivalent; the first one simply uses shorthand syntax:
The System.String
(string
in C#) is a class
, hence a “reference type”, but because it is so common, C# will automatically, in certain circumstances, such as with literal strings, call new
.
For a version of Circle
using more encapsulation syntax and features, see the separate page for C# Encapsulation Syntax.
Conclusions
The OO encapsulation version is the ideal, but to achieve that, you have to know a) how to encapsulate (easy), b) how to inherit, which requires OOAD (Object-Oriented Analysis & Design), and c) how to implement polymorphism and interfaces, which requires more syntax knowledge, and even more OOAD.
However, and this cannot be stressed enough, there is nothing inherently “wrong” with the other organisations. In particular, the second version already provides numerous benefits, and encapsulation without inheritance and polymorphism, is but a simple step further, with even more promise of reuse. Useful programs do not have to be fully object-oriented.
2017-11-30: Editing. [brx;jjc]
2017-11-26: Additional topics. Editing. [brx;jjc]
2017-08-06: Created. [brx]