L'installation de .NET 4.5 n'est pas anodine : le targeting .NET 4.0 n'isole pas de tous les changements brisants, au runtime comme à la compilation
.NET 4.5 est un remplacement de .NET 4.0, pas une version qui s'installe à côté.
Ça semble évident une fois que c'est dit mais je pense qu'il est tout de même utile de faire un petit rappel, juste au cas où :-)
La prise en compte de ce fait dès maintenant pour des applications .NET 4.0 n'étant pas amenées à être migrées explicitement vers .NET 4.5, mais qui pourraient être amenées à cohabiter avec des applications .NET 4.5, pourrait permettre d'être proactif sur la correction d'éventuels bugs et d'éviter des effets de bord lors de la mise en production de l'autre application (ou pourrait aider à justifier une isolation de l'environnement des applications existantes, au besoin).
Ce rappel pourrait aussi être utile à ceux qui analysent des bugs sur des applications déployées dans des environnements qu'ils ne maitrisent pas forcément complètement : prendre en compte ce fait dans le processus d'analyse d'incident pourrait leur faire gagner un peu de temps.
Je parle ici de l'installation du framework seul et non pas de celle du SDK ou de Visual Studio 2012.
Changements ayant un impact à l'exécution d'une application déjà compilée pour .NET 4.0
Une liste de changements brisants est disponible ici : Application Compatibility in the .NET Framework 4.5
Ces changements peuvent avoir un impact direct sur une application .NET 4.0 existante sans qu'elle soit recompilée.
Prenons l'exemple du changement "Unobserved exceptions in System.Threading.Tasks.Task operations" de la catégorie "Core" :
Change : Because the System.Threading.Tasks.Task class represents an asynchronous operation, it catches all non-severe exceptions that occur during asynchronous processing. In the .NET Framework 4.5, if an exception is not observed and your code never waits on the task, the exception will no longer propagate on the finalizer thread and crash the process during garbage collection.
Impact : This change enhances the reliability of applications that use the Task class to perform unobserved asynchronous processing. The previous behavior can be restored by providing an appropriate handler for the TaskScheduler.UnobservedTaskException event.
Prenons cet exemple de code mettant en avant une Task, avec exception non gérée, dont on n'attend pas le résultat :
static void Main(string[] args)
{
Task.Factory.StartNew(() =>
{
throw new NotImplementedException();
});
// Attente de 5 secondes pour laisser de la marge
Thread.Sleep(5000);
// Forçage d'une collection complète du GC
// et attente de l'exécution des finaliseurs
GC.Collect();
GC.WaitForPendingFinalizers();
Console.WriteLine("Press a key to quit...");
Console.ReadKey(true);
}
Sous .NET 4.0, ce type de code entrainait forcément le crash de l'application au moment de la finalisation de l'instance de Task :
Unhandled Exception: System.AggregateException: A Task's exception(s) were not observed either by Waiting on the Task or accessing its Exception property. As a result, the unobserved exception was rethrown by the finalizer thread. ---> System.NotImplementedException: The method or operation is not implemented.
at Program.<Main>b__0()
at System.Threading.Tasks.Task.InnerInvoke()
at System.Threading.Tasks.Task.Execute()
--- End of inner exception stack trace ---
at System.Threading.Tasks.TaskExceptionHolder.Finalize()
Une fois .NET 4.5 installé, et sans rien faire d'autre, elle ne crashera plus.
A première vue excellente nouvelle pour une vraie application sauf que, suivant ce qu'elle fait de ce qui ressort du traitement en échec, le changement de comportement peut avoir des conséquences plus ou moins graves qui étaient peut-être, jusqu'alors, évitées du fait que l'application s'arrêtait brutalement avant que l'état non validé ne soit utilisé (en partant du principe que l'instance de Task était finalisée suffisamment tôt).
Changements ayant un impact à la compilation (csc.exe, MSBuild et VS2010 sous certaines conditions)
En tant que remplacement de .NET 4.0, .NET 4.5 met aussi à jour le compilateur C# livré avec le Framework (csc.exe) : tout code compilé via csc.exe risque donc de bénéficier de certains changements qui lui ont été apportés dès qu'une nouvelle compilation aura lieu après l'installation de .NET 4.5.
Cela peut avoir un impact sur certaines applications où du code est compilé à la volée via csc.exe (comme des sites ASP.NET, par exemple).
Concernant la compilation via MSBuild (ligne de commande et agents de build) et Visual Studio 2010, je pense que le comportement va dépendre de la configuration MSBuild utilisée pour le projet.
Je n'ai pas encore vraiment creusé ce vaste sujet.
Concernant la compilation sous Visual Studio 2012 en targeting .NET 4.0, la question ne se pose pas : les améliorations du compilateur éligibles s'appliquent car selon toute logique le compilateur in-process doit avoir le même comportement que csc.exe.
En tout cas, là encore, si la compilation bascule sur les dernières évolutions il peut y avoir quelques effets de bord.
Prenons cet exemple de code connu pour ne pas donner le résultat auquel on aurait pu s'attendre en .NET 4.0 (exemple bateau, mais dans l'immédiat je n'ai pas trouvé d'idée de code concret qui n'ajoute pas trop de détails masquant le fond du problème) :
static void Main(string[] args)
{
Int32[] integers = new Int32[] { 1, 2, 3, 4, 5 };
// !!! NE PAS UTILISER CE CODE !!!
// !!! DON'T USE THAT CODE !!!
foreach (Int32 integer in integers)
{
Task.Factory.StartNew(() =>
{
Console.WriteLine(integer);
});
}
Thread.Sleep(2000);
Console.WriteLine("Press a key to quit.");
Console.ReadKey(true);
}
En .NET 4.0, au lieu d'afficher 5 chiffres différents dans un ordre indéterminé comme on aurait pu s'y attendre à la première lecture, ce code a toutes les chances d'afficher une série de cinq caractères '5'.
Sans rentrer dans les détails, ce comportement est lié à la façon dont le compilateur va émettre le code C# correspondant à cette utilisation d'une variable hors scope dans le délégué, et à l'endroit où est déclarée cette variable.
Pour mémoire, afin d'obtenir l'effet désiré sous .NET 4.0, il fallait corriger le code d'une de ces manières :
static void Main(string[] args)
{
Int32[] integers = new Int32[] { 1, 2, 3, 4, 5 };
// !!! NE PAS UTILISER CE CODE !!!
// !!! DON'T USE THAT CODE !!!
foreach (Int32 integer in integers)
{
Int32 i = integer;
Task.Factory.StartNew(() =>
{
Console.WriteLine(i);
});
}
Thread.Sleep(2000);
Console.WriteLine("Press a key to quit.");
Console.ReadKey(true);
}
static void Main(string[] args)
{
Int32[] integers = new Int32[] { 1, 2, 3, 4, 5 };
// !!! NE PAS UTILISER CE CODE !!!
// !!! DON'T USE THAT CODE !!!
foreach (Int32 integer in integers)
{
Task.Factory.StartNew((i) =>
{
Console.WriteLine(i);
},
integer);
}
Thread.Sleep(2000);
Console.WriteLine("Press a key to quit.");
Console.ReadKey(true);
}
Le compilateur C# livré avec .NET 4.5 n'émet plus le même code IL que celui de .NET 4.0 pour le premier exemple de code : ce qu'on pouvait considérer comme un bug du compilateur ou plutôt un comportement non-intuitif a été corrigé pour cette release et le code compilé affichera une liste de 5 chiffres différents dans un ordre indéfini, comme initialement attendu.
Ce changement est visible pour une compilation sous Visual Studio 2012 (y compris en cible .NET 4.0) ou en utilisant le CSC mis à jour par l'installation de .NET 4.5, mais pourrait aussi s'appliquer comme dit plus haut à des compilations depuis Visual Studio 2010 ou MSBuild en général.
Là encore cela peut sembler être une bonne nouvelle, sauf que si l'environnement de maintenance d'une application existante n'est pas isolé de celui servant à produire les nouvelles applications ciblant le nouveau framework .NET, on risque de se retrouver à livrer une régression à un endroit non modifié du code : si jamais l'application fonctionnait correctement avec ce bug non détecté, le fait que le code initialement défaillant "tombe en marche" peut avoir des conséquences indésirables.
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 :