Navigation

Inscrivez-vous gratuitement
pour pouvoir participer, suivre les réponses en temps réel, voter pour les messages, poser vos propres questions et recevoir la newsletter

  1. #1
    Chroniqueur Actualités

    C# 9.0 est en développement et Microsoft prévoit de nouvelles fonctionnalités
    C# 9.0 est en développement et Microsoft prévoit de nouvelles fonctionnalités
    comme les constructeurs principaux qui permettent de réduire les frais généraux de programmation

    Microsoft a publié C# 8.0 en septembre dernier et a aussi entamé les travaux pour la version 9.0 du langage. Plusieurs propositions ont été faites parmi lesquelles l’on retrouve des fonctionnalités très intéressantes. La prochaine version de C# pourrait apporter les expressions switchs, les types records et un code de validation des paramètres nuls simplifié. Le jalon C# 9.0 sur GitHub recense toutes les fonctionnalités proposées par la communauté, mais n’annonce aucune date de sortie officielle pour la prochaine version de C#. Voici quelques-unes des propositions.

    Validation nulle simplifiée

    Cette nouvelle fonctionnalité permet de simplifier la validation des paramètres standard en utilisant une petite annotation sur les paramètres. En décorant la valeur d'un paramètre à une méthode avec une petite annotation, cela facilite la logique interne et l’on n’a plus besoin de clauses de validation/garde nulles. Cela a pour effet de réduire le code de validation passe-partout.

    Constructeurs principaux

    Selon la description de la fonctionnalité, les constructeurs principaux permettent de réduire les frais généraux de programmation en plaçant les arguments des constructeurs directement dans le champ d'application d'une classe. En gardant votre logique de construction minimale, vous éliminez le code superflu qui encourage l'écrémage des fichiers de code au lieu de faire un véritable examen du code. En ayant moins de code, vous rendez plus difficile la dissimulation de bogues et votre code est plus significatif.

    En d’autres mots, vous n’avez plus besoin de déclarer explicitement un champ d'appui. Cela simplifie tous ces constructeurs, ces champs, ces getters/setters de propriété, etc. auxquels vous êtes si habitués.


    Classes record

    C’est un formulaire de déclaration simplifié pour une classe C# ainsi que les types de structures. Les records sont similaires aux constructeurs principaux cités en haut. Le but de cette proposition est de supprimer la nécessité d'écrire autant de code passe-partout lors de la création d'une nouvelle classe ou d’une structure. Ils fournissent un mécanisme de déclaration d'un type de données en décrivant les membres de l'agrégat ainsi que le code supplémentaire ou les écarts par rapport à l'habituel passe-partout, le cas échéant.

    Unions discriminées via les classes Enum

    Le terme d'union discriminée (union disjointe) est emprunté aux mathématiques. L'union discriminée est également largement utilisée dans les langages de programmation, et sert à additionner les types de données existants. Dans C# 9.0, il permet de définir des types pouvant contenir un nombre quelconque de types de données différents. Leur fonctionnalité est comparable à celle des unions C++ ou des variantes Visual Basic, avec l'avantage d'être plus sûres.
    Les unions discriminées sont utiles pour les données hétérogènes ; les données qui peuvent avoir des cas individuels, avec des cas de validité et d'erreur ; les données dont le type varie d'une instance à l'autre. En outre, elle offre une alternative pour les petites hiérarchies d'objets.

    CallerArgumentExpression

    Cette fonctionnalité a pour but de permettre aux développeurs de capturer l'expression transmise à une méthode, de permettre de meilleurs messages d'erreur dans les API de diagnostic/test et de réduire le nombre de frappes.

    Déclarations de haut niveau et déclarations des membres, englobant un dialecte de scripts dans le C#

    Le compilateur C# comprend actuellement un dialecte du langage utilisé pour les scripts et à des fins interactives. L'utilisation du dialecte de script a été relativement mineure, mais l'usage s'intensifie. Le plan est basé sur la conviction que des extensions de C# pourraient être ajoutées au langage plutôt que d'avoir un dialecte de script séparé.

    Le C# est un langage de programmation orienté objet appartenant à la famille C, et similaire à Java. Il prend également en charge la collecte des déchets et la programmation orientée composants, en utilisant des paquets de fonctionnalités autonomes et autodescriptives. La liste de toutes les propositions pour C# 9 est disponible sur la page GitHub du langage.

    Source : C#

    Et vous ?

    Qu'en pensez-vous ?

    Voir aussi

    .NET Core 3.0 est disponible avec le support du développement d'applications Windows Desktop C# 8.0, ARM64, une prise en charge JSON intégrée rapide et de nombreuses autres améliorations

    C# 8.x va introduire la fonctionnalité Records pour faciliter la création des classes POCO, selon Developers Anonymous

    Microsoft livre un aperçu des nouveautés de C# 8.0 et envisage de commencer à livrer cette version dans les préversions de Visual Studio 2019
    Contribuez au club : Corrections, suggestions, critiques, ... : Contactez le service news et Rédigez des actualités

  2. #2
    Chroniqueur Actualités

    C# 9.0 est disponible et apporte de nombreuses fonctionnalités notamment les expressions with
    C# 9.0 est disponible et apporte de nombreuses fonctionnalités et améliorations comme les expressions with,
    ou encore les classes Record

    Propriétés Init-only

    Les initialiseurs d'objets sont assez impressionnants. Ils donnent au client d'un type un format très flexible et lisible pour créer un objet, et ils sont particulièrement parfaits pour la création d'objets imbriqués où toute une arborescence d'objets est créée en une seule fois. En voici une simple:

    Code C# :Sélectionner tout -Visualiser dans une fenêtre à part
    1
    2
    3
    4
    5
    new Person
    {
        FirstName = "Scott",
        LastName = "Hunter"
    }


    La seule grande limitation aujourd'hui est que les propriétés doivent être modifiables pour que les initialiseurs d'objet fonctionnent: ils fonctionnent en appelant d'abord le constructeur de l'objet (par défaut, sans paramètre dans ce cas), puis en les affectant aux setters de propriété.

    Les propriétés init-only corrigent cela! Elles introduisent un accesseur init qui est une variante de l'accesseur set qui ne peut être appelé que lors de l'initialisation de l'objet:

    Code C# :Sélectionner tout -Visualiser dans une fenêtre à part
    1
    2
    3
    4
    5
    public class Person
    {
        public string FirstName { get; init; }
        public string LastName { get; init; }
    }


    Avec cette déclaration, le code client ci-dessus est toujours légal, mais toute affectation ultérieure aux propriétés FirstName et LastName est une erreur.

    Accesseurs init et champs readonly

    Étant donné que les accesseurs init ne peuvent être appelés que lors de l'initialisation, ils sont autorisés à muter les champs readonly de la classe englobante, tout comme vous le pouvez dans un constructeur.

    Code C# :Sélectionner tout -Visualiser dans une fenêtre à part
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    public class Person
    {
        private readonly string firstName;
        private readonly string lastName;
     
        public string FirstName 
        { 
            get => firstName; 
            init => firstName = (value ?? throw new ArgumentNullException(nameof(FirstName)));
        }
        public string LastName 
        { 
            get => lastName; 
            init => lastName = (value ?? throw new ArgumentNullException(nameof(LastName)));
        }
    }



    Records

    Les propriétés init-only sont idéales si vous souhaitez rendre les propriétés individuelles immuables. Si vous voulez que l'objet entier soit immuable et se comporte comme une valeur, alors vous devriez envisager de le déclarer comme un record (enregistrement) :

    Code C# :Sélectionner tout -Visualiser dans une fenêtre à part
    1
    2
    3
    4
    5
    public data class Person
    {
        public string FirstName { get; init; }
        public string LastName { get; init; }
    }


    Le mot-clé data sur la déclaration de classe le marque comme un enregistrement. Cela lui confère plusieurs comportements similaires à des valeurs. De manière générale, les enregistrements sont censés être davantage considérés comme des « valeurs » (des données) et moins comme objets. Ils ne sont pas censés avoir un état encapsulé mutable. Au lieu de cela, vous représentez le changement au fil du temps en créant de nouveaux enregistrements représentant le nouvel état. Ils sont définis non pas par leur identité, mais par leur contenu.

    Expressions with

    Lorsque vous travaillez avec des données immuables, un modèle courant consiste à créer de nouvelles valeurs à partir de celles existantes pour représenter un nouvel état. Par exemple, si notre personne devait changer son nom de famille, nous le représenterions comme un nouvel objet qui est une copie de l'ancien, exception faite du nom de famille qui est différent. Cette technique est souvent appelée mutation non destructive. Au lieu de représenter la personne au fil du temps, l'enregistrement représente l'état de la personne à un moment donné.

    Pour aider avec ce style de programmation, les enregistrements permettent un nouveau type d'expression; l'expression with :

    Code C# :Sélectionner tout -Visualiser dans une fenêtre à part
    var otherPerson = person with { LastName = "Hanselman" };


    Les expressions with utilisent la syntaxe d'initialisation d'objet pour indiquer ce qui est différent dans le nouvel objet de l'ancien objet. Vous pouvez spécifier plusieurs propriétés.

    Un enregistrement définit implicitement un « constructeur de copie » protected - un constructeur qui prend un objet d'enregistrement existant et le copie champ par champ dans le nouveau:

    Code C# :Sélectionner tout -Visualiser dans une fenêtre à part
    protected Person(Person original) { /* copy all the fields */ } // generated


    L'expression with provoque l'appel du constructeur de copie, puis applique l'initialiseur d'objet en haut pour modifier les propriétés en conséquence.

    Si vous n'aimez pas le comportement par défaut du constructeur de copie généré, vous pouvez définir le vôtre à la place, et celui-ci sera repris par l'expression with.

    Source : Microsoft
    Contribuez au club : Corrections, suggestions, critiques, ... : Contactez le service news et Rédigez des actualités

  3. #3
    Membre expérimenté
    Ca me fait penser pas mal q certaines fonctionnalites des koltin, je dis n'importe quoi ou c'est bien le cas ?

  4. #4
    Membre confirmé
    Je connais pas Kotlin mais l'inspiration vient surement de F# qui à ce genre de features depuis longtemps. C'est la stratégie de Microsoft depuis longtemps. F# sert en quelque sorte de Labo et les features sont ensuite intégré dans C#.

  5. #5
    Expert éminent
    Bonjour,

    Y'a un truc que j'ai pas bien compris avec le "record" (mot clé data).

    Du coup, c'est quoi la différence entre un "struct" et un "class data", mise à part que le "class data" est readonly ?

    Pourquoi ne pas avoir créé un "struct readonly" à la place du coup ?

    Transformer une déclaration de type référence en type valeur, alors qu'une déclaration de type valeur existe déjà, je suis perplexe !
    On ne jouit bien que de ce qu’on partage.

  6. #6
    Nouveau Candidat au Club
    Domain-driven design (DDD)
    Moi qui structure mon code selon le "Domain-driven design (DDD)", c’est évidemment pour représenter un Value Object.

  7. #7
    Expert éminent
    Citation Envoyé par zero_divide Voir le message
    Moi qui structure mon code selon le "Domain-driven design (DDD)", c’est évidemment pour représenter un Value Object.
    Justement, j'ai un peu du mal à comprendre…

    A la base, une instance d'objet, c'est une référence.
    Donc un objet valeur c'est à la base plus ou moins antonyme.

    C#, depuis la version 1.0 a coupé la poire en deux, en proposant les structures, qui n'ont rien à voir avec les structures qu'on peut trouver dans d'autres langages : ce sont des objets à part entière (ils héritent d'ailleurs du type objetc) et supportent méthodes et constructeurs... La seule grosse différence avec une classe, c'est d'être justement un type valeur.

    Code csharp :Sélectionner tout -Visualiser dans une fenêtre à part
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    30
    31
    32
    33
    34
    35
    36
    37
    38
    39
    40
    41
    42
    using System;
     
    namespace StructOrObject
    {
        class Program
        {
            static void Main(string[] args)
            {
                MaStructure s1 = new MaStructure("Alfred", "E. Neuman");
                Console.WriteLine(s1.FullName);
                Console.WriteLine(s1.NbNotEmptyChars());
     
                MaStructure s2 = new MaStructure() { FullName = "Alfred E. Neuman" };
                Console.WriteLine(s2.FullName);
                Console.WriteLine(s2.NbNotEmptyChars());
     
                Console.WriteLine(s1.Equals(s2));
            }
        }
     
        struct MaStructure
        {
            public string FullName { get; set; }
     
            public MaStructure(string firstName, string lastName)
            {
                FullName = $"{firstName} {lastName}";
            }
     
            public int NbNotEmptyChars()
            {
                if (string.IsNullOrWhiteSpace(FullName))
                {
                    return 0;
                }
                else
                {
                    return FullName.Replace(" ", "").Length;
                }
            }
        }
    }


    Du coup, un "Value Object", existe déjà dans .NET et s'appelle struct...

    Le seul truc qui manque, c'est qu'on ne peut pas initialiser une proriété readonly directement avec un new MaStructure() { FullName = "Toto" } mais je ne vois pas ce qui empêchait de mettre "init" sur les structures aussi...
    Dans quel cas un "class data" est-il différent d'un "struct" donc toutes les propriétés sont readonly ?

    Bref, je pige vraiment pas.
    On ne jouit bien que de ce qu’on partage.

  8. #8
    Membre régulier
    Citation Envoyé par StringBuilder Voir le message

    Le seul truc qui manque, c'est qu'on ne peut pas initialiser une proriété readonly directement avec un new MaStructure() { FullName = "Toto" } mais je ne vois pas ce qui empêchait de mettre "init" sur les structures aussi...
    Dans quel cas un "class data" est-il différent d'un "struct" donc toutes les propriétés sont readonly ?

    Bref, je pige vraiment pas.
    Les classes et les structures ont aussi 2 différences majeures :
    • Les instances de classes sont stockées dans le tas, qui est une zone mémoire large gérée par le ramasse-miettes. Alors que les instances de structures sont stockées sur la pile et détruites automatiquement lorsqu'on sort de la portée.
    • Les classes sont dérivables, alors que les structures ne le sont pas.


    Les classes data restent des classes et continuent donc de profiter de ces 2 caractéristiques propres aux classes.
    De plus, en lisant l'article original de Microsoft, je crois comprendre que les classes data ont besoin de l'héritage dans certains scénarios (cf. chapitre Value-based equality and inheritance)

  9. #9
    Expert éminent
    En effet, bien vu pour l'héritage. J'avais pourtant l'exemple sous les yeux, je n'y avais pas pensé.

    Par contre, pour la partie gestion de la mémoire, j'ai envie de dire :
    - on s'en moque un peu (après tout, c'est la sauce interne du Framework, ça peut tout à fait changer d'une version à l'autre, voir même d'une implémentation à l'autre)
    - mais surtout, je ne vois pas l'intérêt de stocker des value objects dans une zone mémoire gérée par le GC. En effet, vu que ce sont des objets valeur, il ne peut y avoir qu'une seule référence (c'est con de manipuler une référence d'un type value... c'est un peu dommage d'utiliser un objet valeur pour n'en manipuler que la référence. Et très peu de chance/raison de recréer le même objet exactement, (et vérifier qu'il est identique à un détruit précédemment prendra de toute façon autant de temps que de le récupérer) sinon, pourquoi l'avoir détruit ?
    On ne jouit bien que de ce qu’on partage.

  10. #10
    Expert éminent sénior
    Citation Envoyé par StringBuilder Voir le message
    Y'a un truc que j'ai pas bien compris avec le "record" (mot clé data).

    Du coup, c'est quoi la différence entre un "struct" et un "class data", mise à part que le "class data" est readonly ?
    A priori, un class data reste une classe. Donc passage par référence. Le passage en tant que paramètre dans une méthode ou la copie dans une variable serait donc bien rapide que dans le cas d'une struct, notamment lorsqu'il y a beaucoup d'attributs.

    L'objectif c'est de donner un comportement de type "valeur" à une classe. L'égalité, par exemple, se base non plus sur les références, mais bien sur la valeur des attributs sous-jacents.

    Citation Envoyé par Programmator

    Les instances de classes sont stockées dans le tas, qui est une zone mémoire large gérée par le ramasse-miettes. Alors que les instances de structures sont stockées sur la pile et détruites automatiquement lorsqu'on sort de la portée.
    Partiellement inexacte. Les types valeur peuvent être allouées sur le tas et non sur la pile dans certaines condicitions. Par exemple, en cas de boxing :
    Code :Sélectionner tout -Visualiser dans une fenêtre à part
    1
    2
     
    object monEntier = 3; // alloué sur le tas !
    François DORIN
    Consultant informatique : conception, modélisation, développement (C#/.Net et SQL Server)
    Site internet | Profils Viadéo & LinkedIn
    ---------
    Page de cours : fdorin.developpez.com
    ---------
    N'oubliez pas de consulter la FAQ C# ainsi que les cours et tutoriels

  11. #11
    Membre expérimenté
    Dans le debat classe vs struct, il ya aussi les generiques :

    Code :Sélectionner tout -Visualiser dans une fenêtre à part
    public void Prout<T>(T t) where T : class


    ce qui engrange des doublon si l'on doit a la fois gerer des classes et des structures...
    Au final avec le temps je pousse mes collegues a ne pas utiliser les struct pour toutes ces raisons. Mais il reste ce soucis de mutabilite que l'on ne desire pas forcement.

    Je me demande si ces data class fonctionnent correctement lors d'un post mvc, par exemple

  12. #12
    Membre régulier
    Citation Envoyé par François DORIN Voir le message
    A priori, un class data reste une classe. Donc passage par référence. Le passage en tant que paramètre dans une méthode ou la copie dans une variable serait donc bien rapide que dans le cas d'une struct, notamment lorsqu'il y a beaucoup d'attributs.

    L'objectif c'est de donner un comportement de type "valeur" à une classe. L'égalité, par exemple, se base non plus sur les références, mais bien sur la valeur des attributs sous-jacents.

    Partiellement inexacte. Les types valeur peuvent être allouées sur le tas et non sur la pile dans certaines condicitions. Par exemple, en cas de boxing :
    Code :Sélectionner tout -Visualiser dans une fenêtre à part
    1
    2
     
    object monEntier = 3; // alloué sur le tas !
    Je partage tout à fait ce que tu dis François concernant le comportement de type valeur. C'est d'ailleurs déjà ce qui existe avec le type string, qui se comporte comme un type valeur bien que ce soit un type référence.

    Concernant l'allocation mémoire, même dans le boxing, la règle des types valeurs stockés sur la pile reste vraie. C'est un mécanisme que j'ai bien étudié pour faire mon cours C# et voici ce que j'ai compris :
    3 est un type valeur qui est donc stocké sur la pile.
    monEntier étant de type object, il doit obligatoirement référencer un emplacement du tas. S'il pointait sur un emplacement de la pile, cela créerait un défaut de sécurité potentiel. Par conséquent, le runtime réserve un bloc de mémoire sur le tas, y copie la valeur de l'entier 3, puis référence cette copie dans l’objet monEntier. Ceci est illustré par le schéma suivant :



    La règle types valeurs stockés sur la pile et types références stockés sur le tas est ainsi toujours respectée.

###raw>template_hook.ano_emploi###