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).