繁体   English   中英

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

[英]Operator Overloading with C# Extension Methods

我正在尝试使用扩展方法将运算符重载添加到 C# StringBuilder class。具体来说,给定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

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

这目前是不可能的,因为扩展方法必须在静态类中,而静态类不能有运算符重载。 但是正在为 C# 的某些未来版本讨论功能 Mads 在 2017 年的这段视频中更多地谈到了实施它。

C# 语言 PM 的 Mads Torgersen 说:

...对于 Orcas 版本,我们决定采取谨慎的方法,只添加常规扩展方法,而不是扩展属性、事件、运算符、静态方法等。常规扩展方法是我们 LINQ 所需要的,并且它们具有一种语法上最小的设计,对于其他一些成员类型无法轻易模仿。

我们越来越意识到其他类型的扩展成员可能有用,因此我们将在 Orcas 之后回到这个问题。 但是没有保证!

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

我很遗憾地报告,我们将不会在下一个版本中这样做。 我们确实在我们的计划中非常重视扩展成员,并花了很多精力试图让他们正确,但最终我们无法让它足够顺利,并决定让位于其他有趣的功能。

这仍然在我们未来版本的雷达上。 如果我们获得大量引人注目的场景来帮助推动正确的设计,那么将会有所帮助。

如果您控制要使用此“扩展运算符”的位置(无论如何您通常都会使用扩展方法),您可以执行以下操作:

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 += (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);

不是一般性的答案,但可能对未来寻找此类事物的寻求者感兴趣。

尽管不可能使用运算符,但您始终可以创建 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;    
        }
    }


}

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

顺便说一句,所有数字转换都会在需要修复的字符串生成器中产生垃圾。 我不得不为那个工作的东西写一个包装器,我使用它。 那值得细读。

以前的答案似乎仍然是正确的,这在 C# 中没有实现。但是,您可以通过多一点代码来获得非常接近的结果。

选项1,StringBuilder周围的包装器class

像这样:

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 object。


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

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

暂无
暂无

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM