<?xml version="1.0" encoding="UTF-8"?>
<rss version="2.0"
	xmlns:content="http://purl.org/rss/1.0/modules/content/"
	xmlns:wfw="http://wellformedweb.org/CommentAPI/"
	xmlns:dc="http://purl.org/dc/elements/1.1/"
	xmlns:atom="http://www.w3.org/2005/Atom"
	xmlns:sy="http://purl.org/rss/1.0/modules/syndication/"
	xmlns:slash="http://purl.org/rss/1.0/modules/slash/"
	xmlns:georss="http://www.georss.org/georss" xmlns:geo="http://www.w3.org/2003/01/geo/wgs84_pos#" xmlns:media="http://search.yahoo.com/mrss/"
	>

<channel>
	<title>Codingly &#187; Cecil</title>
	<atom:link href="http://codingly.com/tag/cecil/feed/" rel="self" type="application/rss+xml" />
	<link>http://codingly.com</link>
	<description>Par Romain Verdier</description>
	<lastBuildDate>Thu, 24 May 2012 09:10:53 +0000</lastBuildDate>
	<language>fr</language>
	<sy:updatePeriod>hourly</sy:updatePeriod>
	<sy:updateFrequency>1</sy:updateFrequency>
	<generator>http://wordpress.com/</generator>
<cloud domain='codingly.com' port='80' path='/?rsscloud=notify' registerProcedure='' protocol='http-post' />
<image>
		<url>http://0.gravatar.com/blavatar/62c090a2ec42be744d871177bd874854?s=96&#038;d=http%3A%2F%2Fs2.wp.com%2Fi%2Fbuttonw-com.png</url>
		<title>Codingly &#187; Cecil</title>
		<link>http://codingly.com</link>
	</image>
	<atom:link rel="search" type="application/opensearchdescription+xml" href="http://codingly.com/osd.xml" title="Codingly" />
	<atom:link rel='hub' href='http://codingly.com/?pushpress=hub'/>
		<item>
		<title>Introduction à Mono.Cecil : Implémenter INotifyPropertyChanged</title>
		<link>http://codingly.com/2008/11/10/introduction-a-monocecil-implementer-inotifypropertychanged/</link>
		<comments>http://codingly.com/2008/11/10/introduction-a-monocecil-implementer-inotifypropertychanged/#comments</comments>
		<pubDate>Mon, 10 Nov 2008 19:20:09 +0000</pubDate>
		<dc:creator>Romain Verdier</dc:creator>
				<category><![CDATA[Articles]]></category>
		<category><![CDATA[.NET]]></category>
		<category><![CDATA[AOP]]></category>
		<category><![CDATA[C#]]></category>
		<category><![CDATA[Cecil]]></category>
		<category><![CDATA[CIL]]></category>

		<guid isPermaLink="false">http://romainverdier.wordpress.com/?p=379</guid>
		<description><![CDATA[Vous allez probablement m&#8217;en vouloir à force, mais j&#8217;ai décidé de continuer mes expériences autour d&#8217;INotifyPropertyChanged. Cette fois-ci, en utilisant Mono.Cecil pour faire un peu d&#8217;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 [...]<img alt="" border="0" src="http://stats.wordpress.com/b.gif?host=codingly.com&#038;blog=3510695&#038;post=379&#038;subd=romainverdier&#038;ref=&#038;feed=1" width="1" height="1" />]]></description>
			<content:encoded><![CDATA[<p>Vous allez probablement m&#8217;en vouloir à force, mais j&#8217;ai décidé de continuer mes expériences autour d&#8217;<code>INotifyPropertyChanged</code>. Cette fois-ci, en utilisant <a href="http://www.mono-project.com/Cecil">Mono.Cecil</a> pour faire un peu d&#8217;IL rewriting. Où comment tisser un aspect sans utiliser de framework AOP. Comparée à celle basée sur <a href="http://www.postsharp.org/about/architecture/">PostSharp.Laos</a>, cette solution a un inconvénient majeur : elle est plus roots.</p>
<p>En revanche, aucune autre méthode à ma connaissance ne permet d&#8217;obtenir un tissage aussi fin. Par fin, j&#8217;entends spécifique, propre, non pollué par le code que génèrent les outils d&#8217;AOP classiques pour supporter les mécanismes d&#8217;interception. Donc les performances seront à la clé puisqu&#8217;une fois l&#8217;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é <code>INotifyPropertyChanged</code> à la main. Le développement aura juste été un peu plus cher&#8230;</p>
<p><span id="more-379"></span></p>
<p>Même si la plupart du temps vous préfèrerez sans doute bénéficier de l&#8217;abstraction offerte par les outils d&#8217;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&#8217;en bailler, hein.)</p>
<p>Bref, ce post va plus ressembler à un article sur Cecil qu&#8217;à un article sur <code>INotifyPropertyChanged</code>. Il y aura probablement deux parties, mais ce n&#8217;est pas une promesse :</p>
<ul>
<li>Part 1 : première version d&#8217;un <em>transformer</em> utilisant Cecil pour implémenter <code>INotifyPropertyChanged</code> (cet article)</li>
<li>Part 2 : version améliorée du <em>transformer</em> lançant l&#8217;évènement de notification uniquement lorsque la valeur affectée à la propriété est différente</li>
</ul>
<p>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.</p>
<p>Mais commençons par le commencement, en prenant pour victime la <code>Person</code> des articles précédents. Le but est de pouvoir écrire :</p>
<p><pre class="brush: csharp;">
	[NotifyPropertyChanged]
	public class Person
	{
		private string name;

		public string Name
		{
			get { return this.name; }
			set { this.name = value; }
		}
	}
</pre></p>
<p>et d&#8217;obtenir au final une classe <code>Person</code> implémentant <code>INotifyPropertyChanged</code> et capable donc de notifier par l&#8217;événement <code>PropertyChanged</code> les changements de ses propriétés.</p>
<p>Cecil permet de charger un assembly, de l&#8217;inspecter et de le modifier éventuellement avant de le sauvegarder, de le copier, etc. L&#8217;idée consiste donc à :</p>
<ol>
<li>Compiler le code ci-dessus normalement, afin d&#8217;obtenir un assembly</li>
<li>Lancer un transformer, qui utilise Cecil afin de charger cet assembly</li>
<li>Modifier l&#8217;assembly, toujours avec Cecil, pour y injecter l&#8217;implémentation d&#8217;<code>INotifyPropertyChanged</code> en manipulant le code CIL.</li>
<li>Sauvegarder l&#8217;assembly modifié</li>
</ol>
<h3>Le transformer</h3>
<p>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&#8217;assembly à instrumenter :</p>
<p><pre class="brush: csharp;">
    private static void Main(string[] args)
    {
        if (args.Length == 0)
        {
            Console.WriteLine(&quot;Usage: transformer &lt;assembly file&gt;&quot;);
            return;
        }
        new Transformer(path).Run();
    }
</pre></p>
<p>D&#8217;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&#8217;en seront que plus lisibles, à défaut d&#8217;être solides.</p>
<p>Commençons par une vue d&#8217;ensemble des étapes nécessaires à la transformation des assemblies, que nous détaillerons ensuite. (Vous aimez les opcodes ?)</p>
<ol>
<li>Chargement de l&#8217;assembly à instrumenter</li>
<li>Identification des types à modifier : on recherche ceux qui sont taggés par notre attribut <code>[NotifyPropertyChanged]</code></li>
<li>Transformation de chacun de ces types pour leur faire supporter <code>INotifyPropertyChanged</code></li>
<li>Sauvegarde de l&#8217;assembly modifié</li>
</ol>
<p>Le schéma ci-dessous met en avant les différentes étapes de la transformation :<br />
<img src="http://romainverdier.files.wordpress.com/2008/11/transformer.jpg?w=720" alt="transformer" title="transformer"   class="alignnone size-full wp-image-393" /></p>
<ul>
<li>[#1] Implémentation proprement dite de l&#8217;interface <code>INotifyPropertyChanged</code></li>
<li>[#2] Ajout d&#8217;une fonction &#8220;helper&#8221; pour le déclenchement de l&#8217;évènement <code>PropertyChanged</code></li>
<li>[#3] Instrumentation des setters, en utilisant la méthode précédente pour lancer les notifications</li>
</ul>
<p>Voyons comment implémenter ça. Rappelez-vous, tout commence par :</p>
<p><pre class="brush: csharp;">
	new Transformer(path).Run();
</pre></p>
<p>Le constructeur (qui en fait sans doute un peu trop) :</p>
<p><pre class="brush: csharp;">
public Transformer(string assemblyPath)
{
    this.assemblyPath = assemblyPath;
    this.assembly = AssemblyFactory.GetAssembly(assemblyPath);
    this.assembly.MainModule.LoadSymbols();
    this.assembly.MainModule.FullLoad();
}
</pre></p>
<p>Assez trivial : on laisse tout le travail à Cecil. Son <code>AssemblyFactory</code> nous permet de récupérer la définition de l&#8217;assembly à modifier. Afin de conserver les sequence points et permettre de debuguer l&#8217;assembly modifiée, on charge les infos de debug. L&#8217;appel à la méthode <code>FullLoad</code> permet de charger en mémoire toute la représentation du module principal, afin de pouvoir la modifier par la suite.</p>
<p><strong>Petite parenthèse introductive</strong> : Plusieurs fois, dans les méthodes que je vais présenter par la suite, on aura besoin de récupérer une <code>TypeReference</code>, à partir d&#8217;un <code>Type</code>. Lorsque ce sera le cas, on utilisera une des deux méthodes suivantes :</p>
<p><pre class="brush: csharp;">
private TypeReference GetReference(Type type)
{
    TypeReference typeReference = this.assembly.MainModule.TypeReferences[type.FullName]
                                  ?? this.assembly.MainModule.Import(type);
    return typeReference;
}

private TypeReference GetReference&lt;T&gt;()
{
    return GetReference(typeof (T));
}
</pre></p>
<p>Leur fonctionnement est simple : si on a déjà chargé le type dans le module principal de l&#8217;assembly en cours de modification, on retourne directement la <code>TypeReference</code> 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 <em>définitions</em> et les <em>références</em> pour les différents éléments du code (types, fields, methods, etc.)</p>
<p>Lorsqu&#8217;on veut modifier une méthode, par exemple, on va travailler sur sa définition. Mais si on désire simplement l&#8217;appeler, il nous faut juste sa référence. Ce modèle, plus juste que celui proposé par l&#8217;API de réflexion de la BCL peut sembler troublant au premier abord, mais s&#8217;avère au final plus puissant puisqu&#8217;il colle directement avec les <a href="http://www.ecma-international.org/publications/standards/Ecma-335.htm">spécifications du langage intermédiaire</a>. <strong>Fin de la parenthèse</strong>.</p>
<p>Bien, on a de quoi commencer l&#8217;autopsie du <code>Transformer</code>, dont le nom est plus que douteux&#8230; La méthode <code>Run</code> semble être un point de départ judicieux pour notre analyse :</p>
<p><pre class="brush: csharp;">
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);
}
</pre></p>
<p>La méthode <code>GetTypesToWeave</code> permet de retourner tous les types à modifier, c&#8217;est à dire ceux qui sont décorés par notre attribut <code>[NotifyPropertyChanged]</code>. Ce dernier devra donc être défini dans un assembly quelconque, référencé à la fois par le code client &#8211; celui qui contient les types à instrumenter &#8211; et par notre transformer. Chacun des types est ensuite transformé par les appels successifs aux méthodes <code>WeaveINotifyPropertyChanged</code> et <code>WeaveEventTriggers</code>.</p>
<p>La sauvegarde des modifications s&#8217;effectue elle aussi en plusieurs étapes :</p>
<ul>
<li>On indique qu&#8217;on souhaite conserver les symboles de debugging</li>
<li>On sauvegarde notre travail en remplaçant l&#8217;assembly original par celui que l&#8217;on vient d&#8217;instrumenter.
</ul>
<p>Jusqu&#8217;ici, tout va bien, comme qui dirait. Mais creusons un peu plus. Tout d&#8217;abord, le code de la méthode <code>GetTypesToWeave</code> qui nous permet de retourner les types de l&#8217;assembly à modifier. Il s&#8217;agit essentiellement d&#8217;alléger la méthode <code>Run</code> en évitant les imbrications et en isolant la logique de recherche dans une autre méthode :</p>
<p><pre class="brush: csharp;">
private IEnumerable&lt;TypeDefinition&gt; 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;
            }
        }
    }
}
</pre></p>
<p>Notez qu&#8217;on utilise pour cela un bloc itérateur. Le reste pourrait se passer de commentaire : on retourne les types marqués par l&#8217;attribut <code>[NotifyPropertyChanged]</code> en basant la recherche sur le fullname des attributs.</p>
<p>Passons maintenant à la modification des types. Rappelez-vous, il y a deux étapes principales :</p>
<ul>
<li>Implémentation de <code>INotifyPropertyChanged</code> (méthode <code>WeaveINotifyPropertyChanged</code>)</li>
<li>Instrumentation des setters (méthode <code>WeaveEventTriggers</code>)</li>
</ul>
<h3>Implémentation de <code>INotifyPropertyChanged</code></h3>
<p>L&#8217;implémentation proprement dite de <code>INotifyPropertyChanged</code> est une tâche que l&#8217;on peut aisément redécomposer. Cette redécomposition est d&#8217;ailleurs assez visible dans la méthode <code>WeaveINotifyPropertyChanged</code> que je propose :</p>
<p><pre class="brush: csharp;">
private MethodReference WeaveINotifyPropertyChanged(TypeDefinition type)
{
    TypeReference iNotifyPropertyChangedType = GetReference&lt;INotifyPropertyChanged&gt;();
    type.Interfaces.Add(iNotifyPropertyChangedType);
    FieldReference eventField = WeaveEvent(type);
    
    return WeaveEventFiringHelper(eventField, type);
}
</pre></p>
<p>La première étape consiste à récupérer une <code>TypeReference</code> correspondant à cette interface pour l&#8217;ajouter à la liste des interfaces du type que l&#8217;on modifie. Simple, mais insuffisant : il faut également <em>implémenter</em> l&#8217;interface. Dans le cas d&#8217;<code>INotifyPropertyChanged</code>, on doit ajouter un évènement public <code>PropertyChanged</code> au type. C&#8217;est la méthode <code>WeaveEvent</code> qui s&#8217;en chargera. (Etape [#1] du schéma)</p>
<p>La méthode <code>WeaveEventFiringHelper</code>, quant à elle, permet d&#8217;ajouter une méthode <code>FirePropertyChanged</code> au type. Quelque chose équivalent à :</p>
<p><pre class="brush: csharp;">
    private void FirePropertyChanged(string propertyName)
    {
        if (PropertyChanged != null)
        {
            PropertyChanged(this, new PropertyChangedEventArgs(propertyName));
        }
    }
</pre></p>
<p>C&#8217;est l&#8217;é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. </p>
<p>Dès que vous aurez terminé de bailler, nous détaillerons la première étape qui consiste à ajouter l&#8217;évènement <code>PropertyChanged</code> au type. Soit la méthode <code>WeaveEvent</code> :</p>
<p><pre class="brush: csharp;">
private FieldReference WeaveEvent(TypeDefinition type)
{
    TypeReference eventHandlerTypeRef = GetReference&lt;PropertyChangedEventHandler&gt;();
    MethodReference combineMethodRef = this.assembly.MainModule.Import(combineMethodBase);
    MethodReference removeMethodRef = this.assembly.MainModule.Import(removeMethodBase);
    const string eventName = &quot;PropertyChanged&quot;;

    FieldDefinition eventField = new FieldDefinition(&quot;PropertyChanged&quot;,
                                                     eventHandlerTypeRef,
                                                     FieldAttributes.Private);
    type.Fields.Add(eventField);

    EventDefinition eventDefinition = new EventDefinition(eventName,
                                                          eventHandlerTypeRef,
                                                          0);

    eventDefinition.AddMethod = CreateEventMethod(string.Format(&quot;add_{0}&quot;, eventName),
                                                  combineMethodRef,
                                                  eventField);

    eventDefinition.RemoveMethod = CreateEventMethod(string.Format(&quot;remove_{0}&quot;, eventName),
                                                     removeMethodRef,
                                                     eventField);

    type.Methods.Add(eventDefinition.AddMethod);
    type.Methods.Add(eventDefinition.RemoveMethod);
    type.Events.Add(eventDefinition);

    return eventField;
}
</pre></p>
<p>Le layout n&#8217;est pas terrible, j&#8217;espère que vous ne m&#8217;en tiendrez pas rigueur.</p>
<p>Petit rappel : Lorsqu&#8217;on est au niveau CIL, on a une bonne visibilité sur ce qu&#8217;un langage de plus haut niveau comme C# peut nous cacher. Les évènements constituent un bon exemple. En C#, la déclaration d&#8217;un event est simple :</p>
<p><pre class="brush: csharp;">
public event PropertyChangedEventHandler PropertyChanged;
</pre></p>
<p>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 <code>event</code> qui possède les méthodes &#8220;<code>add</code>&#8221; et &#8220;<code>remove</code>&#8220;. Note : même si ce n&#8217;est pas souvent utilisé, sachez qu&#8217;à partir de C# il est possible de <a href="http://msdn.microsoft.com/en-us/library/aa664650(VS.71).aspx">faire apparaitre ces méthodes</a> pour éventuellement intercepter les abonnements / désabonnements à l&#8217;évènement. </p>
<p>Pour ajouter l&#8217;évènement <code>PropertyChanged</code> à notre type, nous avons donc :</p>
<ul>
<li>Ajouté un champ d&#8217;instance privé de type <code>NotifyPropertyChangedEventHandler</code></li>
<li>Ajouté l&#8217;évènement (pseudo propriété), qui utilise deux méthodes : <code>add_PropertyChanged</code> et <code>remove_PropertyChanged</code></li>
<li>Créé les deux méthodes précédentes, qui utilisent respectivement les méthodes statiques <code>Delegate.Combine</code> et <code>Delegate.Remove</code> pour ajouter et supprimer les handlers au champ d&#8217;instance</li>
</ul>
<p>Puisque l&#8217;IL n&#8217;est pas très lisible, voici la traduction C# de ces deux méthodes :</p>
<p><pre class="brush: csharp;">
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);
}
</pre></p>
<p>Dans notre exemple précédent, on délègue la création de ces deux méthodes à <code>CreateEventMethod</code> dont voici la définition :</p>
<p><pre class="brush: csharp;">
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&lt;PropertyChangedEventHandler&gt;();
    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;
}
</pre></p>
<p>Notez l&#8217;API intuitive que propose Cecil pour ce genre de choses : on instancie une définition de l&#8217;élément que l&#8217;on cherche à ajouter au type (<code>FieldDefinition</code>, <code>EventDefinition</code>, <code>MethodDefinition</code>, etc.), on la configure si besoin, et on l&#8217;ajoute à la définition parente. Pour ce qui est de l&#8217;émission d&#8217;IL pour le corps des méthodes, il suffit d&#8217;obtenir un <code>CilWorker</code> et de créer séquentiellement les instructions en utilisant les codes d&#8217;opérations du langage intermédiaire.</p>
<p>Voilà pour la création de l&#8217;évènement <code>PropertyChanged</code>. Notre type est à présent une implémentation valide de l&#8217;interface <code>INotifyPropertyChanged</code>, puisque qu&#8217;il respecte le contrat imposé par cette dernière : exposer un évènement <code>PropertyChanged</code>, de type <code>PropertyChangedEventHandler</code>. (Etape [#1] du schéma)</p>
<p>Il ne reste plus qu&#8217;à 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&#8217;appellons ici <code>FireNotifyPropertyChanged</code> et c&#8217;est la méthode <code>WeaveEventFiringHelper</code> de notre transformer qui se charge de l&#8217;ajouter au type instrumenté :</p>
<p><pre class="brush: csharp;">
private MethodReference WeaveEventFiringHelper(FieldReference eventField, TypeDefinition type)
{
    MethodReference invokeMethodRef = this.assembly.MainModule.Import(invokeMethodBase);
    MethodReference argsCtorMethodRef = this.assembly.MainModule.Import(constructorMethodBase);
    TypeReference stringTypeRef = GetReference&lt;string&gt;();
    TypeReference voidTypeRef = GetReference(typeof (void));

    MethodDefinition firePropertyChanged = 
        new MethodDefinition(&quot;FirePropertyChanged&quot;, MethodAttributes.Private, type);
    firePropertyChanged.ReturnType = new MethodReturnType(voidTypeRef);
    ParameterDefinition propertyNameParameter =
        new ParameterDefinition(&quot;propertyName&quot;, 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;
}
</pre></p>
<p>La <code>FieldReference</code> récupérée en argument est celle retournée par la méthode précédente, <code>WeaveEvent</code>, qui a ajouté l&#8217;évènement au type. Elle représente d&#8217;ailleurs le champ de l&#8217;évènement, qui doit être utilisé pour l&#8217;invocation. La traduction C# de la méthode générée dynamiquement correspond à :</p>
<p><pre class="brush: csharp;">
    private void FirePropertyChanged(string propertyName)
    {
        if (this.PropertyChanged != null)
        {
            this.PropertyChanged.Invoke(this, new PropertyChangedEventArgs(propertyName));
        }
    }
</pre></p>
<p>C&#8217;est complètement équivalent au code de l&#8217;étape [#2] dans le premier schéma.</p>
<h3>Instrumentation des setters</h3>
<p>A ce stade, pour résumer, nous avons :</p>
<ul>
<li>[#1] Implémenté <code>INotifyPropertyChanged</code> en ajoutant un évènement <code>PropertyChanged</code> au type</li>
<li>[#2] Créé une méthode &#8220;helper&#8221; pour déclencher l&#8217;évènement</li>
</ul>
<p>Très logiquement, il ne reste plus qu&#8217;à appeller cette dernière à partir de tous les setters des propriétés du type que l&#8217;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 <em>modifier</em> le code CIL du corps de méthodes existantes. Car un setter de propriété est une méthode traditionnelle.</p>
<p>Examinons la méthode <code>WeaveEventTriggers</code> du transformer, qui est responsable de cette tache :</p>
<p><pre class="brush: csharp;">
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));
    }
}
</pre></p>
<p>Encore une fois, l&#8217;implémentation est naïve et nous verrons s&#8217;il est possible de l&#8217;améliorer plus tard. La logique est simple :</p>
<ul>
<li>On itère sur <em>toutes</em> les propriétés du type. (Pas très malin&#8230;)</li>
<li>On récupère la définition de la méthode du setter.</li>
<li>On récupère une référence vers la dernière instruction du corps du setter</li>
<li>On injecte le code CIL correspondant à l&#8217;appel de la méthode <code>FirePropertyChanged</code> avant la dernière instruction pour que la notification soit lançée à la fin de chaque modification.</li>
</ul>
<p>En gros, si le setter en C# est du genre :</p>
<p><pre class="brush: csharp;">
	public void set_Name(string value)
	{
		this.name = value;
	}
</pre></p>
<p>On le transforme en :</p>
<p><pre class="brush: csharp;">
	public void set_Name(string value)
	{
		this.name = value;
		FirePropertyChanged(&quot;Name&quot;);
	}
</pre></p>
<p>Note : <code>combineMethodBase</code>, <code>invokeMethodBase</code>, <code>constructorMethodBase</code> et <code>removeMethodBase</code>, référencés dans les méthodes du <code>Transformer</code>, sont des membres statiques initialisés de la façon suivante :</p>
<p><pre class="brush: csharp;">
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(&quot;Combine&quot;, new[] {type, type});
    removeMethodBase = type.GetMethod(&quot;Remove&quot;);
    type = typeof (PropertyChangedEventHandler);
    invokeMethodBase = type.GetMethod(&quot;Invoke&quot;);
    type = typeof (PropertyChangedEventArgs);
    constructorMethodBase = type.GetConstructor(new[] {typeof (string)});
}
</pre></p>
<p>Les <code>MethodBase</code> sont des types de l&#8217;API d&#8217;introspection de <code>System.Reflection</code>. Il est facile de récupérer les <code>MethodReference</code> correspondantes (API Cecil) en utilisant la méthode <code>Import</code> d&#8217;une <code>ModuleDefinition</code>.</p>
<h3>Conclusion intermédiaire</h3>
<p>Voilà une première version du &#8220;transformer&#8221; capable d&#8217;instrumenter un assembly existant pour faire implémenter <code>INotifyPropertyChanged</code> à certains de ses types automatiquement. Les améliorations possibles sont nombreuses, et je ne vais en citer que quelques unes :</p>
<ul>
<li>Meilleure utilisation de Cecil : après tout, je découvre et suis donc loin d&#8217;être un expert</li>
<li>Meilleure intégration au process de build, en utilisant une custom task MSBuild par exemple</li>
<li>Version complète de l&#8217;instrumentation : l&#8217;évènement <code>PropertyChanged</code> ne doit être déclenché que lorsque la valeur affectée à la propriété est différente</li>
<li>Gestion des exceptions et du logging (évidemment)</li>
<li>Etc.</li>
</ul>
<p>Cecil est un outil extrêmement puissant et facile d&#8217;utilisation. Pour info, la mise en place de cette version du <code>Transformer</code> n&#8217;a pas dû me prendre 2 heures, alors que <strike>la femme de ma vie me parlait</strike> je faisais autre chose en même temps, et que je découvrais complètement l&#8217;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&#8217;habitude d&#8217;utiliser <code>System.Reflection</code> et <code>System.Reflection.Emit</code>.</p>
<p>Au final ma première expérience est définitivement positive, et je n&#8217;hésiterai pas dans le futur à recourir à Cecil au besoin. Je pense d&#8217;ailleurs qu&#8217;en collaboration avec une couche facilitant son utilisation dans un context post-build, on aurait quelque chose d&#8217;assez terrible, probablement plus simple à utiliser que PostSharp.Core par exemple pour des tâches bien spécifiques.</p>
<p>Notez tout de même que l&#8217;exemple d&#8217;utilisation de Cecil présenté ici est un peu tordu, et qu&#8217;il y a bien d&#8217;autres (meilleurs) <a href="http://www.mono-project.com/Cecil#Projects">moyens d&#8217;exploiter ses capacités</a>.</p>
<br />Publié dans Articles Tagged: .NET, AOP, C#, Cecil, CIL <a rel="nofollow" href="http://feeds.wordpress.com/1.0/gocomments/romainverdier.wordpress.com/379/"><img alt="" border="0" src="http://feeds.wordpress.com/1.0/comments/romainverdier.wordpress.com/379/" /></a> <a rel="nofollow" href="http://feeds.wordpress.com/1.0/godelicious/romainverdier.wordpress.com/379/"><img alt="" border="0" src="http://feeds.wordpress.com/1.0/delicious/romainverdier.wordpress.com/379/" /></a> <a rel="nofollow" href="http://feeds.wordpress.com/1.0/gofacebook/romainverdier.wordpress.com/379/"><img alt="" border="0" src="http://feeds.wordpress.com/1.0/facebook/romainverdier.wordpress.com/379/" /></a> <a rel="nofollow" href="http://feeds.wordpress.com/1.0/gotwitter/romainverdier.wordpress.com/379/"><img alt="" border="0" src="http://feeds.wordpress.com/1.0/twitter/romainverdier.wordpress.com/379/" /></a> <a rel="nofollow" href="http://feeds.wordpress.com/1.0/gostumble/romainverdier.wordpress.com/379/"><img alt="" border="0" src="http://feeds.wordpress.com/1.0/stumble/romainverdier.wordpress.com/379/" /></a> <a rel="nofollow" href="http://feeds.wordpress.com/1.0/godigg/romainverdier.wordpress.com/379/"><img alt="" border="0" src="http://feeds.wordpress.com/1.0/digg/romainverdier.wordpress.com/379/" /></a> <a rel="nofollow" href="http://feeds.wordpress.com/1.0/goreddit/romainverdier.wordpress.com/379/"><img alt="" border="0" src="http://feeds.wordpress.com/1.0/reddit/romainverdier.wordpress.com/379/" /></a> <img alt="" border="0" src="http://stats.wordpress.com/b.gif?host=codingly.com&#038;blog=3510695&#038;post=379&#038;subd=romainverdier&#038;ref=&#038;feed=1" width="1" height="1" />]]></content:encoded>
			<wfw:commentRss>http://codingly.com/2008/11/10/introduction-a-monocecil-implementer-inotifypropertychanged/feed/</wfw:commentRss>
		<slash:comments>10</slash:comments>
	
		<media:content url="http://0.gravatar.com/avatar/cb0ee4bde49708f4be24a02a5d59e52e?s=96&#38;d=http%3A%2F%2F0.gravatar.com%2Favatar%2Fad516503a11cd5ca435acc9bb6523536%3Fs%3D96" medium="image">
			<media:title type="html">Romain</media:title>
		</media:content>

		<media:content url="http://romainverdier.files.wordpress.com/2008/11/transformer.jpg" medium="image">
			<media:title type="html">transformer</media:title>
		</media:content>
	</item>
	</channel>
</rss>
