This article briefly discusses modular software development in C#.

Please Sign up or sign in to vote.

Table of Contents

Introduction

Software design promotes a set of programming qualities:

Abstraction : The ability to represent parts of a program with a summary of their essential characteristics

: The ability to represent parts of a program with a summary of their essential characteristics Encapsulation : The possibility to expose public parts and hide private parts of an abstraction

: The possibility to expose public parts and hide private parts of an abstraction Modularity: The ability to decompose a program into a set of cohesive and loosely coupled components

Some goals:

Maintainability : Is it easy to modify and extend a program?

: Is it easy to modify and extend a program? Reusability: Is it possible to avoid code duplication?

Modularity helps to have extensible, modifiable, portable, maintainable, reusable, understandable and flexible software. It allows new features to be seamlessly added when desired, and unwanted features to be removed, thus simplifying the user-facing view of the software. It allows several developers to work on different modules concurrently. It also allows to test modules independently. Furthermore, large projects become easier to monitor and to control.

C# such as C, Java and other programming languages allow to create modular software through different ways. In this article, we will briefly explore modular software development through interfaces in C#.

Types of Specifications

Code specification may take different forms:

Functional specification: It should be possible to define sets containing any kind of objects of the same type. It should be possible to add and remove elements from a set, and to tell if an element belongs to a set or not.

Non-functional specification: The implementation should run efficiently on current computers. The implementation should handle correctly sets of arbitrary sizes.



Here comes a problem of validation: How can specifications be checked?

Below is the Interface/Type specification:

public interface ISet<T>{ ISet<T> Add(ISet<T> s, T e); ISet<T> Remove(ISet<T> s, T e); ISet<T> Empty(); bool IsEmpty(ISet<T> s); bool Find(ISet<T> s, T e); int Length(ISet<T> s); }

Below is the formal specification:

IsEmpty(Empty()) = true IsEmpty(Add(s, e)) = false Find(Empty(), e) = false Find(Add(s, e), e) = true Find(Add(s, e), e) = Find(s, e) Add(s, e) = s [ if Find(s, e) = true ] [unconstrained otherwise] Remove(Empty(), e) = Empty() Remove(Add(s, e), e) = s Remove(Add(s, e), f) = Add(Remove(s, f), e)

Modules

A module is a construct representing a unit of code (set of types, values, functions and any expression allowed by a language) and satisfying:

Interface : A module may publicly provide and require a set of components.

: A module may publicly provide and require a set of components. Encapsulation: A module may hide or make abstract some of its components.

Sets of modules are meant to be connected according to the dependencies induced by their interfaces:

Independence: A module should depend only on the interfaces of its dependencies.

Many programming languages provide modules complying only to a subset of these properties:

Interfaces may provide different levels of verification: Names only (Racket, Python), types (C, OCaml).

Encapsulation may not exist (cf. Python module privates).

Interfaces must sometimes be shipped with the module (Racket, Haskell) or live as independent citizens (C, OCaml).

In the following article, we will investigate the capabilities of C# in regard to modules.

No Module Development

All the code is written in a single unit, with few specifications.

Below is an example:

public T[] Empty<T>(){ return new T[]{}; } public bool IsEmpty<T>(T[] s){ return s.Length == 0 ; } public T[] Add<T>(T[] s, T e){ var l = new List<T>(s); l.Add(e); return l.ToArray(); } public T[] Remove<T>(T[] s, T e){ var l = new List<T>(s); l.Remove(e); return l.ToArray(); } public bool Find<T>(T[] s, T e){ return s.Contains(e); } public int Length<T>(T[] s){ return s.Length; }

Favorable features:

REPL (simplifies incremental development)

Extensible language (simplifies writing ad-hoc code)

Problems:

Parts of the code with different purposes are all mixed up in a single file (example: Implementation and tests)

Hinders code reuse/modification of a subset (e.g. modification of the set representation)

Complexifies the verification of the code (All or nothing)

Not adapted to separate compilation, separate testing, team development

Modular Programming

Modular programming breaks the code into a set of cohesive and loosely coupled modules, that

shall be composed depending on the specification.

The relationship interface/implemented class is similar to module/signature.

Below is the Interface/Type specification:

public interface ISet<T>{ ISet<T> Add(ISet<T> s, T e); ISet<T> Remove(ISet<T> s, T e); ISet<T> Empty(); bool IsEmpty(ISet<T> s); bool Find(ISet<T> s, T e); int Length(ISet<T> s); }

Below is a client module:

public class MySet<T>:ISet<T>{ public ISet<T> Add(ISet<T> s, T e) { } public ISet<T> Remove(ISet<T> s, T e) { } public ISet<T> Empty() { } public bool IsEmpty(ISet<T> s) { } public bool Find(ISet<T> s, T e) { } public int Length(ISet<T> s) { } }

And below is a test module:

public class MySetTest{ public void AddTest() { } public void RemoveTest() { } public void EmptyTest() { } public void IsEmptyTest() { } public void FindTest() { } public void LengthTest() { } }

Advantages:

Facilitates code reuse : Both tests and clients can make use of the same implementation module.

: Both tests and clients can make use of the same implementation module. Allows multiple implementations : The client can select an appropriate implementation module without modification of its code.

: The client can select an appropriate implementation module without modification of its code. Ensures that required functions are effectively provided

Allows several developers to work on different modules concurrently

Allows to test modules independently

Large projects become easier to monitor and to control

Predicates and Types

A type is a subset of the values of the language. For example: The bool type is the set { true , false }.

A predicate is a function taking any possible value and returning a boolean:

A predicate is the characteristic function of a type.

Predicates are usually the sign of a language with dynamical type checking.

Contracts

In C#, according to the official documentation, contracts provide a way to specify preconditions, postconditions, and object invariants to the code. Preconditions are requirements that must be met when entering a method or property. Postconditions describe expectations at the time the method or property code exits. Object invariants describe the expected state for a class that is in a good state.

Code contracts include classes for marking the code, a static analyzer for compile-time analysis, and a runtime analyzer. The classes for code contracts can be found in the System.Diagnostics.Contracts namespace.

The benefits of code contracts include the following:

Improved testing : Code contracts provide static contract verification, runtime checking, and documentation generation.

: Code contracts provide static contract verification, runtime checking, and documentation generation. Automatic testing tools : You can use code contracts to generate more meaningful unit tests by filtering out meaningless test arguments that do not satisfy preconditions.

: You can use code contracts to generate more meaningful unit tests by filtering out meaningless test arguments that do not satisfy preconditions. Static verification : The static checker can decide whether there are any contract violations without running the program. It checks for implicit contracts, such as null dereferences and array bounds, and explicit contracts.

: The static checker can decide whether there are any contract violations without running the program. It checks for implicit contracts, such as dereferences and array bounds, and explicit contracts. Reference documentation: The documentation generator augments existing XML documentation files with contract information. There are also stylesheets that can be used with Sandcastle so that the generated documentation pages have contract sections.

Typed Programming

A type system is a formal method applied to a program which aims at classifying elements of the program with types so as to guarantee some correctness of its behavior.

Depending on the language and its compiler, type systems may come in different flavours:

Dynamic type checking : All verifications are done at runtime, no type annotations necessary (Racket, Python, Ruby).

: All verifications are done at runtime, no type annotations necessary (Racket, Python, Ruby). Static type checking: All verifications are done at compile time, type annotations are either required (C#, C, Java) or inferred (OCaml, Haskell).

Advantages:

The types are checked statically.

The Typed type system is pretty expressive.

Interfaces and Abstraction

Abstract data types are:

An abstract and independent representation of a component

Possibly multiple different implementations for the same abstraction

Possibly multiple clients for the same abstraction

Signatures in Other Languages

Interfaces in Java:

interface Set<T>{ Set<T> add(Set<T> set, T e); Set<T> remove(Set<T> set, T e); Set<T> empty(); boolean is_empty(Set<T> set); boolean find(Set<T> set, T e); int length(Set<T> set); }

Signatures in OCaml:

module type SET = sig type ’a set val add : ’a set → ’a → ’a set val remove : ’a set → ’a → ’a set val empty : unit → ’a set val is_empty : ’a set → bool val find : ’a set → ’a → bool val length : ’a set → int end

History