C # a-t-il des propriétés d'extension?

769

C # a-t-il des propriétés d'extension?

Par exemple, puis-je ajouter une propriété d'extension à DateTimeFormatInfoappelée ShortDateLongTimeFormatqui retourneraitShortDatePattern + " " + LongTimePattern ?

Svish
la source
14
Je voulais ajouter une méthode d'extension appelée IsNull sur Nullable <T> qui reviendrait simplement! HasValue. .IsNull () est définitivement moins jolie que .IsNull
Ken
1
Je trouve cela utile pour l'opérateur trinaire?
PedroC88
2
Je voulais que cela imite les Java enumqui peuvent avoir des propriétés et des méthodes. Les C # enumne peuvent pas avoir de propriétés ou de méthodes, mais vous pouvez créer des méthodes d'extension dessus. Cette question m'a été utile et ne devrait pas être close.
Ian McLaird,
Bien que, comme beaucoup de gens l'ont dit, il n'y ait actuellement aucun plan en place pour ajouter cela à la langue, il n'y a aucune raison que cela ne puisse pas être fait. Le fait que F # ait non seulement des propriétés d'extension mais également des extensions statiques prouve que c'est au moins une bonne idée.
Richiban
2
Il devrait y en avoir un
Rootel

Réponses:

366

Pour le moment il n'est toujours pas supporté par le compilateur Roslyn ...

Jusqu'à présent, les propriétés d'extension n'étaient pas considérées comme suffisamment valables pour être incluses dans les versions précédentes de la norme C #. C # 7 et C # 8.0 ont vu cela comme un champion de la proposition, mais cela n'a pas encore été publié, surtout parce que même s'il existe déjà une implémentation, ils veulent le faire dès le départ.

Mais ça va ...

Il existe un élément de membre d'extension dans la liste de travail C # 7 , il peut donc être pris en charge dans un proche avenir. Le statut actuel de la propriété d'extension peut être trouvé sur Github sous l'élément connexe .

Cependant, il y a un sujet encore plus prometteur qui est le "tout étendre" avec un accent particulier sur les propriétés et les classes statiques ou même les champs.

De plus, vous pouvez utiliser une solution de contournement

Comme spécifié dans cet article , vous pouvez utiliser la TypeDescriptorcapacité pour attacher un attribut à une instance d'objet au moment de l'exécution. Cependant, il n'utilise pas la syntaxe des propriétés standard.
C'est un peu différent du sucre syntaxique, ajoutant une possibilité de définir une propriété étendue comme
string Data(this MyClass instance)un alias pour la méthode d'extension
string GetData(this MyClass instance)car il stocke des données dans la classe.

J'espère que C # 7 fournira une extension complète (propriétés et champs), mais sur ce point, seul le temps nous le dira.

Et n'hésitez pas à contribuer car le logiciel de demain proviendra de la communauté.

Mise à jour: août 2016

Comme l'équipe dotnet a publié les nouveautés de C # 7.0 et d'un commentaire de Mads Torgensen :

Propriétés d'extension: nous avons eu un (brillant!) Stagiaire pour les mettre en œuvre au cours de l'été à titre expérimental, avec d'autres types de membres d'extension. Nous restons intéressés par cela, mais c'est un grand changement et nous devons être convaincus que cela en vaut la peine.

Il semble que les propriétés d'extension et d'autres membres soient toujours de bons candidats à inclure dans une future version de Roslyn, mais peut-être pas celle de 7.0.

Mise à jour: mai 2017

Les membres de l'extension ont été fermés en tant que doublon du problème d' extension tout qui est également fermé. La discussion principale portait en fait sur l'extensibilité de type au sens large. La fonctionnalité est désormais suivie ici en tant que proposition et a été supprimée du jalon 7.0 .

Mise à jour: août 2017 - Fonctionnalité proposée par C # 8.0

Bien qu'il ne reste qu'une fonctionnalité proposée , nous avons maintenant une vue plus claire de ce que serait sa syntaxe. Gardez à l'esprit que ce sera également la nouvelle syntaxe des méthodes d'extension:

public interface IEmployee 
{
    public decimal Salary { get; set; }
}

public class Employee
{
    public decimal Salary { get; set; }
}

public extension MyPersonExtension extends Person : IEmployee
{
    private static readonly ConditionalWeakTable<Person, Employee> _employees = 
        new ConditionalWeakTable<Person, Employee>();


    public decimal Salary
    {
        get 
        {
            // `this` is the instance of Person
            return _employees.GetOrCreate(this).Salary; 
        }
        set 
        {
            Employee employee = null;
            if (!_employees.TryGetValue(this, out employee)
            {
                employee = _employees.GetOrCreate(this);
            }
            employee.Salary = value;
        }
    }
}

IEmployee person = new Person();
var salary = person.Salary;

Similaire aux classes partielles, mais compilé en tant que classe / type séparé dans un assembly différent. Notez que vous pourrez également ajouter des membres statiques et des opérateurs de cette façon. Comme mentionné dans le podcast Mads Torgensen , l'extension n'aura aucun état (elle ne peut donc pas ajouter de membres d'instance privés à la classe), ce qui signifie que vous ne pourrez pas ajouter de données d'instance privée liées à l'instance . La raison invoquée est que cela impliquerait de gérer en interne les dictionnaires et cela pourrait être difficile (gestion de la mémoire, etc ...). Pour cela, vous pouvez toujours utiliser le TypeDescriptor/ConditionalWeakTable technique décrite précédemment et avec l'extension de propriété, la cache sous une belle propriété.

La syntaxe est toujours sujette à changement, comme l'indique ce problème . Par exemple, extendspourrait être remplacé par forlequel certains pourraient se sentir plus naturels et moins liés à Java.

Mise à jour de décembre 2018 - Rôles, extensions et membres de l'interface statique

L'extension n'a pas atteint C # 8.0, en raison de certains inconvénients expliqués comme la fin de ce ticket GitHub . Il y a donc eu une exploration pour améliorer la conception. Ici , Mads Torgensen explique quels sont les rôles et extensions et comment ils diffèrent:

Les rôles permettent aux interfaces d'être implémentées sur des valeurs spécifiques d'un type donné. Les extensions permettent aux interfaces d'être implémentées sur toutes les valeurs d'un type donné, dans une région spécifique de code.

Il peut être vu à une division de la proposition précédente dans deux cas d'utilisation. La nouvelle syntaxe d'extension serait la suivante:

public extension ULongEnumerable of ulong
{
    public IEnumerator<byte> GetEnumerator()
    {
        for (int i = sizeof(ulong); i > 0; i--)
        {
            yield return unchecked((byte)(this >> (i-1)*8));
        }
    }
}

alors vous pourriez faire ceci:

foreach (byte b in 0x_3A_9E_F1_C5_DA_F7_30_16ul)
{
    WriteLine($"{e.Current:X}");
}

Et pour une interface statique :

public interface IMonoid<T> where T : IMonoid<T>
{
    static T operator +(T t1, T t2);
    static T Zero { get; }
}

Ajoutez une propriété d'extension sur intet traitez-la intcomme IMonoid<int>:

public extension IntMonoid of int : IMonoid<int>
{
    public static int Zero => 0;
}
Fab
la source
58
C'est l'une des réponses les plus utiles que j'ai jamais suivies sur StackExchange. Mettre constamment à jour le statut et tenir tout le monde informé qui y revient, fournissant des liens solides vers la discussion et l'histoire.
bdrelling
25
C'est formidable que vous gardiez cela à jour - merci
David Thielen
1
Malheureusement, à partir de ce commentaire, les rôles, les extensions et les membres de l'interface statique ne sont signalés que pour C # 11 :(
Ian Kemp
436

Non, ils n'existent pas en C # 3.0 et ne seront pas ajoutés en 4.0. Il figure sur la liste des fonctionnalités souhaitées pour C #, il peut donc être ajouté à une date ultérieure.

À ce stade, le mieux que vous puissiez faire est les méthodes d'extension de style GetXXX.

JaredPar
la source
3
De même avec les propriétés génériques: vous devez utiliser la syntaxe 'GetXXX <>'.
Jay Bazuzi
3
ok, c'est ce que je pensais. @Jay, ouais, je déteste ça aussi, hehe. Surtout l'impossibilité d'avoir un indexeur générique ... soupir
Svish
75
Lien vers la liste des fonctionnalités souhaitées?
Dan Esparza
2
Qu'en est-il des versions 6.0 et 7.0?
Falk
2
Des mises à jour à ce sujet à partir de 2020?
Chad
265

Non, ils n'existent pas.

Je sais que l'équipe C # les envisageait à un moment donné (ou du moins Eric Lippert) - avec les constructeurs et les opérateurs d'extension (cela peut prendre un certain temps pour se faire une idée, mais c'est cool ...) Cependant, je n'ai pas 'ai vu aucune preuve qu'ils feraient partie de C # 4.


EDIT: Ils ne sont pas apparus en C # 5, et en juillet 2014, il ne semble pas non plus que ce sera en C # 6.

Eric Lippert , développeur principal de l'équipe du compilateur C # de Microsoft jusqu'en novembre 2012, a blogué à ce sujet en octobre 2009:

Jon Skeet
la source
2
Oui, et ils pourraient toujours masquer le champ - la définition d'une seule propriété peut définir deux propriétés en dessous, ou vice versa. (Imaginez quelque chose avec une propriété Size normale et des propriétés d'extension Width / Height, ou vice versa.) Cependant, elles seraient plus utiles en lecture seule, je suppose.
Jon Skeet
23
Vous ne pouvez pas vous lier à des méthodes d'extension ... pouvoir ajouter vos propres propriétés pour la liaison de données peut être utile dans de nombreuses situations.
Nick
3
@leppie - La valeur des extensions de propriété profiterait le plus aux propriétés bool et string, je pense. Se débarrasser de la ()fin est beaucoup plus lisible. Je sais pour moi personnellement, au moins 90% des extensions que j'écris sont de ces 2 types.
Code Maverick
4
Pour donner un exemple de la raison pour laquelle cela serait utile, j'ai un modèle EFCF. Dans certaines classes, j'ai des propriétés en lecture seule que j'utilise pour renvoyer des informations formatées: FullName= FirstName + LastName, ShortName= FirstName + LastName[0]. Je voudrais ajouter plus de ces propriétés, mais je ne veux pas "salir" les classes réelles. Dans ce cas, une propriété d'extension, en lecture seule, est parfaite car je peux ajouter la fonctionnalité, garder la classe principale propre et toujours exposer les informations que je veux exposer dans l'interface utilisateur.
Gup3rSuR4c
4
@ JonSkeet: Vous avez raison, j'ai fini par faire ce que je voulais en créant ma propre classe, puis en encapsulant toutes les méthodes et propriétés de classe scellées pertinentes, puis en fournissant static implicit operator FileInfo(FileInfoEx fex)ce qui renvoie mon objet FileInfo contenu. Cela me permet effectivement de traiter le FileInfoEx comme s'il héritait de FileInfo, même si cette classe est scellée.
Steve L
27

Mise à jour (merci à @chaost d' avoir signalé cette mise à jour):

Mads Torgersen: "L'extension n'a pas tout fait en C # 8.0. Elle s'est" rattrapée ", si vous voulez, dans un débat très passionnant sur le futur futur du langage, et maintenant nous voulons nous assurer que nous ne le faisons pas l'ajouter d'une manière qui inhibe ces possibilités futures. Parfois, la conception d'un langage est un jeu très long! "

Source: section commentaires dans https://blogs.msdn.microsoft.com/dotnet/2018/11/12/building-c-8-0/


J'ai cessé de compter combien de fois au fil des ans j'ai ouvert cette question dans l'espoir d'avoir vu cela mis en œuvre.

Eh bien, enfin, nous pouvons tous nous réjouir! Microsoft va introduire cela dans leur prochaine version C # 8.

Alors au lieu de faire ça ...

public static class IntExtensions
{
   public static bool Even(this int value)
   {
        return value % 2 == 0;
   }
}

On va enfin pouvoir le faire comme ça ...

public extension IntExtension extends int
{
    public bool Even => this % 2 == 0;
}

Source: https://blog.ndepend.com/c-8-0-features-glimpse-future/

Korayem
la source
3
Cette semaine, les fonctionnalités C # 8.0 ont été annoncées et je n'ai malheureusement rien vu de tel.
Mateo Torres-Ruiz
1
@ MateoTorres-Ruiz Un commentaire de 'Mads Torgersen' (dev C #), répondant à quelqu'un qui en parlait (il y a 3 jours): "L'extension n'a pas tout fait en C # 8.0. Elle s'est" rattrapée ", si vous voulez , dans un débat très passionnant sur l'avenir de la langue, et maintenant nous voulons nous assurer de ne pas l'ajouter d'une manière qui inhibe ces possibilités futures. Parfois, la conception d'une langue est un jeu très long! " Ça fait mal .. (Lisez ceci sur le lien Korayems, dans la section commentaire)
Chaost
8

Comme @Psyonity l'a mentionné, vous pouvez utiliser le conditionalWeakTable pour ajouter des propriétés aux objets existants. Combiné avec le ExpandoObject dynamique, vous pouvez implémenter des propriétés d'extension dynamique en quelques lignes:

using System.Dynamic;
using System.Runtime.CompilerServices;

namespace ExtensionProperties
{
    /// <summary>
    /// Dynamically associates properies to a random object instance
    /// </summary>
    /// <example>
    /// var jan = new Person("Jan");
    ///
    /// jan.Age = 24; // regular property of the person object;
    /// jan.DynamicProperties().NumberOfDrinkingBuddies = 27; // not originally scoped to the person object;
    ///
    /// if (jan.Age &lt; jan.DynamicProperties().NumberOfDrinkingBuddies)
    /// Console.WriteLine("Jan drinks too much");
    /// </example>
    /// <remarks>
    /// If you get 'Microsoft.CSharp.RuntimeBinder.CSharpArgumentInfo.Create' you should reference Microsoft.CSharp
    /// </remarks>
    public static class ObjectExtensions
    {
        ///<summary>Stores extended data for objects</summary>
        private static ConditionalWeakTable<object, object> extendedData = new ConditionalWeakTable<object, object>();

        /// <summary>
        /// Gets a dynamic collection of properties associated with an object instance,
        /// with a lifetime scoped to the lifetime of the object
        /// </summary>
        /// <param name="obj">The object the properties are associated with</param>
        /// <returns>A dynamic collection of properties associated with an object instance.</returns>
        public static dynamic DynamicProperties(this object obj) => extendedData.GetValue(obj, _ => new ExpandoObject());
    }
}

Un exemple d'utilisation est dans les commentaires xml:

var jan = new Person("Jan");

jan.Age = 24; // regular property of the person object;
jan.DynamicProperties().NumberOfDrinkingBuddies = 27; // not originally scoped to the person object;

if (jan.Age < jan.DynamicProperties().NumberOfDrinkingBuddies)
{
    Console.WriteLine("Jan drinks too much");
}

jan = null; // NumberOfDrinkingBuddies will also be erased during garbage collection
realbart
la source
La meilleure réponse
N73k
1

Parce que j'avais récemment besoin de cela, j'ai regardé la source de la réponse dans:

c # étendre la classe en ajoutant des propriétés

et créé une version plus dynamique:

public static class ObjectExtenders
{
    static readonly ConditionalWeakTable<object, List<stringObject>> Flags = new ConditionalWeakTable<object, List<stringObject>>();

    public static string GetFlags(this object objectItem, string key)
    {
        return Flags.GetOrCreateValue(objectItem).Single(x => x.Key == key).Value;
    }

    public static void SetFlags(this object objectItem, string key, string value)
    {
        if (Flags.GetOrCreateValue(objectItem).Any(x => x.Key == key))
        {
            Flags.GetOrCreateValue(objectItem).Single(x => x.Key == key).Value = value;
        }
        else
        {
            Flags.GetOrCreateValue(objectItem).Add(new stringObject()
            {
                Key = key,
                Value = value
            });
        }
    }

    class stringObject
    {
        public string Key;
        public string Value;
    }
}

Il peut probablement être beaucoup amélioré (dénomination, dynamique au lieu de chaîne), je l'utilise actuellement dans CF 3.5 avec un ConditionalWeakTable hacky ( https://gist.github.com/Jan-WillemdeBruyn/db79dd6fdef7b9845e217958db98c4d4 )

Psyonity
la source
Désolé, mais bien que cela semble très détaillé, cela n'a rien à voir avec les propriétés d'extension, mais ne montre que les méthodes d'extension.
Viking