The .NET framework offers a whole range of so-called generic classes.
The best-known example is probably the <type> List, which allows you to create strongly typed lists.
This mechanism is powerful, and we can also define generic classes in our developments.
But what is a generic class?
In fact, a generic class is a template of behaviors that I can apply to multiple situations, on multiple objects.
To explain this, we will use a simple example throughout the tutorial.
I work in a company that makes motor vehicles. Vehicles for which all associations are possible! (All chassis can accommodate all engines, and all wheel models).
Let’s add that I’m lazy, and that I do not want to rewrite the entire definition of a vehicle to each model that I have to create (or each possible association),
Nevertheless, I must control, the definition of associations (engine, chassis, wheels).
Why not create a car template? That is the question of the day …
To do this, I have to think as follows:
What are the similar behaviors?
A vehicle can:
roll,
honking,
Behave on the road,
From this, I know what will be implemented in a common way …
My generic class is limited to making calls to types handled (call the engine, wheels and chassis if necessary).
Basıc defınıtıon of my class vehıcle …
Note the following syntax:
1 2 3 |
public class Vehicle <ClassChassis, ClassMotor, ClassWheels> |
Quickly two problems arise:
A compiler error: I can not instantiate value in the constructor for declared types,
I do not know the accessible operations for the defined types, and therefore, I can not use their behaviors …
ClassChassis
ClassMotor
ClassWheels
Type constraints
To overcome this, we will specify the generic types declared by precision clauses (as in SQL, when we filter with a where clause).
In .NET this notion of precision is presented as type constraints.
These constraints are declared just after the declaration of the class, according to the following pattern:
where <type>: constraint [, constraint]
We’ll see the list of possible constraints right after our example,
In our code it gives:
Above, the constraint informs us that the types:
ClassChassis
ClassMotor
ClassWheels
have an empty constructor.
This allows me to instantiate in the manufacturer instances of engine, chassis and wheels.
where T : struct | The argument of type T must be a value type, not nullable (System.Int32, … or a structure defined in the code), the type System.String is not allowed because it can take null as a value. |
where T : class | The argument of type T must be a reference type, a class. |
where T : new() | The argument of type T must have an empty constructor, if several constraints are defined, this must be the last one. |
where T : <className> | The argument of type T must be of type <ClassName>, or derive from it, where <ClassName> is also a generic type. Example: public class Gen <T1, T1parent> where T1: T1parent |
where T : <InterfaceName> | The T-type argument must implement the <InterfaceName> interface |
where T : ClassName | The argument of type T must be of type ClassName, or derive from it. |
Back to our example, we need to know the manipulable members in our generic class, therefore, we need to define Interfaces that implement my classes (Wheels, Engine and Chassis). I can also define classes that inherit my classes (abstract or not), depending on the context.
To do this, and because I am clean in my architectures, I will create another assembly, which contains only the interfaces of my objects. In the latter, I will define three interfaces:
IMotor,
IWheel,
IChassis,
I will use these definitions to manipulate my types within my generic class.
IMotor defines the following methods and properties:
<<Interface>> IMotor
1 2 3 4 5 6 7 8 9 |
+ Cylinder: Int32 + Power: Int32 + Couple: Int32 + SharedReport: Int32 + MonteRegime(): String + ChangeBackReport(monteDescend: bool): String |
IRoue définit les méthodes et propriétés suivantes :
<<Interface>> IWheel
1 2 3 4 5 |
+ ModelName: String + Size:Int + Turned(speed: Int32) : String |
IChassis defines the following methods and properties:
<<Interface>> IChassis
1 2 3 4 5 |
+ ModelName: String + Behavior (typeRoute: String): String |
The definition of my class will take this syntax:
1 2 3 4 5 6 7 |
class Vehicle<ClassChassis, ClassMotor, ClassWheels> where ClassChassis : IChassis, new() where ClassMotor : IMotor, new() where ClassWheels :IWheel, new() ...... |
I was asked one day to design a data access layer (DAL) mechanism, but that allows to no longer write a single line of code, while having the mechanism of access to data and relational object mapping.
This generic mechanism basically automates the generation of queries to a SQL CE database for the following operations:
Creating the table,
Inserting data,
Data update,
Select all
Select according to criteria (dynamically managed).
A kind of framework like Ibatis but without the complexity of XML parameterization.
Its mechanism is based on two principles:
Decoration of classes to be handled by System.Attributes “home”,
The generic class can thus be based on these attributes to have information allowing to manipulate the database in connection with the classes,
The types to specify for the generic class are:
The type of the object to handle,
The type that makes it possible to have search criteria elements
The type representing an object list.