Przeciążanie operatorów za pomocą metod rozszerzenia języka C #

174

Próbuję użyć metod rozszerzających, aby dodać przeciążenie operatora do StringBuilderklasy C # . Konkretnie biorąc StringBuilder sb, chciałbym sb += "text"stać się odpowiednikiem sb.Append("text").

Oto składnia tworzenia metody rozszerzenia dla StringBuilder:

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

Pomyślnie dodaje blahmetodę rozszerzenia do StringBuilder.

Niestety, wydaje się, że przeciążenie operatora nie działa:

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

Między innymi słowo kluczowe thisnie jest dozwolone w tym kontekście.

Czy jest możliwe dodawanie przeciążeń operatorów za pomocą metod rozszerzających? Jeśli tak, jaki jest właściwy sposób, aby to zrobić?

Jude Allred
źródło
4
Chociaż na pierwszy rzut oka wydaje się to fajnym pomysłem, rozważ var otherSb = sb + "hi";
siekierka - zrobiona z SOverflow

Odpowiedzi:

150

Obecnie nie jest to możliwe, ponieważ metody rozszerzające muszą znajdować się w klasach statycznych, a klasy statyczne nie mogą mieć przeciążeń operatorów.

Mads Torgersen, premier języka C # mówi:

... w wydaniu Orcas zdecydowaliśmy się przyjąć ostrożne podejście i dodać tylko zwykłe metody rozszerzające, w przeciwieństwie do właściwości rozszerzeń, zdarzeń, operatorów, metod statycznych itp. itp. Potrzebowaliśmy zwykłych metod rozszerzających w LINQ, a one miały minimalny składniowo projekt, którego nie można było łatwo naśladować w przypadku niektórych innych rodzajów elementów członkowskich.

Coraz bardziej zdajemy sobie sprawę, że inne rodzaje członków rozszerzenia mogą być przydatne, więc wrócimy do tego problemu po Orkach. Nie ma jednak gwarancji!

Edytować:

Właśnie zauważyłem, Mads napisał więcej w tym samym artykule :

Z przykrością informuję, że nie będziemy tego robić w następnym wydaniu. W naszych planach potraktowaliśmy członków rozszerzenia bardzo poważnie i poświęciliśmy wiele wysiłku, próbując je naprawić, ale ostatecznie nie mogliśmy sprawić, by było to wystarczająco gładkie i zdecydowaliśmy się ustąpić innym interesującym funkcjom.

To jest nadal na naszym radarze w przyszłych wydaniach. Pomoże to, jeśli otrzymamy dużą liczbę przekonujących scenariuszy, które mogą pomóc w opracowaniu właściwego projektu.


Ta funkcja jest obecnie w tabeli (potencjalnie) dla języka C # 8.0. Mads mówi nieco więcej o wdrażaniu go tutaj .

Jacob Krall
źródło
Ta strona została usunięta; ten problem nadal nie został rozwiązany.
Chris Moschini
17
Szkoda. Chciałem tylko dodać operator, który pomnoży TimeSpan przez wartość skalarną ... :(
Filip Skakun
Miałem nadzieję, że zaimplementuję tę samą koncepcję, aby rzutować Stringna PowerShell ScriptBlock.
Trevor Sullivan,
Oznacza to, że nie mogę przeciążać%, aby być xor między wartościami logicznymi? :( martwy na true.Xor(false)potem
Spark
3
@SparK ^jest operatorem xor w C #
Jacob Krall
57

Jeśli kontrolujesz miejsca, w których chcesz używać tego „operatora rozszerzenia” (co i tak zwykle robisz z metodami rozszerzającymi), możesz zrobić coś takiego:

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;
  }

} 

StringBuilderWrapperKlasy deklaruje ukryte operatora konwersji od A StringBuilder i deklaruje żądany +operatora. W ten sposób StringBuildermożna przekazać a ReceiveImportantMessage, które zostanie po cichu przekonwertowane na a StringBuilderWrapper, gdzie +można użyć operatora.

Aby uczynić ten fakt bardziej przejrzystym dla wywołujących, możesz zadeklarować ReceiveImportantMessagejako przyjmowanie a StringBuilderi po prostu użyć takiego kodu:

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

Lub, aby użyć go w tekście tam, gdzie już używasz StringBuilder, możesz po prostu zrobić to:

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

Stworzyłem post o podobnym podejściu, aby uczynić go IComparablebardziej zrozumiałym.

Jordão
źródło
2
@Leon: Naprawdę chciałem to skomponować, a nie odziedziczyć. W każdym razie nie mogłem po nim odziedziczyć, ponieważ jest zapieczętowany.
Jordão
5
@Leon: To jest serce tej techniki. Mogę to zrobić, ponieważ w programie jest zadeklarowany niejawny operator konwersji,StringBuilderWrapper który to umożliwia.
Jordão,
1
@pylover: Masz rację, wymaga to stworzenia nowego typu, który otoczy StringBuildertyp i zapewni z niego niejawny operator konwersji. Następnie można go używać z literałami łańcuchowymi, jak pokazano na przykładzie:sb += "Hello World!";
Jordão,
2
Mogę więc zasugerować dodanie metody rozszerzającej do String: PushIndent(" ".X(4))(można by też wywołać Times). A może za pomocą tego konstruktora: PushIndent(new String(' ', 4)).
Jordão,
1
@ Jordão: Świetna odpowiedź;)
Vinicius
8

Wygląda na to, że obecnie nie jest to możliwe - istnieje otwarty problem z opinią, który wymaga tej funkcji w Microsoft Connect:

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

sugeruje, że może pojawić się w przyszłej wersji, ale nie jest zaimplementowana w bieżącej wersji.

Dylan Beattie
źródło
Co dokładnie masz na myśli, mówiąc „obecnie nie jest możliwe?” Musi to być możliwe w środowisku CLR, ponieważ F # obsługuje rozszerzenie wszystko.
Matthew Olenik
1
Myślę, że ma na myśli, że nie jest to możliwe w C #, a nie w CLR. Cała rzecz dotycząca metod rozszerzających i tak jest sztuczką kompilatora C #.
tofi9
1
Link nie żyje.
CBHacking
1

Chociaż nie jest możliwe wykonanie operatorów, zawsze możesz po prostu utworzyć metody Add (lub Concat), Subtract i 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
źródło
1

Ha! Szukałem "przeciążenia operatora rozszerzenia" z dokładnie tym samym pragnieniem, dla sb + = (rzecz).

Po przeczytaniu odpowiedzi tutaj (i stwierdzeniu, że odpowiedź brzmi „nie”), dla moich szczególnych potrzeb wybrałem metodę rozszerzającą, która łączy w sobie sb.AppendLine i sb.AppendFormat i wygląda lepiej niż obie.

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);
    }

}

A więc,

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

Nie jest to ogólna odpowiedź, ale może zainteresować przyszłych poszukujących tego rodzaju rzeczy.

david van brink
źródło
4
Myślę, że twoja metoda rozszerzenia byłaby lepsza, gdybyś nazwał ją AppendLinezamiast Line.
DavidRR
0

Można go uzbroić w owijkę i przedłużki, ale nie da się tego zrobić prawidłowo. Kończysz ze śmieciami, które całkowicie mijają cel. Mam gdzieś tutaj post, który to robi, ale jest bezwartościowy.

Btw Wszystkie konwersje numeryczne tworzą śmieci w konstruktorze ciągów, które należy naprawić. Musiałem napisać opakowanie do tego, co działa i używam go. Warto to przeczytać.

będą motywować
źródło