jeudi 21 avril 2011

Enumération conditionnelle sur les collections

Après l’écriture du post précédent concernant la surveillance de l’énumération d’une collection au moyen de délégués, il apparait que la méthode (simple) mise en œuvre peut largement être étendue jusqu’à construire un ensemble complet de méthodes d’extension de IEnumerable<T> agissant non pas sur la collection elle-même comme les méthodes de System.Linq.Enumerable, mais sur son énumération. Par exemple, on peut parfaitement créer une nouvelle méthode EnumerateIf permettant de spécifier une condition lors de l’énumération. Si la condition n’est pas satisfaite, l’énumération sur l’élément courant de la collection est ignorée. Pour implémenter EnumerateIf, il suffit de déclarer la méthode d’extension suivante :

public static IEnumerable<TSource> EnumerateIf<TSource>(this IEnumerable<TSource> source,
Predicate<TSource> predicate)
{
EEnumerable<TSource> eenumerable = new EEnumerable<TSource>(source,
predicate);
return eenumerable;
}



Le nouveau constructeur d’EEnumerable<T> transmet la condition à son enumerator :

public EEnumerable(IEnumerable<TSource> source,
Predicate<TSource> enumerationCondition)
{
Source = source;
Enumerator = new EEnumerator<TSource>(source.GetEnumerator(), enumerationCondition, this);
}



Et le nouveau constructeur d’EEnumerator<T> affecte simplement le prédicat :

public Predicate<TSource> EnumerationCondition { get; set; }
public IEnumerator<TSource> Source { get; set; }
private EEnumerable<TSource> parent;

public EEnumerator(IEnumerator<TSource> source,
Predicate<TSource> enumerationCondition,
EEnumerable<TSource> parent)
{
Source = source;
EnumerationCondition = enumerationCondition;
this.parent = parent;
}



Comme pour le post précédent, la vraie logique se trouve en fait dans la méthode MoveNext, appelée effectivement au fur et à mesure de l’énumération. Ici, les lignes 6 à 8 réalisent le comportement souhaité en “sautant” les éléments de la Source interne qui ne satisfont pas la condition :


   1:  public bool MoveNext()
   2:  {
   3:      if (OnItemEnumerating != null)
   4:          OnItemEnumerating(Current);
   5:   
   6:      bool result = Source.MoveNext();
   7:      while (EnumerationCondition != null && !EnumerationCondition(Current) && result)
   8:          result = Source.MoveNext();
   9:   
  10:      if (OnItemEnumerated != null)
  11:          OnItemEnumerated(Current);
  12:      if (!result && parent.OnEnumerated != null)
  13:          parent.OnEnumerated(parent);
  14:      return result;
  15:  }



Ainsi, avec le code d’exemple suivant :

List<int> ints = new List<int>();
ints.Add(1);
ints.Add(2);
ints.Add(3);
ints.Add(4);

ints.EnumerateIf(i => i >= 3)
.Monitor(i => Console.WriteLine("Monitoring collection"),
i => Console.WriteLine("Collection monitored"),
i => Console.WriteLine("Current item is " + i),
i => Console.WriteLine("Next item will be " + i))
.ToList();



Le résultat affiché dans la console est bien le résultat attendu :


image


La méthode EnumerateIf n’est qu’un premier exemple. D’autres méthodes du même style sont envisageables :



  • EnumerateWhile

  • EnumerateRange (en spécifiant l’index minimal et maximal, en vue d’une pagination)

  • EnumerateWithDelay (ajout d’un Timer pour insérer une délai entre chaque appel à MoveNext)

  • EnumerateDuring (énumérer autant d’éléments que possible en un temps donné)

  • etc.

Evidemment, il reste à trouver des cas d’utilisation concrets pour ces différentes méthodes, mais d’une façon générale, il devient possible d’insérer du comportement pendant l’énumération des collections pour réaliser un traitement “ultime” au moment de la récupération des données.


Ainsi, à partir d’un ensemble de données disponibles (stockées par exemple dans un cache), on peut réaliser le filtrage (EnumerateIf) et la pagination (EnumerateRange) sans avoir besoin de créer une nouvelle collection (habituellement par les méthodes Where, Skip et Take), mais en modifiant uniquement la façon dont les données sont énumérées.

mardi 19 avril 2011

Surveiller l’énumération des valeurs d’une collection

Dans le Framework .NET, il n’existe pas de mécanisme pour surveiller l’évaluation des éléments dans une collection. Néanmoins, une telle fonctionnalité peut être intéressante dans le cas de requêtes LINQ complexes, ou pour mettre à jour un affichage au fur et à mesure du parcours de la collection. Un tel mécanisme peut être réalisé au moyen de nouvelles classes implémentant IEnumerable<T> et IEnumerator<T>. En effet, lorsqu’une collection est parcourue (par exemple par un foreach, ou en appelant la méthode ToList sur la collection), la méthode IEnumerable<T>.GetEnumerator est exécutée. Pendant le parcours, chaque élément de la collection est atteint par un appel à la méthode IEnumerator<T>.MoveNext. Il suffit donc d’insérer du comportement dans ces 2 méthodes. Par exemple, pour implémenter IEnumerable<T> :

class EEnumerable<TSource> : IEnumerable<TSource>
{
public IEnumerable<TSource> Source { get; set; }
public IEnumerator<TSource> Enumerator { get; set; }
public Action<IEnumerable<TSource>> OnEnumerating { get; set; }
public Action<IEnumerable<TSource>> OnEnumerated { get; set; }

public EEnumerable(IEnumerable<TSource> source,
Action<IEnumerable<TSource>> OnEnumerating,
Action<IEnumerable<TSource>> OnEnumerated,
Action<TSource> OnItemEnumerating,
Action<TSource> OnItemEnumerated)
{
Source = source;
this.OnEnumerating = OnEnumerating;
this.OnEnumerated = OnEnumerated;
Enumerator = new EEnumerator<TSource>(Source.GetEnumerator(),
OnItemEnumerating, OnItemEnumerated, this);
}

public IEnumerator<TSource> GetEnumerator()
{
if (OnEnumerating != null)
OnEnumerating(this);
return Enumerator;
}

System.Collections.IEnumerator System.Collections.IEnumerable.GetEnumerator()
{
if (OnEnumerating != null)
OnEnumerating(this);
return Enumerator;
}
}

Et pour implémenter IEnumerator<T> :

class EEnumerator<TSource> : IEnumerator<TSource>
{
public Action<TSource> OnItemEnumerating { get; set; }
public Action<TSource> OnItemEnumerated { get; set; }
public IEnumerator<TSource> Source { get; set; }

private EEnumerable<TSource> parent;

public TSource Current
{
get { return Source.Current; }
}

public EEnumerator(IEnumerator<TSource> source,
Action<TSource> OnItemEnumerating,
Action<TSource> OnItemEnumerated,
EEnumerable<TSource> parent)
{
Source = source;
this.OnItemEnumerating = OnItemEnumerating;
this.OnItemEnumerated = OnItemEnumerated;
this.parent = parent;
}

public void Dispose()
{
Source.Dispose();
}

object System.Collections.IEnumerator.Current
{
get { return Source.Current; }
}

public bool MoveNext()
{
if (OnItemEnumerating != null)
OnItemEnumerating(Current);
bool result = Source.MoveNext();
if (OnItemEnumerated != null)
OnItemEnumerated(Current);
if (!result && parent.OnEnumerated != null)
parent.OnEnumerated(parent);
return result;
}

public void Reset()
{
Source.Reset();
}
}



Enfin, on peut ajouter une méthode d’extension à IEnumerable<T> pour masquer l’utilisation de ces classes :

public static class Enumerable
{
public static IEnumerable<TSource> Monitor<TSource>(this IEnumerable<TSource> source,
Action<IEnumerable<TSource>> OnEnumerating, Action<IEnumerable<TSource>> OnEnumerated,
Action<TSource> OnItemEnumerating, Action<TSource> OnItemEnumerated)
{
EEnumerable<TSource> eenumerable = new EEnumerable<TSource>(source,
OnEnumerating, OnEnumerated, OnItemEnumerating, OnItemEnumerated);
return eenumerable;
}
}



Globalement, on peut dire que EEnumerable<T> agit comme un décorateur de IEnumerable<T>, en lui ajoutant la fonctionnalité de surveillance de la collection lors du parcours. La méthode Monitor nouvellement créée est accessible pour n’importe quelle collection et retourne elle-même un IEnumerable<T>, ce qui permet de ne pas briser les chaines d’appels aux autres méthodes d’extension de IEnumerable<T>. Voici un exemple d’utilisation de la méthode Monitor :

List<int> ints = new List<int>();
ints.Add(1);
ints.Add(2);
ints.Add(3);
ints.Add(4);

ints.Monitor(i => Console.WriteLine("Monitoring"),
i => Console.WriteLine("Monitored"),
i => Console.WriteLine("Monitoring " + i),
i => Console.WriteLine("Monitored " + i))
.ToList();



Ce code permet d’obtenir le résultat suivant :


image


On voit que le parcours de la collection, nécessaire lors de l’appel à ToList, effectue en fait 5 appels à MoveNext alors que la collection ne contient que 4 éléments. Le premier appel permet de se placer au niveau du premier élément de la collection. La collection est ensuite parcourue, élément par élément. Enfin, le dernier appel à MoveNext renvoie false, indiquant qu’il n’y a plus d’élément dans la collection.


On peut complexifier légèrement l’exemple pour voir qu’en fonction de la position de l’appel à Monitor par rapport aux autres méthodes d’extension, on obtient un résultat différent dans la console. Par exemple le code suivant :

ints.Where(i => i > 2)
.OrderByDescending(i => i)
.Monitor(i => Console.WriteLine("Monitoring collection"),
i => Console.WriteLine("Collection monitored"),
i => Console.WriteLine("Current item is " + i),
i => Console.WriteLine("Next item will be " + i))
.ToList();



Donne le résultat suivant :


image


On dispose donc d’un moyen simple pour surveiller l’énumération des valeurs d’une collection, tout en ayant la possibilité de réagir en insérant du code quelconque.

mercredi 13 avril 2011

Offre de découverte Azure

L’offre de découverte de la plateforme Azure vient d’être reconduite jusqu’au 30 Septembre. Certaines limites, comme la bande passante entrante et sortante, sont relevées (20Go entrants et 20Go sortants inclus par mois). Attention néanmoins aux éléments suivants :

  • Pour y souscrire, il faut obligatoirement indiquer un numéro de carte bancaire, comme pour l’offre de découverte précédente. Ceci implique de vérifier régulièrement sa consommation pour éviter les surprises en cas de dépassement.
  • Seule une seule instance extra-small peut tourner en permanence (750 Heures par mois). Une instance small est incluse également, mais seulement pour 25 Heures par mois. Le fait de multiplier le nombre d’instances ou d’augmenter la taille des instances entrainera donc systématiquement un dépassement si elles doivent tourner en permanence. En outre, cela veut dire qu’on peut difficilement expérimenter une combinaison “classique” Web Role/Worker Role gratuitement.
  • La base SQL Azure d’un Go n’est incluse que pendant les 3 premiers mois. Ceci veut dire qu’en s’inscrivant maintenant, elle ne sera pas incluse jusqu’à la fin de l’offre (30 Septembre).

L’offre permet néanmoins de monter un environnement raisonnable en utilisant toutes les briques d’Azure (Compute, Storage, Service Bus & Access Control, SQL Azure).

samedi 26 mars 2011

Intégrer un composant Silverlight en WPF

Intégration

Il est parfois nécessaire d’intégrer un composant Silverlight dans une application WPF, étant donné que certains contrôles Silverlight n’existent pas en WPF. La méthode la plus courante consiste à placer un WebBrowser dans l’application WPF, et d’utiliser la méthode Navigate en indiquant l’URL d’une page contenant le composant Silverlight à intégrer. Ainsi, dans le XAML côté WPF :

<WebBrowser Name="wb"  />



Dans le constructeur correspondant côté .xaml.cs :

public MainWindow()
{
InitializeComponent();
wb.Navigate("http://aaa.bbb.com/toto.aspx");
}



Le résultat est visuellement excellent et remarquablement fluide. En plus de l’unification des développements (Xaml + C#), on dispose donc d’un moyen très simple et efficace pour tirer profit de développements réalisés en Silverlight a priori incompatibles avec WPF sans recompilation du code source.


Une fois l’intégration réalisée, le problème qui se pose immédiatement est celui de la communication entre le composant Silverlight et le reste de l’application WPF. Certains articles expliquent comment réaliser cette communication en passant par le document HTML, seul point de liaison disponible depuis l’application Silverlight et le WebBrowser WPF. Néanmoins, le moins que l’on puisse dire est que cette méthode n’est pas maintenable et qu’elle réduit l’évolutivité de l’architecture. Un véritable bricolage.


Une méthode de communication élégante


Pour faire communiquer l’application WPF avec le composant Silverlight, le mieux est encore d’utiliser la technologie prévue à cet effet : WCF. Plus précisément, il s’agit de concevoir un service WCF hébergé sur le serveur Web de l’application hébergeant le composant Silverlight. L’application WPF doit pouvoir envoyer des informations au composant Silverlight (et inversement), en passant systématiquement par le service WCF. Par conséquent, l’application WPF et l’application Silverlight doivent pouvoir :



  • Envoyer des messages au service WCF
  • Recevoir des messages provenant du service WCF.

Le service WCF agit comme un relai et doit pouvoir :



  • Recevoir des messages de l’application WPF et les relayer à l’application Silverlight
  • Recevoir des messages de l’application Silverlight et les relayer à l’application WPF.

Le service WCF a donc besoin de pouvoir recevoir ET envoyer des messages : un CallbackContract va être utilisé. D’autre part, les communications bidirectionnelles à permettre correspondent à un binding pollingDuplexHttpBinding avec Silverlight et à un binding wsDualHttpBinding avec WPF. Pour pouvoir appliquer ces 2 binding au service WCF, il faut les endpoints correspondants :

<services>
<service name="WpfSilverlightCommunication.CommunicationService">
<endpoint
address="silverlight"
binding="pollingDuplexHttpBinding"
bindingConfiguration="multipleMessagesPerPollPollingDuplexHttpBinding"
contract="WpfSilverlightCommunication.ICommunicationService">
</endpoint>
<endpoint
address="wpf"
binding="wsDualHttpBinding"
contract="WpfSilverlightCommunication.ICommunicationService">
</endpoint>
<endpoint
address="mex"
binding="mexHttpBinding"
contract="IMetadataExchange"/>
</service>
</services>



Le reste de la configuration WCF (system.serviceModel) peut être configuré de façon classique :

<extensions>
<bindingExtensions>
<add name="pollingDuplexHttpBinding"
type="System.ServiceModel.Configuration.PollingDuplexHttpBindingCollectionElement,System.ServiceModel.PollingDuplex, Version=4.0.0.0, Culture=neutral, PublicKeyToken=31bf3856ad364e35" />
</bindingExtensions>
</extensions>
<bindings>
<wsDualHttpBinding>
<binding sendTimeout="00:00:05" />
</wsDualHttpBinding>
<pollingDuplexHttpBinding>
<binding name="multipleMessagesPerPollPollingDuplexHttpBinding"
duplexMode="SingleMessagePerPoll" sendTimeout="00:00:05"/>
</pollingDuplexHttpBinding>
</bindings>



Ainsi, l’application Silverlight pourra accéder au service WCF par l’adresse <adresseService>.svc/silverlight et l’application WPF par l’adresse <adresseService>.svc/wpf. Le service WCF fonctionne de la façon suivante :



  • Le mode d’instance est InstanceContextMode.Single, parce que l’application WPF et l’application Silverlight ne se connectent pas avec le même identifiant de session. Par conséquent, avec la configuration par défaut (PerSession), faire communiquer les 2 instances du service serait plus compliqué.
  • Le service contient une méthode Ping qui enregistre en premier lieu les canaux de communication vers l’application WPF et vers l’application Silverlight.
  • Chaque couple WPF/Silverlight est identifié par un Guid que partagent les 2 applications.

Le code du contrat de service WCF est le suivant :

[ServiceContract(CallbackContract=typeof(ICommunicationServiceHandler))]
public interface ICommunicationService
{
[OperationContract]
bool Ping(string id);

[OperationContract(IsOneWay=true)]
void Order(string appId, List<string> values);
}

[ServiceContract]
public interface ICommunicationServiceHandler
{
[OperationContract(IsOneWay = true)]
void Receive(List<string> values);
}



Et le service :

[ServiceBehavior(InstanceContextMode=InstanceContextMode.Single, ConcurrencyMode=ConcurrencyMode.Multiple)]
public class CommunicationService : ICommunicationService
{
Dictionary<string, ICommunicationServiceHandler> silverlightAppChannels = new Dictionary<string, ICommunicationServiceHandler>();
Dictionary<string, ICommunicationServiceHandler> wpfAppChannels = new Dictionary<string, ICommunicationServiceHandler>();

public void Order(string appId, List<string> values)
{
string endpointExtension = OperationContext.Current.EndpointDispatcher.EndpointAddress.Uri.Segments.Last();
if (silverlightAppChannels[appId] != null && wpfAppChannels[appId] != null)
{
switch (endpointExtension)
{
case "silverlight":
wpfAppChannels[appId].Receive(parcelleIds);
break;
case "wpf":
silverlightAppChannels[appId].Receive(parcelleIds);
break;
default:
break;
}
}
Ping(appId);
}

public bool Ping(string id)
{
string endpointExtension = OperationContext.Current.EndpointDispatcher.EndpointAddress.Uri.Segments.Last();
switch (endpointExtension)
{
case "silverlight":
silverlightAppChannels[id] = OperationContext.Current.GetCallbackChannel<ICommunicationServiceHandler>();
break;
case "wpf":
wpfAppChannels[id] = OperationContext.Current.GetCallbackChannel<ICommunicationServiceHandler>();
break;
default:
break;
}
return true;
}
}



La méthode principale, Order, effectue le relai en appelant la méthode Receive de l’autre canal de communication précédemment enregistré par la méthode Ping.


Pour faire en sorte que l’application WPF et l’application Silverlight partagent le même Guid, l’application WPF peut le créer et le transmettre en paramètre d’URL :

private static Guid appId = Guid.NewGuid();

public MainWindow()
{
InitializeComponent();
wb.Navigate("<adressePage>?guid=" + appId.ToString());
}



Pour le reste, l’application WPF agit de façon tout à fait classique comme client de l’endpoint avec le binding wsDualHttpBinding : après ajout de la référence de service, la fenêtre WPF peut implémenter le contrat de callback WCF et contenir une instance du proxy. Il faut simplement penser à appeler une fois la méthode Ping en passant le Guid au chargement de la fenêtre.


De même, l’application Silverlight agit comme un client tout à fait classique de l’endpoint ayant le binding PollingDuplexHttpBinding. Pour récupérer le Guid initialisé par l’application WPF, l’application Silverlight peut procéder de la manière suivante, par exemple pour l’appel initial à Ping :

proxy.PingAsync(HtmlPage.Document.QueryString["guid"]);



Conclusion


Une méthode originale et élégante a été conçue pour faire communiquer une application WPF avec une application Silverlight qui serait rendue dans un WebBrowser. On pourrait facilement étendre cette méthode selon plusieurs axes :



  • Réalisation d’autres types de relais, de type hub (broadcast) ou routeur par exemple.
  • Communication entre une application Winforms et un composant Silverlight avec le composant WebBrowser de WinForms.
  • Synchronisation des 2 applications qui pourraient s’échanger périodiquement leurs données.
  • Introduction d’une queue de messages et maintien des messages en attente dans le service WCF, en cas de perte de connexion.

mardi 22 mars 2011

Suivre l’activité d’une classe

Dans de nombreux cas, il peut être intéressant de suivre l’activité d’une classe quelconque, en particulier pour logger tous les appels à toutes ses méthodes. Quelques exemples :

  • Suivre l’activité d’un service Web.
  • Enregistrer tous les appels à une couche d’accès aux données.
  • Enregistrer tous les appels sur une classe réalisant un accès à une ressource externe, comme une classe réalisant les accès au disque, à un FTP, une classe encapsulant l’envoi de mails, etc.

A priori, il n’existe aucun mécanisme permettant de logger automatiquement tous les appels à une classe quelconque, de préférence sans modifier le code appelant ni le code appelé. Parmi les 4 sections suivantes, les 3 premières présentent des méthodes approchantes. La dernière présente une méthode originale basée sur les Code Contracts permettant d’atteindre le résultat recherché.

IntelliTrace & Rapports de performance

Il s’agit des méthodes les plus génériques. Elles permettent de tout savoir sur l’exécution d’un programme, mais sont limitées à la production de données de trace. Il ne sera donc pas possible de configurer totalement les données produites. De plus, seule la version Ultimate de Visual Studio 2010 possèdent ces fonctionnalités.

Concernant l’utilisation d’IntelliTrace, il est nécessaire d’activer l’option Evènements IntelliTrace et information sur les appels pour obtenir des informations sur les appels de méthodes :

image

Avec le bout de code suivant en exemple :

static void Main(string[] args)
{
Toto toto = new Toto();
toto.Operation();
}



La fenêtre IntelliTrace affiche à l’exécution :


image


On a bien une trace de tous les appels à la classe Toto. Bien entendu, dans le cas d’une application professionnelle complexe, le nombre d’appels devient tel que cette méthode n’est pas envisageable.


Concernant l’utilisation des rapports de performance, il suffit de lancer l’assistant performance :


image


Et de choisir Instrumentation :


image


Après exécution du programme, le profileur produit un fichier .vsp qui présente un grand nombre d’informations statistiques sur l’exécution. Par exemple, en basculant en mode d’affichage Fonctions, on voit entre autres que la méthode Operation a été appelée une seule fois :


image


L’IntelliTrace et les rapports de performance ne permettent cependant pas de logger explicitement les appels à une classe en particulier, et il n’est pas raisonnable de les utiliser sur une application de production pendant une durée trop longue. Il s’agit donc seulement de solutions approchantes au problème énoncé.


Méthodes existantes


Bien souvent, s’il y a besoin de suivre l’activité d’une classe en particulier, c’est que le contexte applicatif est propice à ce besoin. Dans ces cas, les technologies existantes anticipent le besoin en proposant les mécanismes nécessaires. Par exemple, pour logger les appels à un Service WCF, il est possible de créer un EndpointBehavior personnalisé dans lequel on passera avant/après chaque requête (methodes AfterReceiveRequest et BeforeSendReply). Autre exemple, en WebForms ASP.NET, tout accès à une page ASPX peut être détecté au moyen de l’évènement Page_BeginRequest. Enfin, pour logger tous les appels aux bases de données, le problème est souvent contourné par l’utilisation de SQL Server Profiler ou de tout autre outil similaire avec d’autres SGBDR.


Toutes ces méthodes sont efficaces, mais elles ne répondent au problème énoncé que dans des cas particuliers.


Design Patterns


Certains design patterns, comme l’Observer ou le Decorator, peuvent être utilisés pour ajouter une fonction de Log à une classe existante. Néanmoins, pour les mettre en place, il faut soit modifier le code de la classe à logger, soit modifier le code appelant. Par exemple, dans le cas du pattern Decorator, il faut :



  • extraire une interface ou une classe abstraite de la classe à surveiller,
  • implémenter une autre classe de log qui implémente la classe abstraite ou l’interface
  • modifier le code appelant la méthode pour ajouter la fonction de log.

On peut bien entendu utiliser le pattern Factory pour ajouter systématiquement la fonction de log, mais cela ne fait que déplacer le problème, puisque le code appelant doit alors solliciter la Factory. En outre, ceci oblige à appliquer le pattern pour chaque classe à surveiller et à “doubler” les méthodes, donc on augmente la complexité et on ajoute un niveau d’abstraction. Tout ceci est disproportionné par rapport à un problème simple a priori.


Utilisation détournée des Code Contracts


Inclus dans mscorlib à partir du Framework .NET 4.0, les Code Contracts proposent des fonctionnalités de validation de code activables par un certain nombre d’options de compilation. A la base des Code Contracts, il y a 3 mécanismes permettant de réaliser cette validation de code :



  • La méthode Contract.Requires permettant de spécifier une précondition sur une méthode
  • La méthode Contract.Ensures permettant de spécifier une postcondition sur une méthode
  • L’attribut ContractInvariantMethod appliqué à une méthode, activant une validation effectuée “à la fin de chaque méthode publique de la classe”.


C’est bien sûr l’attribut ContractInvariantMethod qui va être utilisé comme support pour réaliser la fonction de log. En effet, l’idée est de profiter de ce mécanisme pour réaliser un log plutôt qu’une validation. Pour comprendre comment fonctionnent réellement les Code Contracts, la première étape consiste à activer l’option correspondante dans l’onglet “Code Contracts” (des paramètres du projet) disponible si la fonctionnalité est installée :


image


Dans la classe Toto à surveiller, il suffit d’ajouter une méthode ayant l’attribut ContractInvariantMethod de la façon suivante :

class Toto
{
public void Operation()
{
}

[ContractInvariantMethod]
void aaa()
{
Contract.Invariant(false);
}
}



Après compilation et utilisation de Reflector sur l’assembly produite, on voit que l’activation des Code Contracts modifie la compilation du programme en ajoutant automatiquement, à la fin de chaque méthode publique, un appel à la méthode aaa du bout de code précédent, rebaptisée $InvariantMethod$. Par exemple, pour la méthode Operation qui ne faisait rien, on a en fait :


image


Par l’utilisation des Code Contracts, on a donc un mécanisme automatisé pour appeler une méthode après n’importe quel appel d’une méthode publique d’une classe. Ceci correspond bien au but recherché, encore faut-il arriver à injecter du code pour effectuer la surveillance de la classe. En effet, si on essaie de modifier la méthode aaa pour y réaliser autre chose que des appels successifs à Contract.Invariant, alors une erreur de compilation survient. Cependant, si on retourne dans les propriétés du projet, on voit qu’il est possible de définir une classe pour Custom Rewriter Methods dans laquelle on va pouvoir injecter du code quelconque :


image


Les méthodes de cette classe seront appelées uniquement si une validation par les Code Contracts échoue, c’est pourquoi il y a cette instruction dans la méthode aaa permettant de faire échouer systématiquement la validation :

Contract.Invariant(false);



Dans la classe indiquée pour Custom Rewriter Methods sont attendues des méthodes statiques ayant des signatures particulières. Pour celle qui nous intéresse ici, c’est-à-dire Invariant, on pourrait définir la méthode suivante :

public static class RuntimeFailureMethods
{
public static void Invariant(bool cond, string userMsg, string condText)
{
StackFrame[] sf = new StackTrace().GetFrames();
if (sf.Length >= 3)
{
MethodBase mb = sf[2].GetMethod();
Console.WriteLine("Appel de {0}.{1}", mb.DeclaringType.Name, mb.Name);
}
}
}



Ce code détermine quelle méthode a abouti à l’appel à Invariant en accédant au 3e élément de la pile des appels. En effet, le premier élément est toujours la méthode courante, le deuxième est la méthode aaa, et le troisième est bien la méthode d’origine appelée sur notre classe à surveiller. En exécutant toujours notre même Main, on obtient :


image



Les Code Contracts permettent donc bien de suivre automatiquement l’activité d’une classe quelconque, sans modification du code appelant ni du code appelé.