“+=”的C#运算符重载

本文关键字:运算符 重载 | 更新日期: 2023-09-27 18:01:07

我正在尝试为+=执行运算符重载,但我做不到。我只能使+的运算符过载。

为什么?

编辑

这不起作用的原因是我有一个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。

“+=”的C#运算符重载

可重载运算符,来自MSDN:

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

更重要的是,没有一个赋值运算符可以重载。我认为这是因为垃圾收集和内存管理会受到影响,这是CLR强类型世界中潜在的安全漏洞。

然而,让我们看看运算符到底是什么。根据著名的Jeffrey Richter的书,每种编程语言都有自己的运算符列表,这些运算符列表是在一个特殊的方法调用中编译的,而CLR本身对运算符一无所知。那么,让我们看看++=操作符背后到底隐藏着什么。

看到这个简单的代码:

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

这与赋值运算符不能重载的原因相同。您无法编写能够正确执行分配的代码。

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。

您不能重载+=,因为它实际上不是一个唯一的运算符,它只是语法糖。x += y只是书写x = x + y的一种简写方式。由于+=是根据+=运算符定义的,因此在x += yx = x + y的行为方式不完全相同的情况下,允许您分别重写它可能会产生问题。

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

我可以理解,您可能希望将其视为一个单独的操作:在像x += 10这样的语句中,您知道可以在适当的位置更改x对象,也许可以节省一些时间/内存,而不是在将其分配给旧引用之前创建一个新对象x + 10

但考虑一下这个代码:

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

a == b应该在最后吗?对于大多数类型,否,ab多10。但是,如果您可以重载+=运算符,使其在适当的位置发生突变,那么是的。现在考虑一下ab可以传递到程序的较远部分。如果你的对象在代码不希望的地方开始改变,你可能的优化可能会产生令人困惑的错误

换言之,如果性能如此重要,那么用x.increaseBy(10)这样的方法调用替换x += 10就不难了,而且对所有相关人员来说都会清楚得多。

我想你会发现这个链接信息丰富:Overloadable Operators

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

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

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

MSDN

由于,仅使+操作员过载

x += y等于x = x + y

+=运算符中使用+的运算符重载,A += B等于A = operator+(A, B)

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

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

你可以做

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

 foo = foo + 10;

这将平等地编译和运行。

这个问题的答案总是一样的:如果+过载,你为什么需要+=。但是如果我有这样的课会怎样呢。

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#为什么要实现+=,并且如果定义了它,则将使用它。有人说++=之间没有差异可以防止错误,但这不是我自己的问题吗?

我有完全相同的问题,我不可能比这个人的回答得更好

一个更好的设计方法是显式铸造。你肯定可以超载施法。