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