Introduction à Mono.Cecil : Implémenter INotifyPropertyChanged
Vous allez probablement m’en vouloir à force, mais j’ai décidé de continuer mes expériences autour d’INotifyPropertyChanged
. Cette fois-ci, en utilisant Mono.Cecil pour faire un peu d’IL rewriting. Où comment tisser un aspect sans utiliser de framework AOP. Comparée à celle basée sur PostSharp.Laos, cette solution a un inconvénient majeur : elle est plus roots.
En revanche, aucune autre méthode à ma connaissance ne permet d’obtenir un tissage aussi fin. Par fin, j’entends spécifique, propre, non pollué par le code que génèrent les outils d’AOP classiques pour supporter les mécanismes d’interception. Donc les performances seront à la clé puisqu’une fois l’assembly retravaillé avec Cecil, il ne sera plus possible de faire la différence entre son code IL et celui qui aurait été généré si nous avions implémenté INotifyPropertyChanged
à la main. Le développement aura juste été un peu plus cher…
Même si la plupart du temps vous préfèrerez sans doute bénéficier de l’abstraction offerte par les outils d’AOP classiques, vous trouverez peut-être excitant de jouer avec Cecil pour déveloper des aspects bas niveau en mettant les mains dans le camboui. (Vous pouvez aussi trouver ça naze au point d’en bailler, hein.)
Bref, ce post va plus ressembler à un article sur Cecil qu’à un article sur INotifyPropertyChanged
. Il y aura probablement deux parties, mais ce n’est pas une promesse :
- Part 1 : première version d’un transformer utilisant Cecil pour implémenter
INotifyPropertyChanged
(cet article) - Part 2 : version améliorée du transformer lançant l’évènement de notification uniquement lorsque la valeur affectée à la propriété est différente
Eventuellement, je fournirai quelques pistes pour lancer automatiquement ce genre de transformations en post-build dans la seconde partie, si seconde partie il y a.
Mais commençons par le commencement, en prenant pour victime la Person
des articles précédents. Le but est de pouvoir écrire :
[NotifyPropertyChanged] public class Person { private string name; public string Name { get { return this.name; } set { this.name = value; } } }
et d’obtenir au final une classe Person
implémentant INotifyPropertyChanged
et capable donc de notifier par l’événement PropertyChanged
les changements de ses propriétés.
Cecil permet de charger un assembly, de l’inspecter et de le modifier éventuellement avant de le sauvegarder, de le copier, etc. L’idée consiste donc à :
- Compiler le code ci-dessus normalement, afin d’obtenir un assembly
- Lancer un transformer, qui utilise Cecil afin de charger cet assembly
- Modifier l’assembly, toujours avec Cecil, pour y injecter l’implémentation d’
INotifyPropertyChanged
en manipulant le code CIL. - Sauvegarder l’assembly modifié
Le transformer
Dans cette première version, imaginons quelque chose de naïf : un programme en ligne de commande, qui prend en argument le chemin de l’assembly à instrumenter :
private static void Main(string[] args) { if (args.Length == 0) { Console.WriteLine("Usage: transformer <assembly file>"); return; } new Transformer(path).Run(); }
D’ailleurs, je vais continuer à être naïf en laissant de côté la gestion du logging, des exceptions, etc. Les snippets présentés ici n’en seront que plus lisibles, à défaut d’être solides.
Commençons par une vue d’ensemble des étapes nécessaires à la transformation des assemblies, que nous détaillerons ensuite. (Vous aimez les opcodes ?)
- Chargement de l’assembly à instrumenter
- Identification des types à modifier : on recherche ceux qui sont taggés par notre attribut
[NotifyPropertyChanged]
- Transformation de chacun de ces types pour leur faire supporter
INotifyPropertyChanged
- Sauvegarde de l’assembly modifié
Le schéma ci-dessous met en avant les différentes étapes de la transformation :
- [#1] Implémentation proprement dite de l’interface
INotifyPropertyChanged
- [#2] Ajout d’une fonction « helper » pour le déclenchement de l’évènement
PropertyChanged
- [#3] Instrumentation des setters, en utilisant la méthode précédente pour lancer les notifications
Voyons comment implémenter ça. Rappelez-vous, tout commence par :
new Transformer(path).Run();
Le constructeur (qui en fait sans doute un peu trop) :
public Transformer(string assemblyPath) { this.assemblyPath = assemblyPath; this.assembly = AssemblyFactory.GetAssembly(assemblyPath); this.assembly.MainModule.LoadSymbols(); this.assembly.MainModule.FullLoad(); }
Assez trivial : on laisse tout le travail à Cecil. Son AssemblyFactory
nous permet de récupérer la définition de l’assembly à modifier. Afin de conserver les sequence points et permettre de debuguer l’assembly modifiée, on charge les infos de debug. L’appel à la méthode FullLoad
permet de charger en mémoire toute la représentation du module principal, afin de pouvoir la modifier par la suite.
Petite parenthèse introductive : Plusieurs fois, dans les méthodes que je vais présenter par la suite, on aura besoin de récupérer une TypeReference
, à partir d’un Type
. Lorsque ce sera le cas, on utilisera une des deux méthodes suivantes :
private TypeReference GetReference(Type type) { TypeReference typeReference = this.assembly.MainModule.TypeReferences[type.FullName] ?? this.assembly.MainModule.Import(type); return typeReference; } private TypeReference GetReference<T>() { return GetReference(typeof (T)); }
Leur fonctionnement est simple : si on a déjà chargé le type dans le module principal de l’assembly en cours de modification, on retourne directement la TypeReference
correspondante, sinon on importe le type pour créer la référence. Car Cecil fait bien la distinction (qui existe au niveau CIL) entre les définitions et les références pour les différents éléments du code (types, fields, methods, etc.)
Lorsqu’on veut modifier une méthode, par exemple, on va travailler sur sa définition. Mais si on désire simplement l’appeler, il nous faut juste sa référence. Ce modèle, plus juste que celui proposé par l’API de réflexion de la BCL peut sembler troublant au premier abord, mais s’avère au final plus puissant puisqu’il colle directement avec les spécifications du langage intermédiaire. Fin de la parenthèse.
Bien, on a de quoi commencer l’autopsie du Transformer
, dont le nom est plus que douteux… La méthode Run
semble être un point de départ judicieux pour notre analyse :
public void Run() { foreach (TypeDefinition type in GetTypesToWeave()) { MethodReference firePropertyChangedMethod = WeaveINotifyPropertyChanged(type); WeaveEventTriggers(type, firePropertyChangedMethod); } this.assembly.MainModule.SaveSymbols(); AssemblyFactory.SaveAssembly(this.assembly, this.assemblyPath); }
La méthode GetTypesToWeave
permet de retourner tous les types à modifier, c’est à dire ceux qui sont décorés par notre attribut [NotifyPropertyChanged]
. Ce dernier devra donc être défini dans un assembly quelconque, référencé à la fois par le code client – celui qui contient les types à instrumenter – et par notre transformer. Chacun des types est ensuite transformé par les appels successifs aux méthodes WeaveINotifyPropertyChanged
et WeaveEventTriggers
.
La sauvegarde des modifications s’effectue elle aussi en plusieurs étapes :
- On indique qu’on souhaite conserver les symboles de debugging
- On sauvegarde notre travail en remplaçant l’assembly original par celui que l’on vient d’instrumenter.
Jusqu’ici, tout va bien, comme qui dirait. Mais creusons un peu plus. Tout d’abord, le code de la méthode GetTypesToWeave
qui nous permet de retourner les types de l’assembly à modifier. Il s’agit essentiellement d’alléger la méthode Run
en évitant les imbrications et en isolant la logique de recherche dans une autre méthode :
private IEnumerable<TypeDefinition> GetTypesToWeave() { string attributeFullName = typeof (NotifyPropertyChangedAttribute).FullName; foreach (TypeDefinition type in this.assembly.MainModule.Types) { foreach (CustomAttribute attribute in type.CustomAttributes) { if (attribute.Constructor.DeclaringType.FullName == attributeFullName) { yield return type; break; } } } }
Notez qu’on utilise pour cela un bloc itérateur. Le reste pourrait se passer de commentaire : on retourne les types marqués par l’attribut [NotifyPropertyChanged]
en basant la recherche sur le fullname des attributs.
Passons maintenant à la modification des types. Rappelez-vous, il y a deux étapes principales :
- Implémentation de
INotifyPropertyChanged
(méthodeWeaveINotifyPropertyChanged
) - Instrumentation des setters (méthode
WeaveEventTriggers
)
Implémentation de INotifyPropertyChanged
L’implémentation proprement dite de INotifyPropertyChanged
est une tâche que l’on peut aisément redécomposer. Cette redécomposition est d’ailleurs assez visible dans la méthode WeaveINotifyPropertyChanged
que je propose :
private MethodReference WeaveINotifyPropertyChanged(TypeDefinition type) { TypeReference iNotifyPropertyChangedType = GetReference<INotifyPropertyChanged>(); type.Interfaces.Add(iNotifyPropertyChangedType); FieldReference eventField = WeaveEvent(type); return WeaveEventFiringHelper(eventField, type); }
La première étape consiste à récupérer une TypeReference
correspondant à cette interface pour l’ajouter à la liste des interfaces du type que l’on modifie. Simple, mais insuffisant : il faut également implémenter l’interface. Dans le cas d’INotifyPropertyChanged
, on doit ajouter un évènement public PropertyChanged
au type. C’est la méthode WeaveEvent
qui s’en chargera. (Etape [#1] du schéma)
La méthode WeaveEventFiringHelper
, quant à elle, permet d’ajouter une méthode FirePropertyChanged
au type. Quelque chose équivalent à :
private void FirePropertyChanged(string propertyName) { if (PropertyChanged != null) { PropertyChanged(this, new PropertyChangedEventArgs(propertyName)); } }
C’est l’étape [#2] de la transformation, également signalée dans le schéma précédent. La référence vers cette méthode est retournée pour permettre son appel plus tard à partir des setters des propriétés.
Dès que vous aurez terminé de bailler, nous détaillerons la première étape qui consiste à ajouter l’évènement PropertyChanged
au type. Soit la méthode WeaveEvent
:
private FieldReference WeaveEvent(TypeDefinition type) { TypeReference eventHandlerTypeRef = GetReference<PropertyChangedEventHandler>(); MethodReference combineMethodRef = this.assembly.MainModule.Import(combineMethodBase); MethodReference removeMethodRef = this.assembly.MainModule.Import(removeMethodBase); const string eventName = "PropertyChanged"; FieldDefinition eventField = new FieldDefinition("PropertyChanged", eventHandlerTypeRef, FieldAttributes.Private); type.Fields.Add(eventField); EventDefinition eventDefinition = new EventDefinition(eventName, eventHandlerTypeRef, 0); eventDefinition.AddMethod = CreateEventMethod(string.Format("add_{0}", eventName), combineMethodRef, eventField); eventDefinition.RemoveMethod = CreateEventMethod(string.Format("remove_{0}", eventName), removeMethodRef, eventField); type.Methods.Add(eventDefinition.AddMethod); type.Methods.Add(eventDefinition.RemoveMethod); type.Events.Add(eventDefinition); return eventField; }
Le layout n’est pas terrible, j’espère que vous ne m’en tiendrez pas rigueur.
Petit rappel : Lorsqu’on est au niveau CIL, on a une bonne visibilité sur ce qu’un langage de plus haut niveau comme C# peut nous cacher. Les évènements constituent un bon exemple. En C#, la déclaration d’un event est simple :
public event PropertyChangedEventHandler PropertyChanged;
Au niveau IL, le compilateur C# va en fait générer un champ privé encapsulé par une propriété un peu spéciale de type event
qui possède les méthodes « add
» et « remove
« . Note : même si ce n’est pas souvent utilisé, sachez qu’à partir de C# il est possible de faire apparaitre ces méthodes pour éventuellement intercepter les abonnements / désabonnements à l’évènement.
Pour ajouter l’évènement PropertyChanged
à notre type, nous avons donc :
- Ajouté un champ d’instance privé de type
NotifyPropertyChangedEventHandler
- Ajouté l’évènement (pseudo propriété), qui utilise deux méthodes :
add_PropertyChanged
etremove_PropertyChanged
- Créé les deux méthodes précédentes, qui utilisent respectivement les méthodes statiques
Delegate.Combine
etDelegate.Remove
pour ajouter et supprimer les handlers au champ d’instance
Puisque l’IL n’est pas très lisible, voici la traduction C# de ces deux méthodes :
public void add_PropertyChanged(PropertyChangedEventHandler value) { this.PropertyChanged = (PropertyChangedEventHandler) Delegate.Combine(this.PropertyChanged, value); } public void remove_PropertyChanged(PropertyChangedEventHandler value) { this.PropertyChanged = (PropertyChangedEventHandler) Delegate.Remove(this.PropertyChanged, value); }
Dans notre exemple précédent, on délègue la création de ces deux méthodes à CreateEventMethod
dont voici la définition :
private MethodDefinition CreateEventMethod(string methodName, MethodReference delegateMethodReference, FieldReference eventField) { const MethodAttributes attributes = MethodAttributes.Public | MethodAttributes.HideBySig | MethodAttributes.Final | MethodAttributes.SpecialName | MethodAttributes.NewSlot | MethodAttributes.Virtual; TypeReference eventHandlerTypeRef = GetReference<PropertyChangedEventHandler>(); TypeReference voidType = GetReference(typeof (void)); MethodDefinition methodDef = new MethodDefinition(methodName, attributes, voidType); methodDef.Parameters.Add(new ParameterDefinition(eventHandlerTypeRef)); CilWorker cilWorker = methodDef.Body.CilWorker; cilWorker.Emit(OpCodes.Ldarg_0); cilWorker.Emit(OpCodes.Ldarg_0); cilWorker.Emit(OpCodes.Ldfld, eventField); cilWorker.Emit(OpCodes.Ldarg_1); cilWorker.Emit(OpCodes.Call, delegateMethodReference); cilWorker.Emit(OpCodes.Castclass, eventHandlerTypeRef); cilWorker.Emit(OpCodes.Stfld, eventField); cilWorker.Emit(OpCodes.Ret); return methodDef; }
Notez l’API intuitive que propose Cecil pour ce genre de choses : on instancie une définition de l’élément que l’on cherche à ajouter au type (FieldDefinition
, EventDefinition
, MethodDefinition
, etc.), on la configure si besoin, et on l’ajoute à la définition parente. Pour ce qui est de l’émission d’IL pour le corps des méthodes, il suffit d’obtenir un CilWorker
et de créer séquentiellement les instructions en utilisant les codes d’opérations du langage intermédiaire.
Voilà pour la création de l’évènement PropertyChanged
. Notre type est à présent une implémentation valide de l’interface INotifyPropertyChanged
, puisque qu’il respecte le contrat imposé par cette dernière : exposer un évènement PropertyChanged
, de type PropertyChangedEventHandler
. (Etape [#1] du schéma)
Il ne reste plus qu’à déclencher cet évènement, et pour cela, il est habituellement préférable de prévoir une méthode spécifique qui sera appellée à partir des setters (Etape [#2]). Nous l’appellons ici FireNotifyPropertyChanged
et c’est la méthode WeaveEventFiringHelper
de notre transformer qui se charge de l’ajouter au type instrumenté :
private MethodReference WeaveEventFiringHelper(FieldReference eventField, TypeDefinition type) { MethodReference invokeMethodRef = this.assembly.MainModule.Import(invokeMethodBase); MethodReference argsCtorMethodRef = this.assembly.MainModule.Import(constructorMethodBase); TypeReference stringTypeRef = GetReference<string>(); TypeReference voidTypeRef = GetReference(typeof (void)); MethodDefinition firePropertyChanged = new MethodDefinition("FirePropertyChanged", MethodAttributes.Private, type); firePropertyChanged.ReturnType = new MethodReturnType(voidTypeRef); ParameterDefinition propertyNameParameter = new ParameterDefinition("propertyName", 0, ParameterAttributes.None, stringTypeRef); firePropertyChanged.Parameters.Add(propertyNameParameter); CilWorker cilWorker = firePropertyChanged.Body.CilWorker; Instruction returnInstruction = cilWorker.Create(OpCodes.Ret); cilWorker.Emit(OpCodes.Ldarg_0); cilWorker.Emit(OpCodes.Ldfld, eventField); cilWorker.Emit(OpCodes.Brfalse_S, returnInstruction); cilWorker.Emit(OpCodes.Ldarg_0); cilWorker.Emit(OpCodes.Ldfld, eventField); cilWorker.Emit(OpCodes.Ldarg_0); cilWorker.Emit(OpCodes.Ldarg_1); cilWorker.Emit(OpCodes.Newobj, argsCtorMethodRef); cilWorker.Emit(OpCodes.Callvirt, invokeMethodRef); cilWorker.Append(returnInstruction); type.Methods.Add(firePropertyChanged); return firePropertyChanged; }
La FieldReference
récupérée en argument est celle retournée par la méthode précédente, WeaveEvent
, qui a ajouté l’évènement au type. Elle représente d’ailleurs le champ de l’évènement, qui doit être utilisé pour l’invocation. La traduction C# de la méthode générée dynamiquement correspond à :
private void FirePropertyChanged(string propertyName) { if (this.PropertyChanged != null) { this.PropertyChanged.Invoke(this, new PropertyChangedEventArgs(propertyName)); } }
C’est complètement équivalent au code de l’étape [#2] dans le premier schéma.
Instrumentation des setters
A ce stade, pour résumer, nous avons :
- [#1] Implémenté
INotifyPropertyChanged
en ajoutant un évènementPropertyChanged
au type - [#2] Créé une méthode « helper » pour déclencher l’évènement
Très logiquement, il ne reste plus qu’à appeller cette dernière à partir de tous les setters des propriétés du type que l’on modifie (étape [#3]). Cette fois-ci, on ne va pas créer un nouveau champ, un nouvel évènement ou une nouvelle méthode, on va modifier le code CIL du corps de méthodes existantes. Car un setter de propriété est une méthode traditionnelle.
Examinons la méthode WeaveEventTriggers
du transformer, qui est responsable de cette tache :
private static void WeaveEventTriggers(TypeDefinition type, MethodReference firePropertyChanged) { foreach (PropertyDefinition property in type.Properties) { MethodDefinition method = property.SetMethod; if (method == null) { continue; } Instruction retInstruction = method.Body.Instructions[method.Body.Instructions.Count - 1]; CilWorker cilWorker = method.Body.CilWorker; cilWorker.InsertBefore(retInstruction, cilWorker.Create(OpCodes.Ldarg_0)); cilWorker.InsertBefore(retInstruction, cilWorker.Create(OpCodes.Ldstr, property.Name)); cilWorker.InsertBefore(retInstruction, cilWorker.Create(OpCodes.Call, firePropertyChanged)); } }
Encore une fois, l’implémentation est naïve et nous verrons s’il est possible de l’améliorer plus tard. La logique est simple :
- On itère sur toutes les propriétés du type. (Pas très malin…)
- On récupère la définition de la méthode du setter.
- On récupère une référence vers la dernière instruction du corps du setter
- On injecte le code CIL correspondant à l’appel de la méthode
FirePropertyChanged
avant la dernière instruction pour que la notification soit lançée à la fin de chaque modification.
En gros, si le setter en C# est du genre :
public void set_Name(string value) { this.name = value; }
On le transforme en :
public void set_Name(string value) { this.name = value; FirePropertyChanged("Name"); }
Note : combineMethodBase
, invokeMethodBase
, constructorMethodBase
et removeMethodBase
, référencés dans les méthodes du Transformer
, sont des membres statiques initialisés de la façon suivante :
private static readonly MethodBase combineMethodBase; private static readonly MethodBase invokeMethodBase; private static readonly MethodBase constructorMethodBase; private static readonly MethodBase removeMethodBase; static Transformer() { var type = typeof (Delegate); combineMethodBase = type.GetMethod("Combine", new[] {type, type}); removeMethodBase = type.GetMethod("Remove"); type = typeof (PropertyChangedEventHandler); invokeMethodBase = type.GetMethod("Invoke"); type = typeof (PropertyChangedEventArgs); constructorMethodBase = type.GetConstructor(new[] {typeof (string)}); }
Les MethodBase
sont des types de l’API d’introspection de System.Reflection
. Il est facile de récupérer les MethodReference
correspondantes (API Cecil) en utilisant la méthode Import
d’une ModuleDefinition
.
Conclusion intermédiaire
Voilà une première version du « transformer » capable d’instrumenter un assembly existant pour faire implémenter INotifyPropertyChanged
à certains de ses types automatiquement. Les améliorations possibles sont nombreuses, et je ne vais en citer que quelques unes :
- Meilleure utilisation de Cecil : après tout, je découvre et suis donc loin d’être un expert
- Meilleure intégration au process de build, en utilisant une custom task MSBuild par exemple
- Version complète de l’instrumentation : l’évènement
PropertyChanged
ne doit être déclenché que lorsque la valeur affectée à la propriété est différente - Gestion des exceptions et du logging (évidemment)
- Etc.
Cecil est un outil extrêmement puissant et facile d’utilisation. Pour info, la mise en place de cette version du Transformer
n’a pas dû me prendre 2 heures, alors que la femme de ma vie me parlait je faisais autre chose en même temps, et que je découvrais complètement l’API non documentée. Il est préférable par contre de connaître le CIL, même si ILdasm ou Reflector peuvent vous assister efficacement. Enfin, et presque paradoxalement, la prise en main de Cecil vous paraitra déroutante si vous avez l’habitude d’utiliser System.Reflection
et System.Reflection.Emit
.
Au final ma première expérience est définitivement positive, et je n’hésiterai pas dans le futur à recourir à Cecil au besoin. Je pense d’ailleurs qu’en collaboration avec une couche facilitant son utilisation dans un context post-build, on aurait quelque chose d’assez terrible, probablement plus simple à utiliser que PostSharp.Core par exemple pour des tâches bien spécifiques.
Notez tout de même que l’exemple d’utilisation de Cecil présenté ici est un peu tordu, et qu’il y a bien d’autres (meilleurs) moyens d’exploiter ses capacités.
Je suis tout a fait d’accord que l’implementation d’INotifyPropertyChanged avec Laos ne genere pas un code tres elegant… Pour une utilisation serieuse, il vaut mieux utiliser le bas niveau.
Cecil et la couche inferieure de PostSharp Core sont des produits tres similaires, qui ont ete developpes au meme moment sans contact mutuel. L’implementation de PostSharp serait tres similaire.
Au-dela de ce tronc commun, chaque outil a ses avantages. Celui de PostSharp est sans doute une plus grande « boite a outils » pour les operations de niveau intermediaire (tel que le tissage proprement parler), et l’integration dans MSBuild. Cecil a de plus grandes capacites d’analyse… et certainement d’autres avantages que je ne soupconne pas ;).
Mais je crois que, dans le cas precis de cet exemple, l’integration avec MSBuild et les custom attributs est un avantage decisif pour PostSharp. Mais pour un outil comme Gendarme, Cecil possede evidemment plus d’atouts!
-gael
Gael> Hey, content de voir que tu sois passé par là, ça me donne l’occasion de te féliciter pour PostSharp qui est un outil impressionnant :)
Au niveau de la comparaison avec Cecil, je pense qu’on est tous plus ou moins d’accord. L’intégration au processus de build de PostSharp est particulièrement puissante, et offre une super expérience d’utilisation. Le tissage statique devient quasiment transparent (voire magique) lorsqu’on utilise Laos.
Cecil n’offre pas cette intégration, et ne compte probablement pas l’offrir, puisqu’il s’agit avant tout d’une librairie de manipulation et d’analyse d’assemblies. L’intégrer à des outils tiers comme NDepend ou Gendarme semble pertinent, l’utiliser pour faire de l’AOP telle quelle semble être un peu overkill…
Il est certainement plus intelligent de faire la comparaison avec PostSharp.Core, mais encore une fois les différences sont notables. Cecil est un outil permettant de travailler à bas niveau, mais est un outil à part entière, ce qui ne m’a pas vraiment semblé être le cas de PostSharp.Core qui ressemble plutôt à une super-fondation pour des plug-ins, spécifiques ou non. En revanche, l’intégration au process de build (entre autres) fait déjà partie de l’offre PostSharp.Core.
Dans un article précédent (que tu as peut-être vu passé), je propose une implémentation d’
INotifyPropertyChanged
avec Laos, mais il aurait été intéressant de créer un plugin spécifique au dessus de PostSharp.Core. Peut-être existe-il déjà, au moins à titre d’exemple ? Mes brefs essais me font penser qu’il y a déjà un bon niveau d’abstraction sur le CIL lorsqu’on utilise PostSharp.Core, alors que Cecil est directement très calée sur la structure du langage intermédiaire.Idéalement, et c’est ce que je soulève en fin d’article, j’aurais aimé avoir les outils de PostSharp (intégration MSBuild, custom attributes, multicasting), et l’API de Cecil pour le tissage bas-niveau. Je parle toujours d’
INotifyPropertyChanged
, hein.PostSharp Core a deux niveaux: le bas niveau (lecture des assemblies, modele objet, …) et le niveau intermediaire (platte-forme des plug-ins, tisseur). PostSharp Laos est considere comme une couche de haut niveau.
http://www.postsharp.org/about/architecture/
Je crois que les couches de bas-niveau de PostSharp Core et de Cecil sont quasiment identiques (puisqu’elles decalquent la specification CIL). Donc je dirais que PostSharp.Core a toutes(ou la plupart) des fonctions de l’API de Cecil pour le tissage de bas niveau. Autrement dit: on peut faire toutes les modifications que l’on veut avec PostSharp Core; on peut le faire aussi avec Cecil. Et on peut utiliser PostSharp Core comme une librairie, comme pour Cecil. La difference la plus fondamentale est dans la licence: PostSharp Core est dual GPL/commercial. Et puis, derriere JB il y a Novell, une autre difference importante.
Voir par exemple http://code.google.com/p/postsharp-user-plugins/wiki/Log4PostSharp pour un exemple de plug-in effectuant des modifications de bas niveau.
@ »j’aurais aimé avoir les outils de PostSharp (intégration MSBuild, custom attributes, multicasting), et l’API de Cecil pour le tissage bas-niveau »: c’est deja le cas!
Je vois qu’il faudra que vous m’invitiez aussi ;) (mais je viens de Prague…).
J’avais bien fait la distinction entre la couche de haut niveau, et PostSharp.Core. Ce qui semble m’avoir échappé, apparemment, c’est la distinction qui existe entre les deux couches constituant PostSharp.Core.
Mes essais – brefs et superficiels – s’étaient surtout concentrés sur Laos. J’avais très rapidement essayé d’utiliser PostSharp.Core pour faire des modifications de bas niveau, mais visiblement je n’ai pas creusé assez profondément. Au lieu de développer un plug-in sur PostSharp.Core, j’aurais dû utiliser ce dernier différemment. Je vais voir ce que ça donne.
Juste par curiosité, tu as des exemples de projets open source qui utilisent PostSharp.Core.dll ? Ou bien, encore moins discret : est-ce qu’il y a beaucoup de projets qui tombent sous le coup de la licence commerciale ?
Sinon, je ne crois pas prendre trop de risques en disant que tu serais le bienvenue à nos réunions parisiennes… Nous n’avons pas vraiment fait venir Jb : il nous a signalé qu’il passait sur Paris et on en a profité. Si d’aventure tu devais passer dans le coin, n’hésite pas à en faire autant :)
Plusieurs projets open-source ont develope un plug-in sur base de PostSharp Core. Il y en a quelques uns sur http://code.google.com/p/postsharp-user-plugins, quoiqu’aucun ne soit vraiment significatif. Ayende a commence a utiliser PostSharp pour NHibernate; peut-etre qu’il utilisera le bas niveau.
Les projets qui tombent sous la license commerciale: dataobjects.net et starcounter pour l’instant.
Un peu ennuyant que ton blog ne supporte pas les notifications par mail!
-gael
Yep, j’étais déjà passé sur google code, et j’ai lu le post d’ayende au sujet de son utilisation de PostSharp. Je ne sais pas trop où ça en est remarque.
Pour ce qui est des notifications, il y a les flux RSS… Je crois même que feedburner permet de s’abonner par email, mais uniquement aux posts, pas aux commentaires.
[…] INotifyPropertyChanged avec Mono.Cecil […]
[…] Pour conclure c’etait pour moi une des meilleurs sessions (avec TDD). J’ai pu y redécouvrir l’AOP avec les bons termes et les bons outils.Merci Romain Vous pouvez dès maintenant retrouver le compte rendu de Jb Evain ici. Je vous recommande également de lire 2 posts d’introduction à l’AOP sur le blog de Romain INotifyPropertyChanged avec PostSharp et INotifyPropertyChanged avec Mono.Cecil […]
[…] L’interface INotiffyPropertyChange : cette interface définit un événement à déclencher lorsqu’une valeur de propriété a été modifiée. http://www.simonferquel.net/blog/archive/2009/10/07/inotifypropertychanged.aspx https://codingly.com/2008/09/30/inotifypropertychanged-sans-les-strings-ca-vous-dit/ https://codingly.com/2008/10/29/utiliser-laop-avec-postsharp-pour-implementer-inotifypropertychanged/ https://codingly.com/2008/11/10/introduction-a-monocecil-implementer-inotifypropertychanged/ […]
Je cherche ce machin sur le net et je tombe sur ton site sans vouloir, comme quoi tu deviens la référence mon salaud ! ;)
Je sais pas trop si c’est une bonne chose que j’aprenne le C# en suivant tes conseils mais bon… je vais qd même tenter d’en tirer qqchose !
++