nventive se met à l’Open-Source et c’est très cool!
Au cours des derniers jours, j’ai personnellement participé à la publication en open-source de code qu’on a depuis un certain temps chez nventive. Et c’est très cool!
Ce n’est pas la première fois que nventive publie en open-source, mais la dernière fois, ça remonte quand même à longtemps où Umbrella v0.9
avait été publié.
Uno – le nom de l’Open-Source chez nventive
Tout d’abord, il faut savoir que tout ce qui est publié ou le deviendra sera sous le nom « Uno« . C’est le nom qui a été choisi pour tout ce qui est open-source. Le nom Umbrella existe toujours mais est plutôt utilisé pour les projets internes désormais.
Pour le moment, nous avons publié 4 projets:
Uno.Core
: Une libraire qui contient BEAUCOUP de stock. Ce sont un grand nombre de petits utilitaires pour vous faciliter la vie avec .NET. Allant de simples extension methods jusqu’aux outils de gestion de concurrence (threading), en passant par le logging, bien sûr!
Frameworks:.NETStandard 2.0
,.NETFramework 4.6
&UAP 10.0
-
Uno.SourceGeneration
: Ça, c’est big. Ou, plutôt, ça permet de faire des choses big : nventive a réussi à mettre dans un seul package tout ce qui est nécessaire pour coder un GÉRÉNATEUR DE CODE avec Roslyn.
Frameworks:.NETFramework 4.6
(ça roule dans Roslyn!) -
Uno.RoslynHelpers
: Petit projet qui a pour but de fournir des outils pour faciliter l’écriture de code Roslyn. C’est vraiment tout petit et ce n’est absolument pas nécessaire pour créer un générateur de code en utilisantUno.SourceGeneration
.
Frameworks:.NETFramework 4.6
(ça roule dans _Roslyn!) -
Uno.CodeGen
: Ça, c’est mon bébé. Une première version fonctionnelle devrait être disponible dans les prochains jours. Il s’agit d’un générateur de code (qui utiliseUno.SourceGeneration
) qui permet de faciliter l’écriture d’ENTITÉES IMMUABLES et de la GESTION D’ÉGALITÉ dans votre code C#. Voir un exemple de code ci-bas.
Uno.SourceGeneration
Pour créer un générateur de source, suivre les étapes suivantes :
- Créer un projet de type librairie en
.net 4.6
- Ajouter une référence nuget à
Uno.SourceGeneration
. - Faire les modifications dans les fichier
.csproj
tel que décrit sur la page github - Créer une classe publique qui hérite de
SourceGenerator
. Ne pas mettre de constructeur, elle sera créée automatiquement par les tâches MSBuild de génération. - Utilisez le
context
reçu en paramètre pour analyser le projet existant et générer du code.
Fonctionnement des tâches de génération
Des tâches spéciales s’injectent dans le processus de MsBuild pour intercepter certaines opérations. Roslyn est utilisé intensivement pour l’analyze des sources, bien sûr!
- Tout d’abord, les tâches de génération vont charger les différents générateurs et identifier l’ordre d’exécution. En effet, les attributs
[GenerateBefore]
et[GenerateAfter]
permettent de spécifier des dépendances entre les générateurs. Ça permet d’effectuer une génération basée sur le résultat compilé d’une autre, ce qui peut être très pratique. - Ensuite, Roslyn est utilisé pour compiler le code du projet.
- Le résultat de la compilation (partielle ou complète, selon s’il y a des erreurs) sera passé au générateur.
- Le générateur produira du code qui sera à son tour intégré à la compilation. Les fichiers de code générés seront également copiés sur disque pour la vraie compilation.
À savoir…
- Dans la version actuelle, il faut une première compilation pour que le code généré soit disponible pour l’auto-complete de VisualStudio. Quelques fois il faut le forcer un peu. Nous travaillons actuellement à enlever cette limitation.
- Il n’est pas possible de modifier du code, on peut simplement en ajouter. Le plus simple est généralement à travers des classes partielles. Par modifier, je veux dire qu’il n’est pas possible de faire ne sorte d’enlever du code analysé par Roslyn. Vous pouvez ajouter du code à la compilation, mais pas en enlever.
- Une protection a été mise en place pour empêcher une recompilation de tout les projets dès que ces derniers ont un générateur. Si la compilation est identique à la compilation précédente, le fichier sur disque n’est pas remplacé, ce qui permet à MsBuild de ne pas recompiler inutilement le projet. Il va sans dire qu’il est nécessaire que votre générateur produise toujours le même résultant. Bref, évitez de mettre la date ou l’heure dans le code généré.
Uno.CodeGen
Le projet Uno.CodeGen
est un regroupement des différents générateurs de code qui seront développés pour le Projet Uno. Pour le moment, il y a 2 générateurs :
ImmutableGenerator
: Utilisé pour simplifier le code d’entités immuables.EqualityGenerator
: Utilisé pour automatiser le code nécessaire pour implémenter le.Equals()
,.GetHashCode()
et implémenter les interfacesIEquatable<t>
,IKeyEquatable
etIKeyEquatable</t><t>
.
Ces 2 générateurs ont été mis en place pour diminuer les erreurs dûs à la répétition du code nécessaire pour effectuer ces fonctions.
Normalement vous devriez être en train de vous demander c’est quoi les interfaces
IKeyEquatable
etIKeyEquatable</t><t>
. Il s’agit simplement des interfaces définies dansUno.Core
et qui servent à comparer les clés de 2 entités. Une version mise à jour d’une entité aura une équalité de clé (key equals) alors qu’elles ne seront pas equals.Le code pour l’égalité de clé ne sera généré que si vous utilisez
Uno.Core
.
Le générateur d’entités immuables
Qu’est-ce qu’une entité immuable ? C’est une entité qui ne peut pas changer après sa création. Si vous voulez la modifier, vous devez en créer une nouvelle instance qui contient la modification. Travailler avec des entités immuables apporte un bon nombre d’avantages, spécialement quand on fait du code fonctionnel et du multi-threading.
Pour définir une entité immuable, voici d’abord les requis:
- Doit être une classe (les
struct
ne sont pas acceptés). - Aucun champ (field) non-static n’est autorisé.
- Toutes les propriétés non-statiques doit être
{ get; }
sans aucun setter de défini. Vous pouvez mettre des valeurs par défaut, ex:
public string City { get; } = "Unknown";
- Le constructeur par défaut n’est pas autorisé. D’ailleurs vous n’avez pas besoin d’en définir un (c’est permis, toutefois).
- Vous devez ajouter l’attribut
[GenerateImmutable]
sur la classe.
Seront générés pour vous :
* Une classe T.Builder
, soit un builder muable pour votre classe.
* Une propriétée statique T.Default
qui contiendra une version par défaut de votre classe.
* Chaque propriété sera copiée dans le builder pour en avoir une version modifiable.
* Des méthodes .WithXXX()
seront générés pour chaque propriétés (où XXX est remplacé par le nom de la propriété), permettant de manière fluente de modifier l’état du builder.
* Des méthodes d’extension sur la classe originale permettront également de faire .WithXXX()
et d’obtenir un builder. Il s’agit de méthodes d’extension pour éviter un « null reference exception » quand la source est null
.
* Des conversions implicites de T
vers T.Builder
et inversement.
Voici un exemple de code :
[GenerateImmutable]
public class User
{
public string Id { get; } = "";
public string Email { get; } = null;
public string Name { get; } = "Unknown";
}
[...]
// On peut d'abord utiliser le .Default
// qui donne les valeurs par défaut des propriétés
User u1 = User.Default;
// On peut aussi créer à partir du builder
User u2 = new User.Builder { Id = "123", Email="a@b.c" };
// Pour obtenir une version modifiée, utilisez les .WithXXX()
User u2bis = u2.WithName("Jean Dit");
// Il y a aussi des optimisations qui permettent
// de chaîner les .WithXXX() sans que les entités
// intermédiaires ne soient crées...
User u3 = User.Builder
.WithId("234")
.WithEmail("t@v.com")
.WithName("Aristote");
Le générateur d’égalité
Le générateur d’égalité a un fonctionnement similaire au générateur d’entités immuables, c’est-à-dire qu’il suffit d’ajouter un attribut pour le déclencher.
Il peut être utilisé autant sur une class
que sur une struct
. Bien que l’immuabilité ne soit pas requis, idéalement ses membres ne devraient pas changer ou les résultats pourraient devenir étranges.
Voici les requis :
- Au moins une propriété (ou champ) devrait avoir l’attribut
[EqualityHash]
or[EqualityKey]
, sinon le HashCode aura toujours la même valeur. - Éviter des types dont les valeurs exposées sont non-constants comme des
IEnumerable</t><t>
À vos risques
Je crois qu’il est important de rappeler qu’il s’agit de code qui n’est couvert par aucune garantie de quelque manière que ce soit. Je ne peux garantir que le code présenté dans cet article fonctionnera avec les versions futures.
Cependant, comme vous avez accès aux sources, libre à vous de le forker et d’en faire ce que vous voulez – tant que vous respectez la license.
Faites-nous des commentaires!
Les PULL-REQUESTS sont acceptés. Si vous voyez des problèmes avec le code publié ou souhaitez faire des amélioration, n’hésitez pas, c’est là pour ça!
Laisser un commentaire