C 4 0 et les amliorations la BCL
C# 4. 0 et les améliorations à la BCL Pierre-Emmanuel Dautreppe – 04 Mars 2010 pierre@dotnethub. be – www. pedautreppe. com – @pedautreppe Découvrez le framework. NET 4. 0 : • les apports au langage • Quelques améliorations de la « Base Class Library »
e System on Orientée Objets opriétés, Indexeurs, Evènements avec autres technologies abilité COM ü Apparition des génériqu Evolution du langage ü Correction de manques ü Types nullables ü Classes statiques ü Simplifications ü Méthodes anonyme ü Itérateurs (yield) safe C# 3. 5 C# 2. 0 (Oct. 2005) C# 1. 0 (Nov. 2001) (3. 0: Nov. 2006) (3. 5: Nov. 2007) (3. 5 sp 1: Nov. 2008) C# 3. 5 : LINQ et méthodes ü LINQ ü Programmation déclarative ü Concis, mais statiquemen ü Méthodes Lambdas 2 ü Programmation fonctionne
Tendances d’évolution de. NET C om me nt Impératif urs on possible des es frameworks Programmation Déclarative foreach ( int i in list ) if ( i > 10 ) result. Add(i); result. Sort(); tion Déclaratif Concurrence / Parallélisation from i in list where i > 10 orderby i select i Progammation Programmation Dynamique Ecriture Dynamique Typage Implicite 3
Co & Contra Variance des génériques Agenda Paramètres nommés et optionnels Types dynamiques nteropérabilité COM Améliorations à la Base Class Library nclusion C# 4. 0 C# 3. 0 C# 2. 0 C# 1. 0 4
C# 4. 0 ü Co & Contra Variance des génériques ü Paramètres nommés et optionnels ü Types dynamiques ü Interopérabilité COM ü Améliorations à la Base Class Library Conclusion 5
Co & Contra Variance – Définition n Covariance Un opérateur de conversion de type est covariant s’il ordonne les types du plus spécifique au plus générique l Si tu me fournis une Sql. Command, alors je peux me Contravariance contenter de la traiter comme une Db. Command l Un opérateur de conversion de type est contravariant s’il ordonne les types du plus générique au plus spécifique l n l Si tu sais comparer deux « object » , alors tu sauras aussi comparer deux « Event. Args » . Ainsi, si tu as besoin d’un comparateur d’Event. Args, alors je peux te donner un comparateur d’objet 6
Co & Contra Variance – Les delegates – 1/2 public class Employé { } public class Développeur : Employé { } Func<Employé> class Program Func<Développeur> { static Employé Crée. Employé() { return null; } static Développeur Crée. Développeur() { return null; } } static void Main(string[] args) { var crée. Employé = new Func<Employé>(Crée. Employé); Employé employé 1 = crée. Employé(); var crée. Developpeur = new Func<Employé>(Crée. Développeur); Employé employé 2 = crée. Developpeur(); } } Les delegates sont covariants 7
Co & Contra Variance – Les delegates – 2/2 delegate void Event. Handler(object sender, Event. Args e); Text. Box txt. Box = new Text. Box(); txt. Box. Click += txt. Box. Key. Down += delegate void Key. Event. Handler(object sender, Key. Event. Args e); void Mon. Handler(object sender, Event. Args e) { } Text. Box txt. Box = new Text. Box(); txt. Box. Click += Mon. Handler; txt. Box. Key. Down += Mon. Handler; Les delegates sont contravariants 8
Covariance des tableaux string[] str. Array = new[] { "A", "B" }; object[] obj. Array = str. Array; obj. Array[0] = 12; string s = str. Array[0]; Les arrays sont covariants… …mais pas “type safe” System. Array. Type. Mismatch. Exception Possible seulement pour les types références int[] int. Array = new[] { 1, 2, 3}; object[] obj. Array = int. Array; Il s’agit ici d’une conversion par “boxing” Interdit 9
Co & Contra Variance des types génériques – 1/4 Génériques = Sécurité de typage List<string> str. Array = new List<string> { "A", "B" }; List<object> obj. Array = str. Array; Non « Type Safe » donc non covariant List<string> str. Array = new List<string> { "A", "B" }; IEnumerable<object> obj. Enum = str. Array; « Type Safe » donc covariant 10
Co & Contra Variance des types génériques – 2/4 public void Test. Contravariance(IComparer<string> comparer) { comparer. Compare("chaine 1", "chaine 2"); } public class My. Object. Comparer : IComparer<object> {. . . } public class My. String. Comparer : IComparer<string> {. . . } IComparer<string> string. Comparer = new My. String. Comparer(); string. Comparer. Compare("chaine 1", "chaine 2"); IComparer<object> object. Comparer = new My. Object. Comparer(); object. Comparer. Compare("chaine 1", "chaine 2") IComparer<T> est contravariant IComparer<string> string. Comparer = new My. String. Comparer(); IComparer<object> object. Comparer = new My. Object. Comparer(); Test. Contravariance(string. Comparer); Test. Contravariance(object. Comparer); 11
Co & Contra Variance des types génériques – 3/4 public interface IEnumerable<out T> : IEnumerable { IEnumerator<T> Get. Enumerator(); } n Mot-clé « out » n T ne doit être utilisé que comme type de retour n IEnumerable<T> est covariant IEnumerable<B> est considéré comme IEnumerable<A> si conversion de référence de B vers A A B IEnumerable<A> IEnumerable<B> 12
Co & Contra Variance des types génériques – 4/4 public interface IComparer<in T> { int Compare(T x, T y); } n Mot-clé « in » n T ne doit être utilisé que comme paramètre d’input n IComparer<T> est contravariant IComparer<A> est considéré comme IComparer<B> si conversion de référence de B vers A A B IComparer<A> IComparer<B> 13
Variance en. NET 4. 0 Interfaces System. Collections. Generic. IEnumerable<out T> System. Collections. Generic. IEnumerator<out T> System. Linq. IQueryable<out T> System. Collections. Generic. IComparer<in T> System. Collections. Generic. IEquality. Comparer<in T> System. IComparable<in T> Delegates System. Func<in T, …, out R> System. Action<in T, …> System. Predicate<in T> System. Comparison<in T> System. Event. Handler<in T> 14
C# 4. 0 ü Co & Contra Variance des génériques ü Paramètres nommés et optionnels ü Types dynamiques ü Interopérabilité COM ü Améliorations à la Base Class Library Conclusion 15
Paramètres Optionnels et Nommés – 1/4 File. Stream Open(string path, File. Mode mode, File. Access access, File. Share share) File. Stream Open(string path, File. Mode mode, File. Access access) File. Stream Open(string path, File. Mode mode) Une méthode principale Plusieurs overloads Redirection avec des valeurs par défaut 16
Paramètres Optionnels et Nommés – 2/4 public void Methode(int x, int y = 5, int z = 10) { } 2 paramètres optionnels Methode(10, 2, 15); Methode(10, 2); //Equivalent à Methode(10, 2, 10); Methode(10); //Equivalent à Methode(10, 5, 10); Methode(10, , 15); //INTERDIT Les paramètres omis doivent être en dernier 17
Paramètres Optionnels et Nommés – 3/4 Methode(10, z: 15); Les paramètres nommées doivent être en dernier Les paramètres non optionnels doivent être spécifiés Methode(y: 15, x: 10); Methode(y: 15, x: 10, z: 2); Les paramètres nommés peuvent être dans n’importe quel ordre Les paramètres nommés sont évalués dans l’ordre d’écriture 18
Paramètres Optionnels et Nommés – 4/4 n Identique à VB. NET n Paramètres optionnels n Doivent être des constantes de compilation n Valeur par défaut copiée dans l’appel n Paramètres nommés n La compilation se base sur les noms Ne changez pas votre API Même question que pour const ou static readonly A utiliser plutôt en temps que client 19
C# 4. 0 ü Co & Contra Variance des génériques ü Paramètres nommés et optionnels ü Types dynamiques ü Interopérabilité COM ü Améliorations à la Base Class Library Conclusion 20
Exemples appels « dynamiques » avec C# 3. 5 Calculator calc = Get. Calculator(); int resultat = calc. Add(10, 20); object calculator = Get. Calculator(); Type calc. Type = calculator. Get. Type(); int result = (int)calc. Type. Invoke. Member("Add", Binding. Flags. Invoke. Method, null, calculator, new object[] { 10, 20 }); Silverlight Javascript C# Script. Object calc = Get. Calculator(); int resultat = (int)calc. Invoke("Add", 10, 20); Script. Scope python = Python. Create. Runtime(). Use. File("Calculator. py"); Script. Engine engine = python. Engine; int resultat = (int)engine. Execute ("Get. Calculator(). Add(10, 20)", python); C# Iron. Python 21
Iron. Python Iron. Ruby C# Visual Basic Autres… Dynamic Language Runtime (DLR) Expression Trees POCO Binder Java. Script Binder Dynamic Dispatch Python Binder Call Site Caching Ruby Binder COM Binder < 22
Types dynamiques – 1/3 Calculator calc = Get. Calculator(); int resultat = calc. Add(10, 20); Statiquement typé comme “dynamic” dynamic calc = Get. Calculator(); int resultat = calc. Add(10, 20); Conversion dynamique de la valeur de retour Appel dynamique de la méthode 23
Types dynamiques – 2/3 Type statique (à la compilation) dynamic Type dynamique(à l’exécution) dynamic x = 1; dynamic y = "Bonjour"; dynamic z = new List<int> { 1, 2, 3 }; Int 32 String List<int> Quand les opérandes sont dynamic… n La sélection des membres est différée à l’exécution n A l’exécution, dynamic est remplacé par le type réel de chaque membre n Le type de retour de l’opération est également dynamic 24
Types dynamiques – 3/3 public class Calculator { public double Add(double a, double b) {. . . } public int Add(int a, int b) {. . . } } Calculator calc = Get. Calculator(); double x = 2. 25, y = 3. 75; double result = calc. Add(x, y); A la compilation : double Add(double, double) Calculator calc = Get. Calculator(); dynamic x = 2. 25, y = 3. 75; dynamic result = calc. Add(x, y); A l’exécution : double Add(double, double) Calculator calc = Get. Calculator(); dynamic x = 2, y = 3; dynamic result = calc. Add(x, y); A l’exécution : int Add(int, int) 25
Types dynamiques – Points à retenir n La résolution des méthodes est différée à l’exécution si nécessaire public void Methode(dynamic d) n On ne peut pas appeler des méthodes d’extensions n A savoir (ou non): n Nouveaux types de conversion : assignation, structurelle, … n dynamic n’existe pas au niveau du code IL 26
Types dynamiques – Exemples d’écritures – 1/2 n Ces écritures sont valides dynamic Ma. Methode(int i, dynamic d) {. . . } dynamic[] mon. Array =. . . IEnumerable<dynamic> enumerable =. . . List<dynamic> liste =. . . class Base. Class<T> { } class Derived : Base. Class<dynamic> { } 27
Types dynamiques – Exemples d’écritures – 2/2 n Ces écritures ne sont pas valides class C : dynamic { } class C : IEnumerable<dynamic> { } class C<T> where T : Base. Class<dynamic> { } n Pour plus d’information : Blog de Chris Burrows http: //blogs. msdn. com/cburrows/default. aspx 28
Créer un type dynamique – 1/2 Dans System. Core. dll Namespace System. Dynamic IDynamic. Object a été renommé public class Dynamic. Object : IDynamic. Meta. Object. Provider { public virtual IEnumerable<string> Get. Dynamic. Member. Names(); public virtual Dynamic. Meta. Object Get. Meta. Object(Expression parameter); public virtual bool Try. Convert(Convert. Binder binder, out object result); public virtual bool Try. Delete. Member(Delete. Member. Binder binder); public virtual bool Try. Get. Member(Get. Member. Binder binder, out object result); public virtual bool Try. Set. Member(Set. Member. Binder binder, object value); public virtual bool Try. Binary. Operation(Binary. Operation. Binder binder, object arg, out object result); public virtual bool Try. Unary. Operation(Unary. Operation. Binder binder, out object result); . . . } 29
Créer un type dynamique – 2/2 public class Mon. Dynamic. Object : Dynamic. Object { Dictionary<string, object> dic = new Dictionary<string, object>(); public override bool Try. Get. Member(Get. Member. Binder binder, out object result) { return this. dic. Try. Get. Value(binder. Name, out result); } public override bool Try. Set. Member(Set. Member. Binder binder, object value) { this. dic[binder. Name] = value; return true; } dynamic mon. Objet = new Mon. Dynamic. Object(); } mon. Objet. Nom = "Dautreppe"; mon. Objet. Prenom = "Pierre-Emmanuel"; Console. Write. Line(mon. Objet. Prenom + " " + mon. Objet. Nom); 30
Types dynamiques – Utilisation de la classe Expando static void Main() { dynamic personne = new Expando. Object(); //1. Définir des propriétés personne. Nom = "Dautreppe"; personne. Prenom = "Pierre-Emmanuel"; //2. Définir des méthodes personne. To. String = new Func<string>( () => personne. Nom + " " + personne. Prenom ); Console. Write. Line(personne. To. String()); //3. Définir un évènement personne. Mon. Event = null; personne. On. Mon. Event = new Action<Event. Args>((e) => { if (personne. Mon. Event != null) personne. Mon. Event(personne, e); }); personne. Mon. Event += new Event. Handler(Mon. Event. Handler); personne. On. Mon. Event(Event. Args. Empty); } private static void Mon. Event. Handler(dynamic obj, Event. Args e) { Console. Write. Line("Mon. Event appelé sur '" + obj. To. String()); } 31
Types dynamiques – Conclusion – 1/2 n Que peut-on appeler ? n Toute méthode définie sur l’instance l l Méthode publique Méthode protected Méthode private (de l’instance) Méthode d’interface (si implémentation implicite) n Que ne peut-on pas appeler ? n Toute méthode « n’existant pas » sur l’instance l l Méthode private (d’une classe de base) Méthode static (quelque soit sa visibilité) Méthode d’interface (si implémentation explicite) Méthode d’extension 32
Types dynamiques – Conclusion – 2/2 n Quand doit-on les utiliser ? n Quand on travaille sans type (par ex. réflexion) n Quand on fait de l’interop l l l n COM Silverlight Javascript … Compenser des manques du Framework l « INumeric » n Quand ne devrait-on pas les utiliser ? n Pour compenser une erreur de design l Eg Coder en utilisant une classe de base, mais avoir besoin d’une connaissance des types dérivés 33
C# 4. 0 ü Co & Contra Variance des génériques ü Paramètres nommés et optionnels ü Types dynamiques ü Interopérabilité COM ü Améliorations à la Base Class Library Conclusion 34
Interopérabilité COM – 1/3 n Paramètres optionnels et nommés object file. Name = "Test. docx"; object missing = System. Reflection. Missing. Value; doc. Save. As(ref file. Name, ref missing, ref missing, ref missing, ref missing, ref missing); doc. Save. As("Test. docx"); ü Les APIs COM déclarent des paramètres optionnels ü « ref » n’est plus obligatoire 35
Interopérabilité COM – 2/3 n COM et dynamic IExcel. Trendlines trend. Lines = (IExcel. Trendlines)serie. Trendlines(Type. Missing); IExcel. Range cell = (IExcel. Range)sheet. Cells[row, column]; IExcel. Trendlines trend. Lines = serie. Trendlines(); IExcel. Range cell = sheet. Cells[row, column]; ü Les APIs COM renvoient des dynamic ü Conversion d’assignation vers le type désiré 36
Interopérabilité COM – 3/3 n « No PIA » : Pas de « Primary Interop Assemblies » namespace Office. Application { Par défaut : True class Program { static void Main(string[] args) { Application excel = new Application(); Worksheet = excel. Sheets. Add(); Chart. Objects charts = sheet. Chart. Objects(); Chart. Object chart = charts. Add(10, 100, 100); Series. Collection series = chart. Chart. Series. Collection(); Series serie = series. Add(. . . ); Trendlines trend. Lines = serie. Trendlines(); Range cell = sheet. Cells[10, 10]; } } } 37
C# 4. 0 ü Co & Contra Variance des génériques ü Paramètres nommés et optionnels ü Types dynamiques ü Interopérabilité COM ü Améliorations à la Base Class Library Ø TUples Conclusion Ø Code Contracts Ø System. IO Ø Parallel Extensions Ø Fichiers mappés Ø System. Numerics 38
BCL – Tuples – 1/2 public Tuple<bool, int> Try. Get. Element(string key) { int valeur; bool result = dic. Try. Get. Value(key, out valeur); return new Tuple<bool, int>(result, valeur); } return Tuple. Create(result, valeur); Supporté nativement par F# et Iron. Python System. Tuple : Factory de Tuple<. . . > public static class Tuple { public static Tuple<T 1> Create<T 1>(T 1 item 1); public static Tuple<T 1, T 2> Create<T 1, T 2>(T 1 item 1, T 2 item 2); . . . De 1 à 8 types génériques public static Tuple<T 1, T 2, T 3, T 4, T 5, T 6, T 7, TRest> Create<T 1, T 2, T 3, T 4, T 5, T 6, T 7, Trest> (T 1 item 1, T 2 item 2, T 3 item 3, T 4 item 4, T 5 item 5, T 6 item 6, T 7 item 7, TRest rest); } Typiquement un autre Tuple<. . . > 39
BCL – Tuples – 2/2 « new Tuple » et « Tuple. Create » sont équivalents presqu e !! var a = new Tuple<int, int, Tuple<int, int>> (1, 2, 3, 4, 5, 6, 7, new Tuple<int, int>(8, 9)); var b = Tuple. Create(1, 2, 3, 4, 5, 6, 7, Tuple. Create(8, 9)); Console. Write. Line(a. Item 5); // 5 Console. Write. Line(a. Rest. Item 2); // 9 Console. Write. Line(b. Item 5); // 5 Console. Write. Line(b. Rest. Item 1. Item 2); // 9 Rest = Tuple<int, int>> 40
BCL – Améliorations à System. IO string[] Get. Files(string path, string search. Pattern); var errorlines = from file in Directory. Get. Files(@"C: logs", "*. log") from line in File. Read. All. Lines(file) where line. Starts. With("Error: ") select string. Format("File={0}, Line={1}", file, line); File. Write. All. Lines(@"C: errorlines. log", errorlines); string[] Read. All. Lines(string path); void Write. All. Lines(string path, string[] contents); IEnumerable<string> Enumerate. Files(string path, string search. Pattern); var errorlines = from file in Directory. Enumerate. Files(@"C: logs", "*. log") from line in File. Read. Lines(file) where line. Starts. With("Error: ") select string. Format("File={0}, Line={1}", file, line); File. Write. All. Lines(@"C: errorlines. log", errorlines); IEnumerable<string> Read. Lines(string path); void Write. All. Lines(string path, IEnumerable<string> contents); 41
BCL – Fichiers mappés en mémoire – 1/2 Environment. System. Page. Size Scénario 1 Exploite la mémoire paginée du ü Gestion de la mémoire par système le système Ressource ü Pagination automatique en partagée mémoire Vue 1 Process 1 Vue 2 Gros Fichier Vue N Scénario 2 Vue 1 Vue N Taille de la vue <= Mémoire disponible pour mapping (2 Gb sur 32 bits) Process 1 Vue N Vue 1 ü Mémoire partagée Stream (lecture séquentielle) Accessor (lecture aléatoire) Process 2 ü Simplification pour IPC (Inter-Process Communication) 42
BCL – Fichiers mappés en mémoire – 2/2 Process 1 using (var mmf = Memory. Mapped. File. Create. New("Fichier. Mappé", 1000)) { using (var stream = mmf. Create. View. Stream()) new Binary. Writer(stream). Write("Bonjour"); var start. Info = new Process. Start. Info("Autre. Process. exe"); start. Info. Use. Shell. Execute = false; Process. Start(start. Info). Wait. For. Exit(); } Ou mmf. Create. View. Accessor Process 2 using (var mmf = Memory. Mapped. File. Open. Existing("Fichier. Mappé")) { using (var stream = mmf. Create. View. Stream()) Console. Write. Line(new Binary. Reader(stream). Read. String()); } 43
Code. Contracts – 1/2 n Permettre l’ajout de contrats dans le code n Disponible dans 3. 5 (installation séparée) n 4 parties n Une nouvelle API System. Diagnostics. Contracts n Le « rewriter » (ccrewrite. exe) l n Le « static checker » (cccheck. exe) l n Injecte le code de vérification des contrats « aux bons endroits » Analyse le code pour vérifier si les contrats sont respectés Le générateur d’assembly (ccrefgen. exe) l Produit une DLL avec les contrats seuls 44
Code. Contracts – 2/2 public class Compte. Bancaire { private int numero. Compte, modulo; private double solde; • Contract. Requires • Pré-condition • Evalué « avant » l’appel public void Effectue. Depot(double montant) • Contract. Ensures { { • Post-condition Contract. Requires(montant > 0); this. solde += montant; Contract. Requires(montant > 0); • Evalué « après » l’appel Contract. Ensures(Contract. Old. Value(this. solde) < this. solde); } this. solde += montant; Contract. Ensures(ancien Contract. Ensures(Contract. Old. Value(this. solde) solde < nouveau solde); < this. solde); } • Contract. Old. Value this. solde += montant; static void Main() • Retourne la valeur avant } { static void Main() l’appel var compte = new Compte. Bancaire { numero. Compte = 0011234567, modulo = 27 }; { [Contract. Invariant. Method] compte. Effectue. Depot(1000); var compte = new Compte. Bancaire { numero. Compte = 0011234567, modulo = 27 }; static void Main() private void Object. Invariant() } compte. Effectue. Depot(1000); { • Contract. Invariant. Method } { } var compte = new Compte. Bancaire { numero. Compte = 0011234567, modulo = 27 }; • Invariance de l’objet Contract. Invariant(this. solde >= 0); } compte. Effectue. Depot(1000); • Appelé après chaque } Contract. Invariant(this. numero. Compte % 97 == this. modulo); appel public } } static void Main() { var compte = new Compte. Bancaire { numero. Compte = 0011234567, modulo = 27 }; compte. Effectue. Depot(1000); } } 45
PFX – Parallel Framework Extensions static void Main() { IEnumerable<int> liste = Enumerable. Range(0, 10000000); var query = from i in liste. As. Parallel() where Est. Nombre. Premier(i) • System. Collection. Concurrent select i; • Blocking. Collection<T> Console. Write. Line(query. Count()); • Concurrent. Queue<T> } • Concurrent. Dictionary<K, V> static bool Est. Nombre. Premier(int p) { int limite = (int)Math. Sqrt(p); for (int i = 2; i <= limite; i++) if (p % i == 0) return false; return true; } Parallel. Invoke(() => Realise. Action 1(), () => Realise. Action 2(), () => Realise. Action 3()); • Lazy<T> • System. Threading • Semaphore. Slim • Manuel. Reset. Event. Slim • Spin. Lock • Visual Studio • Nouveau Debugger • Nouveau Profiler 46
System. Numerics – 1/2 n Une nouvelle DLL : System. Numerics. dll n Big. Integer l n Un entier, « arbitrairement grand » Complex l Structure pour représenter des nombres complexes 47
System. Numerics – 2/2 n Big. Integer static Big. Integer Factorielle(Big. Integer i) { if (i == 2) return 2; return i * Factorielle(--i); } static void Main() { Console. Write. Line(Factorielle(5)); } Console. Write. Line(Factorielle(10)); } Console. Write. Line(Factorielle(55)); } Console. Write. Line(Factorielle(542)); } • Prévu pour. NET 3. 5 • Retardé pour problème de performance • Toutes opérations de int • Autres opérations (statiques) • Abs • Div. Rem • Greatest. Common. Divisor • Remainder 120 3628800 12696403353658275925965100847566516959580321051449436762275840000000 16075350953780118910563284842255295952438387984990549947660251569392881698108 35272449306354580237969682411561904659247352233869578500369085015849202525200 // 11 autres lignes de chiffres 27593805066916468242618448796850243328574235699250768859321062302677755711983 88077661722722820187861511237739216896000000000000000000000000000000000000000 48 000000000
C# 4. 0 ü Co & Contra Variance des génériques ü Paramètres nommés et optionnels ü Types dynamiques ü Interopérabilité COM ü Améliorations à la Base Class Library Conclusion 49
Conclusion – 1/2 n & Framework 4. 0 n Nouveautés au niveau du langage n Codage l l n Simplifications d’écriture Amélioration des performances Design l l l Interopérabilité simplifiée et homogénéisée « Design By Contracts » Framework de concurrence 50
Conclusion – 2/2 n Nouveautés au niveau de l’environnement n Team Foundation Server se démocratise n Nouveaux debuggers n Modélisation UML n Nouveautés au niveau des frameworks n ASP. NET, MVC, AJAX 4. 0, Sync Framework, F#… n Et Après ? n C# 5. 0 (? ) : « Compiler As A Service » Tout simplement une nouvelle révolution 51
Merci à tous ! n N’oubliez pas votre formulaire de participation au concours! n Prochains évènements n 09/03 : Windows Live Meeting – VS 2010 -. NET 4. 0 n 23/03 : My. TIC : Sharepoint 2010 n 30/03 au 01/04 : Tech Days 2010 n 21/04 : Dot. Net. Hub : Méthodes Agiles n 28/04 : Dot. Net. Hub : NService Bus 52
- Slides: 52