Surcharge des opérateurs avec les méthodes d'extension C #

174

J'essaie d'utiliser des méthodes d'extension pour ajouter une surcharge d'opérateur à la StringBuilderclasse C # . Plus précisément, étant donné StringBuilder sb, j'aimerais sb += "text"devenir équivalent à sb.Append("text").

Voici la syntaxe pour créer une méthode d'extension pour StringBuilder:

public static class sbExtensions
{
    public static StringBuilder blah(this StringBuilder sb)
    {
        return sb;
    }
} 

Il ajoute avec succès la blahméthode d'extension au fichier StringBuilder.

Malheureusement, la surcharge des opérateurs ne semble pas fonctionner:

public static class sbExtensions
{
    public static StringBuilder operator +(this StringBuilder sb, string s)
    {
        return sb.Append(s);
    }
} 

Entre autres problèmes, le mot this- clé n'est pas autorisé dans ce contexte.

L'ajout de surcharges d'opérateurs via des méthodes d'extension est-il possible? Si oui, quelle est la bonne façon de procéder?

Jude Allred
la source
4
Bien que cela semble au premier abord une bonne idée, considérez var otherSb = sb + "hi";
hachette - fait avec SOverflow

Réponses:

150

Ce n'est actuellement pas possible, car les méthodes d'extension doivent être dans des classes statiques et les classes statiques ne peuvent pas avoir de surcharges d'opérateurs.

Mads Torgersen, PM du langage C # dit:

... pour la version Orcas, nous avons décidé d'adopter une approche prudente et d'ajouter uniquement des méthodes d'extension régulières, par opposition aux propriétés d'extention, événements, opérateurs, méthodes statiques, etc. Les méthodes d'extension régulières étaient ce dont nous avions besoin pour LINQ, et elles une conception syntaxiquement minimale qui ne pourrait pas être facilement imitée pour certains des autres types de membres.

Nous sommes de plus en plus conscients que d'autres types de membres d'extension pourraient être utiles, et nous reviendrons donc sur ce problème après Orcas. Aucune garantie, cependant!

Éditer:

Je viens de remarquer, Mads a écrit plus dans le même article :

Je suis désolé de signaler que nous ne le ferons pas dans la prochaine version. Nous avons pris les membres d'extension très au sérieux dans nos plans et avons dépensé beaucoup d'efforts pour essayer de les faire correctement, mais au final, nous n'avons pas pu le faire assez facilement et avons décidé de céder la place à d'autres fonctionnalités intéressantes.

Ceci est toujours sur notre radar pour les versions futures. Ce qui aidera, c'est si nous obtenons une bonne quantité de scénarios convaincants qui peuvent aider à conduire la bonne conception.


Cette fonctionnalité est actuellement sur la table (potentiellement) pour C # 8.0. Mads parle un peu plus de sa mise en œuvre ici .

Jacob Krall
la source
Cette page a depuis été supprimée; ce problème n'est toujours pas résolu.
Chris Moschini
17
Dommage. Je voulais juste ajouter un opérateur pour multiplier un TimeSpan par une valeur scalaire ... :(
Filip Skakun
J'espérais implémenter ce même concept pour convertir un Stringen PowerShell ScriptBlock.
Trevor Sullivan
Cela signifie que je ne peux pas surcharger% pour être x ou entre des booléens? : (mort à ce true.Xor(false)moment
SparK
3
@SparK ^est l'opérateur xor en C #
Jacob Krall
57

Si vous contrôlez les endroits où vous souhaitez utiliser cet "opérateur d'extension" (ce que vous faites normalement avec les méthodes d'extension de toute façon), vous pouvez faire quelque chose comme ceci:

class Program {

  static void Main(string[] args) {
    StringBuilder sb = new StringBuilder();
    ReceiveImportantMessage(sb);
    Console.WriteLine(sb.ToString());
  }

  // the important thing is to use StringBuilderWrapper!
  private static void ReceiveImportantMessage(StringBuilderWrapper sb) {
    sb += "Hello World!";
  }

}

public class StringBuilderWrapper {

  public StringBuilderWrapper(StringBuilder sb) { StringBuilder = sb; }
  public StringBuilder StringBuilder { get; private set; }

  public static implicit operator StringBuilderWrapper(StringBuilder sb) {
    return new StringBuilderWrapper(sb);
  }

  public static StringBuilderWrapper operator +(StringBuilderWrapper sbw, string s) { 
      sbw.StringBuilder.Append(s);
      return sbw;
  }

} 

La StringBuilderWrapperclasse déclare un opérateur de conversion implicite à partir de a StringBuilder et déclare l' +opérateur souhaité . De cette façon, a StringBuilderpeut être passé à ReceiveImportantMessage, qui sera silencieusement converti en a StringBuilderWrapper, où le+ opérateur peut être utilisé.

Pour rendre ce fait plus transparent pour les appelants, vous pouvez déclarer ReceiveImportantMessagecomme prenant un StringBuilderet utiliser simplement un code comme celui-ci:

  private static void ReceiveImportantMessage(StringBuilder sb) {
    StringBuilderWrapper sbw = sb;
    sbw += "Hello World!";
  }

Ou, pour l'utiliser en ligne là où vous utilisez déjà a StringBuilder, vous pouvez simplement faire ceci:

 StringBuilder sb = new StringBuilder();
 StringBuilderWrapper sbw = sb;
 sbw += "Hello World!";
 Console.WriteLine(sb.ToString());

J'ai créé un article sur l'utilisation d'une approche similaire pour rendre IComparableplus compréhensible.

Jordão
la source
2
@Leon: Je voulais vraiment le composer, pas en hériter. De toute façon, je ne pourrais pas en hériter car il est scellé.
Jordão
5
@Leon: C'est le cœur de cette technique. Je peux le faire car il y a un opérateur de conversion implicite déclaré dans StringBuilderWrapperqui le rend possible.
Jordão
1
@pylover: Vous avez raison, cela nécessite la création d'un nouveau type, qui encapsulera le StringBuildertype et fournira un opérateur de conversion implicite à partir de celui-ci. Après cela, il peut être utilisé avec des chaînes littérales, comme illustré dans l'exemple:sb += "Hello World!";
Jordão
2
Puis-je suggérer alors, d'ajouter une méthode d'extension à String: PushIndent(" ".X(4))(pourrait également être appelée Times). Ou peut - être en utilisant ce constructeur: PushIndent(new String(' ', 4)).
Jordão
1
@ Jordão: Grande réponse;)
Vinicius
8

Il semble que ce ne soit pas possible actuellement - il y a un problème de rétroaction ouvert demandant cette fonctionnalité même sur Microsoft Connect:

http://connect.microsoft.com/VisualStudio/feedback/ViewFeedback.aspx?FeedbackID=168224

suggérant qu'il pourrait apparaître dans une version future mais n'est pas implémenté pour la version actuelle.

Dylan Beattie
la source
Que voulez-vous dire exactement, "n'est pas possible actuellement?" Cela doit être possible dans le CLR car F # prend en charge tout l'extension.
Matthew Olenik
1
Je pense qu'il veut dire que ce n'est pas possible en C #, pas en CLR. La chose entière des méthodes d'extension est de toute façon une astuce du compilateur C #.
tofi9
1
Link est mort maintenant.
CBHacking
1

Bien qu'il ne soit pas possible de faire les opérateurs, vous pouvez toujours simplement créer des méthodes Add (ou Concat), Subtract et Compare ....

using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;    

namespace Whatever.Test
{
    public static class Extensions
    {
        public static int Compare(this MyObject t1, MyObject t2)
        {
            if(t1.SomeValueField < t2.SomeValueField )
                return -1;
            else if (t1.SomeValueField > t2.SomeValueField )
            {
                return 1;
            }
            else
            {
                return 0;
            }
        }

        public static MyObject Add(this MyObject t1, MyObject t2)
        {
            var newObject = new MyObject();
            //do something  
            return newObject;

        }

        public static MyObject Subtract(this MyObject t1, MyObject t2)
        {
            var newObject= new MyObject();
            //do something
            return newObject;    
        }
    }


}
Chuck Rostance
la source
1

Hah! Je recherchais "surcharge d'opérateur d'extension" avec exactement le même désir, pour sb + = (chose).

Après avoir lu les réponses ici (et vu que la réponse est «non»), pour mes besoins particuliers, je suis allé avec une méthode d'extension qui combine sb.AppendLine et sb.AppendFormat, et semble plus ordonnée que l'un ou l'autre.

public static class SomeExtensions
{
    public static void Line(this StringBuilder sb, string format, params object[] args)
    {
        string s = String.Format(format + "\n", args);
        sb.Append(s);
    }

}

Et donc,

sb.Line("the first thing is {0}",first);
sb.Line("the second thing is {0}", second);

Ce n'est pas une réponse générale, mais peut intéresser les futurs chercheurs qui s'intéressent à ce genre de choses.

David van Brink
la source
4
Je pense que votre méthode d'extension se lirait mieux si vous la nommiez AppendLineau lieu de Line.
DavidRR
0

Il est possible de le configurer avec un wrapper et des extensions mais impossible de le faire correctement. Vous terminez avec des ordures qui vont totalement à l'encontre de l'objectif. J'ai un message quelque part ici qui le fait, mais cela ne vaut rien.

Btw Toutes les conversions numériques créent des déchets dans le générateur de chaînes qui doivent être corrigés. J'ai dû écrire un wrapper pour ce qui fonctionne et je l'utilise. Cela vaut la peine d'être lu.

se motivera
la source