Перегрузка операторов методами расширения C #

174

Я пытаюсь использовать методы расширения для добавления перегрузки оператора в StringBuilderкласс C # . В частности, учитывая StringBuilder sb, я хотел бы sb += "text"стать эквивалентным sb.Append("text").

Вот синтаксис для создания метода расширения для StringBuilder:

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

Он успешно добавляет blahметод расширения к StringBuilder.

К сожалению, перегрузка оператора, похоже, не работает:

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

Среди других вопросов, ключевое слово thisне допускается в этом контексте.

Возможно ли добавить перегрузки операторов с помощью методов расширения? Если да, то как правильно это сделать?

Джуд аллред
источник
4
Хотя поначалу это кажется крутой идеей, рассмотрим var otherSb = sb + "hi";
топор - сделано с SOverflow

Ответы:

150

В настоящее время это невозможно, поскольку методы расширения должны быть в статических классах, а статические классы не могут иметь перегрузки операторов.

Мадс Торгерсен, C # Language PM говорит:

... для релиза Orcas мы решили принять осторожный подход и добавить только обычные методы расширения, в отличие от свойств расширения, событий, операторов, статических методов и т. д. и т. д. Для LINQ нам были необходимы регулярные методы расширения, и они имели синтаксически минимальный дизайн, который не может быть легко воспроизведен для некоторых других типов членов.

Мы все больше осознаем, что могут быть полезны другие типы участников расширения, и поэтому мы вернемся к этому вопросу после Orcas. Хотя никаких гарантий!

Редактировать:

Я только что заметил, Мэдс написал больше в той же статье :

Мне жаль сообщать, что мы не будем делать это в следующем выпуске. Мы очень серьезно относились к участию в расширении наших планов и потратили немало усилий, пытаясь понять их правильно, но в итоге мы не смогли сделать это достаточно гладко и решили уступить место другим интересным функциям.

Это все еще на нашем радаре для будущих выпусков. Что поможет, если мы получим большое количество убедительных сценариев, которые могут помочь создать правильный дизайн.


Эта функция в настоящее время на столе (потенциально) для C # 8.0. Мэдс говорит немного больше о реализации этого здесь .

Джейкоб Кралл
источник
Эта страница была с тех пор удалена; эта проблема до сих пор не решена.
Крис Москини
17
Жаль. Я просто хотел добавить оператор для умножения TimeSpan на скалярное значение ... :(
Филипп Скакун
Я надеялся реализовать эту же концепцию для преобразования Stringв PowerShell ScriptBlock.
Тревор Салливан
Это означает, что я не могу перегрузить%, чтобы быть xor между логическими значениями? :( мёртв true.Xor(false)тогда
SparK
3
@SparK ^- оператор xor в C #
Джейкоб Кралл
57

Если вы контролируете места, где вы хотите использовать этот «оператор расширения» (что вы обычно делаете с методами расширения), вы можете сделать что-то вроде этого:

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

} 

StringBuilderWrapperКласс объявляет оператор неявного преобразования из StringBuilder и объявляет нужный +оператор. Таким образом, StringBuilderможно передать a ReceiveImportantMessage, который будет молча преобразован в a StringBuilderWrapper, где +оператор может быть использован.

Чтобы сделать этот факт более прозрачным для вызывающих, вы можете объявить, ReceiveImportantMessageчто он принимает, StringBuilderи просто использовать такой код:

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

Или, чтобы использовать его там, где вы уже используете StringBuilder, вы можете просто сделать это:

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

Я создал пост об использовании подобного подхода, чтобы сделать его IComparableболее понятным.

Jordão
источник
2
@Leon: я действительно хотел написать это, а не наследовать от него. Во всяком случае, я не мог наследовать от него, так как он запечатан.
Джордао,
5
@Leon: это сердце этой техники. Я могу сделать это , потому что есть неявный оператор преобразования объявлен в StringBuilderWrapperтом , что делает это возможным.
Джордао
1
@pylover: Вы правы, для этого необходимо создать новый тип, который обернет StringBuilderтип и предоставит из него неявный оператор преобразования. После этого его можно использовать со строковыми литералами, как показано в примере:sb += "Hello World!";
Jordão
2
Позвольте мне предложить добавить метод расширения к String: PushIndent(" ".X(4))(также может быть вызван Times). Или , может быть , с помощью этого конструктора: PushIndent(new String(' ', 4)).
Джордао
1
@ Jordão: Отличный ответ;)
Виниций
8

Похоже, что в настоящее время это невозможно - существует проблема с открытым отзывом, запрашивающая эту функцию в Microsoft Connect:

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

предполагая, что это может появиться в будущем выпуске, но не реализовано для текущей версии.

Дилан Битти
источник
Что именно вы имеете в виду, "в настоящее время не возможно?" Это должно быть возможно в CLR, потому что F # поддерживает расширение всего.
Мэтью Оленик
1
Я думаю, что он имеет в виду, что это невозможно в C #, а не в CLR. В любом случае, все методы расширения - это трюк с компилятором C #.
Тофи9
1
Ссылка сейчас мертва.
CBHacking
1

Хотя это невозможно сделать операторы, вы всегда можете просто создать методы Add (или Concat), Subtract и 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;    
        }
    }


}
Чак Ростанс
источник
1

Хах! Я искал "перегрузку оператора расширения" с точно таким же желанием, для sb + = (вещь).

Прочитав ответы здесь (и увидев, что ответ «нет»), для своих конкретных нужд я выбрал метод расширения, который комбинирует sb.AppendLine и sb.AppendFormat и выглядит более аккуратно, чем любой из них.

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

}

Так что,

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

Не общий ответ, но может быть интересен будущим ищущим, рассматривающим подобные вещи.

Дэвид ван Бринк
источник
4
Я думаю, что ваш метод расширения будет лучше читать, если вы назовете его AppendLineвместо Line.
DavidRR
0

Его можно оснастить оберткой и расширениями, но невозможно сделать это правильно. Вы заканчиваете мусором, который полностью побеждает цель. У меня где-то здесь есть пост, который делает это, но это бесполезно.

Кстати, все числовые преобразования создают мусор в строителе строк, который необходимо исправить. Я должен был написать обертку для того, что работает, и я использую ее. Это стоит изучить.

будет мотив
источник