var, var, var... marre, marre, marre...
Au risque de passer pour un vieux c** : pourquoi mettre du var partout ?
Je parle de l'utilisation avec autre chose que des types anonymes bien sûr.
Je parle de l'utilisation avec des vrais types, issus de la BCL ou non.
var str = "bla";
var i = 1;
var value = instance.Value;
On sent tout de suite la puissance du truc là, une vraie révolution.
On risque surtout de bien sentir passer la maintenance du code.
C'est énervant à la lecture
var i = 1;
var value = instance.Value;
Exercice : Au premier coup d'oeil, donner le type de "i".
Vous savez que le type sous un littéral de ce genre est int ? Et vos collègues ?
Exercice : Au premier coup d'oeil, donner le type de "value".
Là vous savez ? Recommencez la semaine prochaine.
C'est particulièrement énervant quand c'est utilisé dans des articles : si vous avez le malheur de lire l'article sans avoir sous la main votre documentation et/ou une connexion à internet, vous vous retrouvez avec une question bien sympathique : "mais c'est quoi le type sur lequel une méthode DoSomethingReallyCool est disponible ?"
var value = instance.Value;
...
value.DoSomethingReallyCool();
Ca m'est arrivé ce matin...
C'est risqué vis-à-vis des changements du code externe
En dehors du côté énervant à la lecture, un autre problème se pose : la détection des changements potentiellement critiques dans le code utilisé.
Imaginons que notre propriété Value retourne une valeur Int32.
Après récupération de cette valeur, nous la transmettons à un autre code/système qui pour une raison ou une autre ne la demande pas sous forme fortement typée Int32 mais plutôt Object, Byte[], ...
public class OtherClass
{
public void DoSomethingWith32Bits(Object value)
{
// ...
}
}
Nous pouvons utiliser au choix l'un de ces 2 codes (oui, je sais : ils sont bidons et moches mais ça suffira) : Method1 utilise un vrai type, Method2 laisse le compilateur inférer le type :
private void Method1()
{
OneClass instance = new OneClass();
OtherClass otherInstance = new OtherClass();
Int32 value = instance.Value;
otherInstance.DoSomethingWith32Bits(value);
}
private void Method2()
{
OneClass instance = new OneClass();
OtherClass otherInstance = new OtherClass();
var value = instance.Value;
otherInstance.DoSomethingWith32Bits(value);
}
Plus tard, le code de OneClass est changé : Value renvoie maintenant un Int64.
Oui, bien sûr, en théorie ce n'est pas censé arriver... Mais il y a la théorie et la pratique. Surtout si nous ne sommes pas maître du code de OneClass.
Avec le code typé normalement (Method1), notre ami le compilateur nous prévient à la compilation suivante : "Cannot implicitly convert type 'long' to 'int'. An explicit conversion exists (are you missing a cast?)"
Nous pouvons aller nous expliquer avec le propriétaire de OneClass, ou apporter les corrections nécessaires si c'était prévu.
Avec le code typé n'importe comment (Method2), nous venons de basculer automatiquement d'un entier 32 bits à un entier 64 bits.
Pratique ! J'espère juste pour nous que le code/système derrière OtherClass va lui aussi apprécier le changement, dans le cas contraire nous risquons quelques blagues particulièrement drôles :
- Crash à la première exécution du code
Peut être en prod si le code n'est pas couvert par des tests systématiques. Car si cette fonctionnalité n'est pas censée être impactée par l'opération de maintenance, elle ne sera peut être pas testée avant la mise en prod.
- Soft : nous avons de la chance, c'est juste l'application.
- Hard : OtherClass utilise en interne du code non managé et nous n'avons vraiment pas de chance, notre valeur est utilisée une fois l'exécution passée en kernel mode.
Un bon BSOD nous fera bien voir par nos utilisateurs (ou plus si notre code est hébergé sur un système non dédié).
- Nous nous apercevons au bout de plusieurs jours/semaines/mois que nous avons corrompus des données
On ne parle ici que d'un simple changement de type de valeur, mais avec l'utilisation de var il est de manière générale possible de basculer d'un type à un autre sans s'en rendre compte du moment que ce dernier expose les mêmes membres que ceux utilisés par le code spécifiant var comme "type".
Prenons le code suivant :
public class OneClass
{
public NiceClass Value
{
get
{
return new NiceClass();
}
}
}
public class NiceClass
{
public void DoSomethingNice()
{
Console.WriteLine("Hello !");
}
}
class Program
{
static void Main(string[] args)
{
OneClass instance = new OneClass();
var niceInstance = instance.Value;
niceInstance.DoSomethingNice();
}
}
Sortie : "Hello !"
Ajoutons le code de FakeNiceClass
public class FakeNiceClass
{
public void DoSomethingNice()
{
Console.WriteLine("BOOM !");
}
}
Et changeons juste le code de OneClass :
public class OneClass
{
//public NiceClass Value
//{
// get
// {
// return new NiceClass();
// }
//}
public FakeNiceClass Value
{
get
{
return new FakeNiceClass();
}
}
}
Ca compile sans problème, et la sortie est "BOOM !".
La version de Main avec vrai typage aurait nécessité, pour passer la phase de compilation, de mettre en place un opérateur de conversion implicite :
- sur FakeNiceClass, et c'est quand même le code de NiceClass.DoSomethingNice qui aurait été exécuté, pas celui de FakeNiceClass.DoSomethingNice.
- sur NiceClass, et c'est le code de NiceClass.DoSomethingNice qui aurait été exécuté, sauf décision explicite dans NiceClass.DoSomethingNice de déléguer l'exécution à FakeNiceClass.DoSomethingNice persistée lors de la conversion.
Déjà plus compliqué à réussir de manière non intentionnelle...
Ce post vous a plu ? Ajoutez le dans vos favoris pour ne pas perdre de temps à le retrouver le jour où vous en aurez besoin :