Blog C#.NET de l'équipe

A la découverte des Expressions C#

publié le 27 juil. 2015 à 06:15 par Nicolas Faugout   [ mis à jour : 27 juil. 2015 à 06:26 ]

Retour d'expérience sur les Expressions, afin de démystifier leur utilisation.

Plus particulièrement je vais parler des Expression Lambda dans le cadre de la sélection de propriétés.

C'est donc un sous ensemble du monde des Expressions mais ça permet déjà d'être à l'aise avec.

Représentation d'un délégué en Expression

Prenons l'expression lambda suivante, qui permet de sélectionner le nom d'un utilisateur :

u => u.Name

=> est une expression lambda (du type LambdaExpression), qui prend 2 éléments lorsqu'on la constuit :
- une expression qui représente le paramètre u
- une expression qui représente la partie droite du lambda, càd l'expression qui représente le corps u.Name

On pourrait exprimer cette expression à l'aide d'un pseudo langage sous la forme suivante :

Lambda(Member())

Ce type de représentation sera très pratique lorsqu'on va parler des Visiteurs d'expression.

Pour fabriquer cette expression from scratch, il faut donc créer une expression de type ParameterExpression (la partie gauche)

var param = Expression.Parameter(typeof(User), "u");

puis une expression de type MemberExpression (la partie droite)

var body = Expression.Property(param, "Name");

NB :  on voit que l'expression paramètre est utilisée dans la construction de l'expression member, puisqu'il faut savoir sur quel type s'effectue la recherche de propriété et quel nom on donne au paramètre.

On peut alors créer l'expression lambda à partir des 2 autres

var lambda = Expression.Lambda<Func<User, string>>(body, param);

Debuging d'une expression

Le code lié aux expressions est toujours assez hard à comprendre, surtout quand on se replonge dedans après quelques semaines.

Un outil assez pratique en mode debug, c'est que le ToString( ) des expressions correspond à leur représentation sous forme de délegué.

Ainsi, lambda.ToString() donnera : u => u.Name

Vous remarquerez ainsi que parfois les expressions sont converties automatiquement par le compilateur à cause des signatures de méthode.

Par ex, l'expression u => u.Id est une Expression<Func<User, int>>

Si on a une méthode qui prend une Expression<Func<User, object>> en entrée, comme c'est souvent le cas (ex : le .Include( ) typé  des IQueryable<T>), on peut lui envoyer cette expression, mais l'expression membre sera convertie de int vers object via un Convert( ), et on le verra en mode debug puisque  l'expression affichera : u => Convert(u.Id)

C'est souvent source d'incompréhension en mode debug dans les visiteurs puisqu'on va passer sur des noeuds auxquels on ne pensait pas !

Sélecteurs multiples

Quand on travaille sur les sélecteurs de propriété, on veut parfois sélectionner des propriétés imbriquées.

u => u.LegalEntity.Name

Revenons à notre pseudo langage, cela donne

Lambda(Member(Member()))

On a en effet 2 expression member imbriquées, puisqu'on accède à la propriété Name de la propriété LegalEntity du paramètre u de type User.

Lorsqu'on se trouve sur une MemberExpression, la propriété .Expression indique l'expression contenant l'expression courante, ce qui permet de remonter de proche en proche.

var body = Expression.Property(Expression.Property(param, "LegalEntity"), "Name");

Plus compliqué, si on veut accéder à une propriété d'une collection, cela donne

d => d.Users.Select(u => u.LegalEntity.Name)

Ici, la partie droite du lambda est un appel de méthode, la méthode .Select( ) des IEnumerable<T>. En regardant le code, on s'aperçoit que c'est  en réalité une extension des IEnumerable<T> déclarée dans la classe Enumerable.

C'est donc un appel de méthode statique qui prend en argument une expression member (d.Users) et une expression lambda (u =>  u.LegalEntity.Name)

Avec le pseudo langage, c'est plus flagrant

Lambda(Select(Member(), Lambda(Member(Member())))

Une MethodCallExpression, surtout celle là, est plus compliquée à fabriquer.

Il lui faut dans l'ordre :
- l'objet dont la classe contient la méthode (ici c'est null puisque la méthode est statique)
- la méthode qu'on veut appeler
- un énumération des arguments à envoyer à la méthode, ici on en a 2

En supposant qu'on a déjà fabriquer u => u.LegalEntity.Name qu'on va appeler "subLambda", cela donne

var method = {codeCompliquéQuiRécupèreLaMéthodeSelectDesEnumerable};
var body = Expression.Call(null, select, Expression.Property(param, "Users"), subLambda);

Sur une MethodCallExpression, on peut récupérer les arguments de la méthode via .Arguments[]

Visiteurs d'expression

Les visiteurs d'expression permettent de parcourir un arbre d'expression et d'en sortir soit un arbre modifié, soit une expression résultat qui n'a rien à voir avec l'arbre (genre un lambda qui retourne un booléen qui indique qu'on a trouvé ce qu'on cherchait).

On peut aussi ajouter des propriétés au visiteur qui peuvent contenir n'importe quel type d'objet.

Le principe d'un visiteur est le suivant :

- on lui envoie une expression à visiter
- il visite chaque sous expression, dans l'ordre suggérer par l'écriture de l'expression dans le pseudo langage vu plus haut)
- pour chaque expression visitée, il retourne l'expression telle quelle.

L'intérêt des visiteurs, c'est qu'on peut dériver de la classe ExpressionVisitor, et surtout qu'on peut overrider toutes les méthodes de visite.

En effet à chaque type d'expression correspond une méthode :

- VisitMember( ) sera joué pour chaque expression de type MemberExpression
- VisitMethoCall( ) pour les MethodCallExpression
- VisitLambda( ) pour les LambdaExression

Les visiteurs sont assez puissant mais nécessite plusieurs passes pour tirer profit de tout leur potentiel. C'est un peu comme un film qu'on doit revoir 3 fois pour le comprendre complètement !

Inutile de montrer ici un exemple, il faut pratiquer !

Test de Memcached

publié le 18 juin 2015 à 08:49 par Nicolas Faugout   [ mis à jour : 22 juin 2015 à 07:16 ]

Afin de mutualiser les données en cache entre les différents sites web d'un même serveur, on envisage de ne plus utiliser ni les variables statiques, ni le cache .NET qui n'est pas externalisé.

Le serveur de cache le plus simple/facile à utiliser est Memcached.

Il s'installe en tant que service windows assez facilement. Il écoute par défaut sur le port 11211 et limite sa taille à 256 Mo


Une fois démarré, on utilise son package Nuget pour abstraire la communication avec le serveur.


J'ai fait un wrapper, qui implémente les fonctions de base de Memcached et nous permettra de changer de fournisseur de cache sans modifier trop de code.

Le gros problème c'est qu'on ne peut pas envoyer n'importe quel objet à Memcached, il faut que la classe de l'objet soit binary sérialisable, ainsi que toutes les classes liées via les propriétés de l'objet.

Pour rendre une classe sérialisable, il suffit d'ajouter l'attribut [Serializable].

Memcached sérialise alors tout seul l'ensemble des propriétés de la classe, y compris :
- les Dictionnaires
- les NameValueCollection
- les interfaces => il faut que les classes concrètes correspond à l'interface soient sérialisable
- les propriétés avec setter privé seront quand même désérialisé par Memcached
- on peut sérialiser/désérialiser un objet en utilisant une interface qu'il implémente

La seule exception que j'ai trouvée jusqu'à présent est la classe .NET MailAddress qui n'est pas sérialisable et qu'il faut soit wrapper, soit remplacer par un string + méthode.

Il existe apparemment des sérialiseurs indépendants complémentaires de Memcached, que je n'ai pas encore testé

EDIT : Il  ne faut surtout pas oublier de Disposer le MemcachedClient une fois utilisé, sinon Memcached plante silencieusement sans s'arrêter ! Il faut soit utiliser le Wrapper, qui gère le boulot, soit mettre le code dans un using( ), soit le disposer à la main.

EDIT2 : Pour debuger Memcached, il est utilise d'activer le Logger, c'est simple et rapide, tout est expliqué dans cet article : https://github.com/enyim/EnyimMemcached/wiki/Configure-Logging#diagnostics-logging


Passage EF 5 vers EF 6

publié le 27 mars 2014 à 01:50 par Nicolas Faugout

La migration vers EF 6 entraîne quelques adaptations mineures à faire dans le code.

L'Enum System.Data.EntityState devient System.Data.Entity.EntityState bien que l'ancienne existe encore, mais n'est plus celle utilisée par la propriété State des Entry d'un DbContext

La librairie SqlFunctions a changé de namespace, elle passe de System.Data.Objects.SqlClient à System.Data.Entity.SqlServer

La librairie EntityFunctions devient DbFunctions, même si l'ancienne lib est toujours là, il faut désormais utiliser la nouvelle

Il faut généralement remplacer les usages de System.Data.Objects vers System.Data.Entity

Reflexion sur une méthode avec paramètre facultatif

publié le 13 mars 2014 à 08:56 par Nicolas Faugout   [ mis à jour : 13 mars 2014 à 09:01 ]

Si vous avez une méthode avec le dernier paramètre facultatif, ce qui est souvent assez pratique, comme suit :

public TOut IGet(Widget appInstance, TKey idResource, Dictionary<string, string> queryParams = null)
{ ... }

Pour aller chercher cette méthode via Reflexion, il faudra signer le dernier paramètre

var signature = new Type[] { typeof(Widget), TKey, typeof(Dictionary<string, string>) };

Mais surtout, au moment d'invoquer la méthode, il faudra obligatoirement envoyer une valeur pour ce paramètre.

var arguments = new object[] { appInstance, idRessource, null };

On ne peut donc pas exploiter le caractère facultatif de l'argument via Reflexion :S


Créer une contrainte unique en SQL sur une colonne NULLABLE

publié le 28 nov. 2013 à 08:05 par Nicolas Faugout   [ mis à jour : 28 nov. 2013 à 08:05 ]

Dans Lucca, on a la colonne "insuranceNumber" qui peut ne pas être utilisée, elle est donc nullable.

On aimerait ajouter une contrainte d'unicité sur cette colonne, mais ça plante car 2 valeurs NULL sont considérés comme un doublon !

La solution, c'est de créer un INDEX filtré !

Sachant qu'une contrainte unique, c'est exactement pareil qu'un index.


Dictionnaire ne contenant que des clés en minuscule

publié le 10 oct. 2013 à 04:53 par Romain Vergnory   [ mis à jour : 10 oct. 2013 à 04:57 ]

Il existe de nombreux dictionnaires dans iLucca qui requiert que les clés soient en minuscules.

Aujourd'hui, on l'assure à la main.

Pourquoi ne pas plutôt faire ça ?


var dico = new Dictionary<string, string>(StringComparer.OrdinalIgnoreCase)

Ne garder que les éléments d'une liste en fonction de leur appartenance à une autre liste

publié le 29 août 2013 à 01:12 par Romain Vergnory   [ mis à jour : 29 août 2013 à 01:35 ]

List<string> ElementsToRemove; //length = n
List<string> MyList; // length = m

Supposons que vous ne vouliez virer de MyList les éléments se trouvant dans ElementsToRemove (ou l'inverse, le problème est similaire).

Première méthode (méthode naïve)

var MyNewList = MyList.Where(el => !ElementsToRemove.Contains(el));

C'est l'idée, mais le problème, c'est que la méthode Contains est O(n).
Le cout total de l'opération est O(m * n) => peut mieux faire !


Deuxième méthode (méthode HashSet): 

Utiliser les HashSet et leur temps d'accès en O(1);

var ElementsToRemoveHashSet = new HashSet(ElementsToRemove);
var MyNewList = MyList.Where(el => !ElementsToRemoveHashSet .Contains(el));

Le cout passe maintenant à O(m + n) (on parcourt chaque collection une fois, une fois pour en faire un hashset, une fois pour virer les éléments qui nous déplaisent)
Mais faut écrire deux lignes, donc c'est pénible.


Troisième méthode (méthode LINQ)

var MyNewList = MyList.Except(ElementsToRemove);

La méthode LINQ fait tout pour vous, transformant les DEUX collections en HashSet : résultat cout : O(m + n)
Attention cependant, cette opération retire les doublons de MyList !!
exemple :
MyList = new List<string() { "a", "a", "b" };
ElementsToRemove = new List<string>() {"b"};
alors
MyNewList= { "a" }


A ne pas oublier si vous voulez améliorer les perfs de vos applis !

HttpResponse.WriteFile vs. HttpResponse.TransmitFile

publié le 28 août 2013 à 08:09 par Utilisateur inconnu   [ mis à jour : 29 août 2013 à 04:28 ]

Quand on demande au CC Server pour télécharger les fichiers d’un client, CC Server va zipper les fichiers dans un fichier zip, écrire le fichier dans la réponse de la requête, et supprimer le fichier zip après le finie du téléchargement. 

Cependant, si les fichiers à télécharger sont très gros (1 Go, par exemple), CC Server pose un problème. Et On aura une erreur de 
Une erreur s'est produite lors de la communication avec l'hôte distant. Le code d'erreur est 0x80070008.

En regardant les Journaux Windows dans le serveur, on a plus d’informations sur l’exception : 
Type d'exception : COMException
Message d'exception : Espace insuffisant pour traiter cette commande. (Exception de HRESULT : 0x80070008)
La commande est celle qui envoie les fichiers du client.

On trouve que CC Server utilise la méthode HttpResponse.WriteFile pour envoyer les fichiers au client. Le problème du code d’erreur 0x80070008 vient de cette utilisation.

HttpResponse.WriteFile écrite le contenu du fichier spécifié dans le flux de sortie de réponse HTTP, sous la forme d’un bloc de fichier. Ça veut dire que cette méthode va mettre un entier fichier en mémoire avant de l’envoyer. Si un fichier est gros et le serveur n’est pas une mémoire disponible aussi grand, ça provoque l’erreur 0x80070008.

La solution utilise la HttpResponse.TransmitFile au lieu de HttpResponse.WriteFile, laquelle écrit aussi le fichier dans le flux de sortie de réponse HTTP, mais sans le placer en mémoire tampon. Avec cette méthode, on pourra envoyer un gros fichier tranquillement.

Valeur par défaut des Enum

publié le 27 août 2013 à 01:37 par Nicolas Faugout   [ mis à jour : 27 août 2013 à 01:38 ]

On travaille la plupart du temps avec des classes, qui sont nativement nullable. Càd que si on déclare un attribut d'un objet comme étant de la classe MyClass et qu'on ne l'initialise pas, il est null.

public class MyMasterClass
{
    public MyClass myNullableObject;
    ...
}

En revanche, pour les types valeur comme les int par ex, l'attribut vaudra 0 par défaut.

public class MyMasterClass
{
    public int myNonNullableInteger;
    ...
}

Si on veut qu'il puisse être null, alors il faut le déclarer en tant que int?

public class MyMasterClass
{
    public int? myNonNullableInteger;
    ...
}

Là où ça se complique, c'est quand on travaille sur des List et qu'on cherche à récupérer le classique .FirstOrDefault( )

Car si la liste est vide, le Default en question dépendra directement du caractère nullable de la classe.

Par ex une liste vide de int indiquera 0 si on lui demande son .FirstOrDefault( ).

var list = new List<int>( );

list.FirstOrDefault( ) == 0 => true;
list.FirstOrDefault( ) == null => false;

Et ceci est également valable pour les enumérations, qui par défaut renvoient l'élément valant 0 ou 0 si aucun élément de la liste n'est associé à 0.

public enum BYDAYType { SU = 0, MO, TU, WE, TH, FR, SA };

var list = new List<BYDAYType>( );

list.FirstOrDefault( ) == BYDAYType.SU => true !!

Je vois 2 pistes pour gérer le cas des listes d'enum vides :

- soit on fait commencer l'enum à 1, et ainsi on peut différencier les 2 cas suivants :

public enum BYDAYType { SU = 1, MO, TU, WE, TH, FR, SA };

var list = new List<BYDAYType>( );

list.FirstOrDefault( ) == 0 => true

list = new List<BYDAYType>( BYDAYType.SU );

list.FirstOrDefault( ) == 0 => false
list.FirstOrDefault( ) == BYDAYType.SU => true

- soit on utilise, tout comme les int, le fait que la liste peut contenir des valeurs null

public enum BYDAYType { SU = 0, MO, TU, WE, TH, FR, SA };

var list = new List<BYDAYType?>( );

list.FirstOrDefault( ) == null => true

J'aime moins cette 2ème solutions pourtant plus "logique" au sens objet du terme, car elle permet d'ajouter null dans la liste, et ainsi d'avoir des choses comme ça :

public enum BYDAYType { SU = 0, MO, TU, WE, TH, FR, SA };

var list = new List<BYDAYType?>( ) { null, BYDAYType.SU, BYDAYType.MO };

list.FirstOrDefault( ) == null => true, ici First ET Default sont égaux

PropertyInfo, late binding & generic parameters

publié le 23 août 2013 à 06:00 par Nicolas Faugout   [ mis à jour : 23 août 2013 à 06:03 ]

En Reflection, on utilise parfois une PropertyInfo pour aller chercher la valeur d'une propriété d'un objet.

Mais si la PropertyInfo a été mise en cache et qu'on veut l'utiliser "plus tard" pour faire un .GetValue sur un objet dont la classe comporte des paramètres générique, alors ça plante !

Dans iLucca, c'est le cas depuis qu'on met en cache les propriétés à sérialiser via le SerializationManager, et pour la classe APICollection<T> qui contient donc 1 paramètre générique.

Pour contourner le pb, il faut aller récupérer sur l'objet, au moment du .GetValue, une version plus "fraîche" de la PropertyInfo via le type de l'objet.

On fera donc

var value = myObject.GetType().GetProperty(myStalePropertyInfo.Name).GetValue(o, null);

au lieu de

var value = myStalePropertyInfo.GetValue(myObject, null);

NB : ceci ne s'applique pas pour les classes sans paramètre générique pour lesquelles ça fonctionne très bien.

Si vous avez mieux je suis preneur !!

1-10 of 85

Comments