C#运算符是否为“ + =”重载?


114

我正在尝试为进行运算符重载+=,但我不能。我只能使操作符重载+

怎么会?

编辑

这不起作用的原因是我有一个Vector类(带有X和Y字段)。考虑以下示例。

vector1 += vector2;

如果我的运算符重载设置为:

public static Vector operator +(Vector left, Vector right)
{
    return new Vector(right.x + left.x, right.y + left.y);
}

那么结果将不会添加到vector1中,而是通过引用,vector1也将成为全新的Vector。


2
似乎已经对此进行了长时间的讨论:maurits.wordpress.com/2006/11/27/…–
Chris S

39
您能解释一下为什么要这样做吗?重载“ +”时,您可以免费获得重载的“ + =”运算符。有一些情况是,你不要想“+ =”超载但确实希望“+”超载?
埃里克·利珀特

3
来自C ++,这感觉很不对,但是在C#中,这确实很有意义。
乔恩·普迪


12
@Mathias:更新:向量的行为应像不变的数学对象。当您将2加到3时,您不会将对象3突变为对象5。您将创建一个全新的对象5。使它们变得易变,违背了这一目标。我将使您的向量类型成为不可变的值类型。
埃里克·利珀特

Answers:


147

来自MSDN的可重载运算符

赋值运算符不能重载,但是+=,例如,可以使用进行重载,赋值运算符可以+重载。

更重要的是,赋值运算符都无法重载。我认为这是因为垃圾收集和内存管理会产生影响,这是CLR强类型化世界中的潜在安全漏洞。

尽管如此,让我们看看运算符到底是什么。根据著名的杰弗里·里希特(Jeffrey Richter)的书,每种编程语言都有自己的运算符列表,这些运算符列表是通过特殊的方法调用进行编译的,而CLR本身对运算符一无所知。因此,让我们看看到底在+and +=运算符后面是什么。

看下面这个简单的代码:

Decimal d = 10M;
d = d + 10M;
Console.WriteLine(d);

让我们查看此指令的IL代码:

  IL_0000:  nop
  IL_0001:  ldc.i4.s   10
  IL_0003:  newobj     instance void [mscorlib]System.Decimal::.ctor(int32)
  IL_0008:  stloc.0
  IL_0009:  ldloc.0
  IL_000a:  ldc.i4.s   10
  IL_000c:  newobj     instance void [mscorlib]System.Decimal::.ctor(int32)
  IL_0011:  call       valuetype [mscorlib]System.Decimal [mscorlib]System.Decimal::op_Addition(valuetype [mscorlib]System.Decimal,
                                                                                                valuetype [mscorlib]System.Decimal)
  IL_0016:  stloc.0

现在让我们看下面的代码:

Decimal d1 = 10M;
d1 += 10M;
Console.WriteLine(d1);

和IL代码为此:

  IL_0000:  nop
  IL_0001:  ldc.i4.s   10
  IL_0003:  newobj     instance void [mscorlib]System.Decimal::.ctor(int32)
  IL_0008:  stloc.0
  IL_0009:  ldloc.0
  IL_000a:  ldc.i4.s   10
  IL_000c:  newobj     instance void [mscorlib]System.Decimal::.ctor(int32)
  IL_0011:  call       valuetype [mscorlib]System.Decimal [mscorlib]System.Decimal::op_Addition(valuetype [mscorlib]System.Decimal,
                                                                                                valuetype [mscorlib]System.Decimal)
  IL_0016:  stloc.0

他们是平等的!因此,+=运算符只是C#中程序的语法糖,您可以简单地重载+运算符。

例如:

class Foo
{
    private int c1;

    public Foo(int c11)
    {
        c1 = c11;
    }

    public static Foo operator +(Foo c1, Foo x)
    {
        return new Foo(c1.c1 + x.c1);
    }
}

static void Main(string[] args)
{
    Foo d1 =  new Foo (10);
    Foo d2 = new Foo(11);
    d2 += d1;
}

该代码将被编译并成功运行为:

  IL_0000:  nop
  IL_0001:  ldc.i4.s   10
  IL_0003:  newobj     instance void ConsoleApplication2.Program/Foo::.ctor(int32)
  IL_0008:  stloc.0
  IL_0009:  ldc.i4.s   11
  IL_000b:  newobj     instance void ConsoleApplication2.Program/Foo::.ctor(int32)
  IL_0010:  stloc.1
  IL_0011:  ldloc.1
  IL_0012:  ldloc.0
  IL_0013:  call       class ConsoleApplication2.Program/Foo ConsoleApplication2.Program/Foo::op_Addition(class ConsoleApplication2.Program/Foo,
                                                                                                          class ConsoleApplication2.Program/Foo)
  IL_0018:  stloc.1

更新:

根据您的更新-正如@EricLippert所说,您确实应该将向量作为不可变的对象。两个向量相加的结果是一个向量,而不是第一个具有不同大小的向量。

如果由于某种原因需要更改第一个向量,则可以使用此重载(但对我而言,这是非常奇怪的行为):

public static Vector operator +(Vector left, Vector right)
{
    left.x += right.x;
    left.y += right.y;
    return left;
}

2
指出情况并非要回答原因。
Jouke van der Maas 2011年

1
@Jouke van der Maas你怎么让我回答为什么不可能呢?设计上不可能,我还能说什么?
VMAtm 2011年

2
他们为什么这样设计?这就是问题的所在。查看其他一些答案。
Jouke van der Maas 2011年

2
仅当您使用C#:p进行“天生”编程时,才“奇怪的行为”。但是,由于答案是正确的并且得到了很好的解释,您也会得到我的+1;)
ThunderGr 2014年

5
@ThunderGr不,无论您使用哪种语言,这都是很奇怪的。为了使陈述v3 = v1 + v2;导致v1改变以及v3不寻常
Assimilater 16'7

17

我认为您会发现此链接有用:超载运算符

赋值运算符不能重载,但是例如+ =可以使用+求值,可以重载。


2
@pickypg-此评论与此线程无关:请取消删除您的答案,它的确回答了我的问题,我认为我别无选择,只能使用您的方法,我认为有更好的解决方法。
Shimmy Weitzhandler 2012年

16

这是由于相同的原因,赋值运算符不能重载。您无法编写可以正确执行分配的代码。

class Foo
{
   // Won't compile.
   public static Foo operator= (Foo c1, int x)
   {
       // duh... what do I do here?  I can't change the reference of c1.
   }
}

赋值运算符不能重载,但是例如+ =可以使用+求值,可以重载。

来自MSDN


16

您不能重载,+=因为它实际上不是唯一的运算符,它只是语法糖x += y只是速记方式x = x + y。因为+=是根据+=运算符定义的,所以在x += yx = x + y行为不完全相同的情况下,允许您分别覆盖它可能会产生问题。

在较低的级别上,C#编译器很可能会将两个表达式都编译为相同的字节码,这意味着运行时很可能在程序执行期间无法将它们区别对待。

我可以理解,您可能希望将其视为单独的操作:在类似的语句中,x += 10您知道可以对x对象进行突变并节省一些时间/内存,而不是x + 10在通过旧引用分配对象之前创建新对象。

但是请考虑以下代码:

a = ...
b = a;
a += 10;

应该a == b在最后吗?对于大多数类型,不a大于b。但是,如果您可能会使+=运算符超负荷以进行原位变异,那么可以。现在考虑一下a,它b可以传递到程序的远处。如果您的对象开始更改代码所不期望的位置,则可能的优化可能会造成令人困惑的错误。

换句话说,如果性能是如此重要,那么x += 10用诸如之类的方法调用来替换它并不难x.increaseBy(10),而且对每个参与人员而言,它都变得更加清晰。


2
就个人而言,我将更it's just syntactic sugar改为it's just syntactic sugar in C#;否则,这听起来太笼统,但是在某些编程语言中,它不仅是语法糖,而且实际上可能会带来性能上的好处。
塞巴斯蒂安·马赫

对于简单(int,float等)类型+=,由于算法简单,可能可以通过智能编译器进行优化。但是,一旦您处理对象,所有的赌注都关闭了。任何语言都面临着几乎相同的问题。这就是为什么运算符重载是邪恶的。
benzado 2011年

@SebastianMach该问题专门用c#和dotnet标签标记。显然,例如在c ++中,“ +”和“ + =”(甚至“ =”)可以分别重载。
Bojidar Stanchev

1
@BojidarStanchev:是的。我为自己9岁以下的年轻人道歉:-D
塞巴斯蒂安·马赫

9

这是因为不能重载此运算符:

赋值运算符不能重载,但是例如+ =可以使用+求值,可以重载。

MSDN

只是重载+运算符,因为

x += y 等于 x = x + y



6

如果您+像这样重载运算符:

class Foo
{
    public static Foo operator + (Foo c1, int x)
    {
        // implementation
    }
}

你可以做

 Foo foo = new Foo();
 foo += 10;

要么

 foo = foo + 10;

这将编译并平均运行。


6

这个问题总是有相同的答案:为什么您需要+=,如果您重载,则免费获得它+。但是,如果我有这样的课程会怎样?

using System;
using System.IO;

public class Class1
{
    public class MappableObject
    {
        FileStream stream;

        public  int Blocks;
        public int BlockSize;

        public MappableObject(string FileName, int Blocks_in, int BlockSize_in)
        {
            Blocks = Blocks_in;
            BlockSize = BlockSize_in;

            // Just create the file here and set the size
            stream = new FileStream(FileName); // Here we need more params of course to create a file.
            stream.SetLength(sizeof(float) * Blocks * BlockSize);
        }

        public float[] GetBlock(int BlockNo)
        {
            long BlockPos = BlockNo * BlockSize;

            stream.Position = BlockPos;

            using (BinaryReader reader = new BinaryReader(stream))
            {
                float[] resData = new float[BlockSize];
                for (int i = 0; i < BlockSize; i++)
                {
                    // This line is stupid enough for accessing files a lot and the data is large
                    // Maybe someone has an idea to make this faster? I tried a lot and this is the simplest solution
                    // for illustration.
                    resData[i] = reader.ReadSingle();
                }
            }

            retuen resData;
        }

        public void SetBlock(int BlockNo, float[] data)
        {
            long BlockPos = BlockNo * BlockSize;

            stream.Position = BlockPos;

            using (BinaryWriter reader = new BinaryWriter(stream))
            {
                for (int i = 0; i < BlockSize; i++)
                {
                    // Also this line is stupid enough for accessing files a lot and the data is large
                    reader.Write(data[i];
                }
            }

            retuen resData;
        }

        // For adding two MappableObjects
        public static MappableObject operator +(MappableObject A, Mappableobject B)
        {
            // Of course we have to make sure that all dimensions are correct.

            MappableObject result = new MappableObject(Path.GetTempFileName(), A.Blocks, A.BlockSize);

            for (int i = 0; i < Blocks; i++)
            {
                float[] dataA = A.GetBlock(i);
                float[] dataB = B.GetBlock(i);

                float[] C = new float[dataA.Length];

                for (int j = 0; j < BlockSize; j++)
                {
                    C[j] = A[j] + B[j];
                }

                result.SetBlock(i, C);
            }
        }

        // For adding a single float to the whole data.
        public static MappableObject operator +(MappableObject A, float B)
        {
            // Of course we have to make sure that all dimensions are correct.

            MappableObject result = new MappableObject(Path.GetTempFileName(), A.Blocks, A.BlockSize);

            for (int i = 0; i < Blocks; i++)
            {
                float[] dataA = A.GetBlock(i);

                float[] C = new float[dataA.Length];

                for (int j = 0; j < BlockSize; j++)
                {
                    C[j] = A[j] + B;
                }

                result.SetBlock(i, C);
            }
        }

        // Of course this doesn't work, but maybe you can see the effect here.
        // when the += is automimplemented from the definition above I have to create another large
        // object which causes a loss of memory and also takes more time because of the operation -> altgough its
        // simple in the example, but in reality it's much more complex.
        public static MappableObject operator +=(MappableObject A, float B)
        {
            // Of course we have to make sure that all dimensions are correct.

            MappableObject result = new MappableObject(Path.GetTempFileName(), A.Blocks, A.BlockSize);

            for (int i = 0; i < Blocks; i++)
            {
                float[] dataA = A.GetBlock(i);

                for (int j = 0; j < BlockSize; j++)
                {
                    A[j]+= + B;
                }

                result.SetBlock(i, A);
            }
        }
    }
}

您是否还说+=“自动实施” 是件好事。如果您尝试使用C#进行高性能计算,则需要具有减少处理时间和内存消耗的功能,如果某人有一个好的解决方案,将受到高度赞赏,但不要告诉我我必须使用静态方法来完成此操作,这只是一个解决方法,我认为没有理由解释为什么C#会在+=未定义的情况下执行该实现,并且如果定义了它将被使用。有人说两者之间没有区别++=可以防止错误,但这不是我自己的问题吗?


2
如果您真的在乎性能,那么您就不会弄乱运算符重载,这只会使您更难知道正在调用什么代码。至于弄乱语义是否+=是您自己的问题……只有在没有其他人必须阅读,维护或执行您的代码的情况下,这才是正确的。
benzado 2011年

2
哈be,benzado 在某种程度上,您是对的,但是我们拥有的是一个用于高性能计算创建原型应用程序的平台。一方面,我们需要具有性能,另一方面,我们需要简单的语义。实际上,我们还希望拥有比C#当前交付数量更多的运算符。在这里,我希望C#5和将编译器作为一种服务技术来充分利用C#语言。但是,随着我与C ++的接触,我很高兴C#中还有C ++的更多功能,但是由于我######################,所以我不想再次接触C ++。
msedi 2011年

2
工程就是权衡;您想要的一切都带有价格。
benzado 2011年

3
算术运算符按照约定返回新实例-因此,它们通常在不可变类型上被覆盖。举例来说,您无法List<T>使用运算子将新元素添加到中list += "new item"。您Add改为调用其方法。
Şafak古尔


0

更好的设计方法是显式铸造。您绝对可以使Casting超载。

By using our site, you acknowledge that you have read and understand our Cookie Policy and Privacy Policy.
Licensed under cc by-sa 3.0 with attribution required.