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

Я пытаюсь использовать методы расширения для добавления перегрузки оператора в C# StringBuilder учебный класс. В частности, учитывая StringBuildersb, Мне бы хотелось 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 не допускается в этом контексте.

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

7 ответов

Решение

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

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

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

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

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

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

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

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


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

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

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 можно передать ReceiveImportantMessage, который будет молча преобразован в 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 более понятно.

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

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

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

Хах! Я искал "перегрузку оператора расширения" с точно таким же желанием, для 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);

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

Хотя это невозможно сделать операторы, вы всегда можете просто создать методы 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, класс-оболочка вокруг StringBuilder

Что-то вроде этого:

      public static class ExtStringBuilder
{
    public class WStringBuilder
    {
        private StringBuilder _sb { get; }
        public WStringBuilder(StringBuilder sb) => _sb = sb;
        public static implicit operator StringBuilder(WStringBuilder sbw) => sbw._sb;
        public static implicit operator WStringBuilder(StringBuilder sb) => new WStringBuilder(sb);
        public static WStringBuilder operator +(WStringBuilder sbw, string s) => sbw._sb.Append(s);
    }
    public static WStringBuilder wrap(this StringBuilder sb) => sb;
}

использование так:

      StringBuilder sb = new StringBuilder();
// requires a method call which is inconvenient
sb = sb.wrap() + "string1" + "string2";

Или:

      WStringBuilder sbw = new StringBuilder();
sbw = sbw + "string1" + "string2";
// you can pass sbw as a StringBuilder parameter
methodAcceptsStringBuilder(sbw);
// but you cannot call methods on it
// this fails: sbw.Append("string3");

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


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

В этом случае, я согласен, это слишком много накладных расходов, чтобы заменить такой метод, какStringBuilderхAppendметод. Однако, если вы ставите за оператором что-то более сложное (например, что-то, требующее цикла for), вы можете обнаружить, что это удобное решение для вашего проекта.

Другие вопросы по тегам