使用 C# 扩展方法的运算符重载

本文关键字:运算符 重载 方法 扩展 使用 | 更新日期: 2023-09-27 17:47:25

我正在尝试使用扩展方法将运算符重载添加到 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

是否可以通过扩展方法添加运算符重载? 如果是这样,正确的方法是什么?

使用 C# 扩展方法的运算符重载

这目前是不可能的,因为扩展方法必须位于静态类中,并且静态类不能具有运算符重载。但是,对于 C# 的未来版本,正在讨论该功能。Mads 在 2017 年的这段视频中谈到了更多关于实施它的信息。

关于为什么它目前没有实现,C#语言PM的Mads Torgersen说:

。对于逆戟鲸的发布,我们决定采取谨慎的方法并添加只有常规的扩展方法,如与扩展属性相反,事件、运算符、静态方法等等。常规扩展方法是我们需要什么 LINQ,他们有语法上最小的设计对于某些人来说不容易模仿其他成员种类。

我们越来越意识到其他类型的扩展成员可能有用,所以我们会回来在逆戟鲸之后解决这个问题。不不过,保证!

在同一篇文章的下面进一步:

我很抱歉地报告我们不会在下一个版本中执行此操作。我们确实非常了解扩展成员认真地在我们的计划中,并花费了很多努力试图得到他们对,但最终我们无法得到它足够顺利,并决定给其他有趣功能的方式。

这仍然在我们的未来雷达上释放。如果我们得到大量引人入胜的场景这有助于推动正确的设计。

如果你控制要使用此"扩展运算符"的位置(通常无论如何,你都会使用扩展方法(,你可以做这样的事情:

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

建议它可能出现在未来的版本中,但未针对当前版本实现。

虽然不可能

做运算符,但你总是可以只创建加法(或康卡特(、减法和比较方法。

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

}

哈!我正在以完全相同的愿望查找"扩展运算符重载",sb += (thing).

在阅读了这里的答案(并看到答案是否定的(后,出于我的特定需求,我采用了一种结合了sb.AppendLinesb.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);

这不是一个笼统的答案,但可能会对未来寻求这种事情的人感兴趣。

可以用包装器和扩展来操纵它,但不可能正确地做到这一点。你以垃圾结束,这完全违背了目的。我在这里的某个地方有一篇文章可以做到这一点,但它毫无价值。

顺便说一句,所有数值转换都会在字符串生成器中创建需要修复的垃圾。我必须为确实有效的包装器编写一个包装器,然后我使用它。这是值得细读的。

前面的答案似乎仍然是正确的,这在 C# 中没有实现。但是,您可以使用更多代码非常接近。

选项 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 而不是StringBuilder制作包装器,这可能更适合,但在这种情况下,您将包装可能许多string对象,而不是 1 个StringBuilder对象。

<小时 />

正如另一个答案所指出的那样,这种策略似乎违背了拥有运算符的很多目的,因为根据用例的不同,您使用它的方式会发生变化。所以感觉有点尴尬。

在这种情况下,我同意,仅仅为了取代像StringBuilder Append方法这样的方法,这开销太大了。但是,如果您将更复杂的东西放在运算符后面(例如,需要 for 循环的东西(,您可能会发现这对您的项目来说是一个方便的解决方案。