`+ =`的C#运算符重载?

Mat*_*zen 109 .net c# operator-overloading .net-4.0 c#-4.0

我试图做操作符重载+=,但我不能.我只能让操作员超载+.

怎么会?

编辑

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

vector1 += vector2;
Run Code Online (Sandbox Code Playgroud)

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

public static Vector operator +(Vector left, Vector right)
{
    return new Vector(right.x + left.x, right.y + left.y);
}
Run Code Online (Sandbox Code Playgroud)

然后结果将不会添加到vector1,而是vector1也将通过引用成为全新的Vector.

VMA*_*Atm 144

来自MSDN的可重载运算符:

赋值运算符不能过载,但是+=,例如,使用+可以重载的方式进行评估.

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

不过,让我们看看究竟是什么运营商.根据着名的Jeffrey Richter的书,每种编程语言都有自己的运算符列表,这些列表是在特殊的方法调用中编译的,而CLR本身对运算符一无所知.那么让我们看看究竟是什么++=运营商背后.

看到这个简单的代码:

Decimal d = 10M;
d = d + 10M;
Console.WriteLine(d);
Run Code Online (Sandbox Code Playgroud)

让我们查看IL-code以获取此说明:

  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
Run Code Online (Sandbox Code Playgroud)

现在让我们看看这段代码:

Decimal d1 = 10M;
d1 += 10M;
Console.WriteLine(d1);
Run Code Online (Sandbox Code Playgroud)

和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
Run Code Online (Sandbox Code Playgroud)

他们是平等的!因此,+=运算符只是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;
}
Run Code Online (Sandbox Code Playgroud)

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

  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
Run Code Online (Sandbox Code Playgroud)

更新:

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

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

public static Vector operator +(Vector left, Vector right)
{
    left.x += right.x;
    left.y += right.y;
    return left;
}
Run Code Online (Sandbox Code Playgroud)

  • @ThunderGr不,不管你在看哪种语言都很奇怪.使语句`v3 = v1 + v2;`结果`v1`被改变以及'v3`是不寻常的 (5认同)
  • 说明情况并非如此. (2认同)
  • 他们为什么这样设计呢; 这就是问题所在,真的.看看其他一些答案. (2认同)
  • 仅当您使用C#:p进行“天生”编程时,才“奇怪的行为”。但是,由于答案是正确的,而且解释得很好,所以您也得到了+1;) (2认同)

pic*_*ypg 17

我想你会发现这个链接提供信息:可重载的运营商

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

  • @pickypg - 这个评论与这个帖子无关:请取消删除[你的答案](http://stackoverflow.com/a/9627650/75500),它确实回答了我的问题,我想我别无选择,只能使用你的方法,我认为有一些更好的方法来解决它. (2认同)

ben*_*ado 16

你不能超载,+=因为它不是一个独特的操作符,它只是语法糖.x += y只是写作的简写方式x = x + y.因为+=是根据+=运算符定义的,所以允许你单独覆盖它可能会产生问题,在这种情况下,x += y并且x = x + y行为方式完全相同.

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

我可以理解你可能想把它当作一个单独的操作:在x += 10你知道你可以改变x对象的语句中并且可能节省一些时间/内存,而不是x + 10在通过旧引用分配它之前创建一个新对象.

但请考虑以下代码:

a = ...
b = a;
a += 10;
Run Code Online (Sandbox Code Playgroud)

应该a == b到底?对于大多数类型,不,a是10多个b.但是如果你可以让+=运算符超载到位,那么是的.现在考虑一下a,b可以传递给程序的远端部分.如果您的对象在代码不期望的位置开始更改,那么您可能的优化可能会产生令人困惑的错误.

换句话说,如果性能非常重要,那么x += 10用类似的方法调用替换它并不困难x.increaseBy(10),并且对于所涉及的每个人来说都更加清晰.

  • 就我个人而言,我会将"它只是语法糖"改为"它只是C#中的语法糖"; 否则,这听起来过于笼统,但在某些编程语言中,它不仅仅是语法糖,而且实际上可能带来性能上的好处. (2认同)

age*_*t-j 15

这是因为赋值运算符不能重载的原因相同.您无法编写能够正确执行赋值的代码.

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.
   }
}
Run Code Online (Sandbox Code Playgroud)

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

来自MSDN.


And*_*ich 9

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

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

MSDN

只是过载+运营商,因为

x += y 等于 x = x + y


Ale*_*dow 6

运算符重载用于+在使用+=操作,A += B等于A = operator+(A, B).


Bal*_*a R 6

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

class Foo
{
    public static Foo operator + (Foo c1, int x)
    {
        // implementation
    }
}
Run Code Online (Sandbox Code Playgroud)

你可以做

 Foo foo = new Foo();
 foo += 10;
Run Code Online (Sandbox Code Playgroud)

要么

 foo = foo + 10;
Run Code Online (Sandbox Code Playgroud)

这将编译并运行相同.


mse*_*edi 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);
            }
        }
    }
}
Run Code Online (Sandbox Code Playgroud)

你还说这+=是"自动实施"的好处.如果你尝试在C#中进行高性能计算,你需要有这样的功能来减少处理时间和内存消耗,如果有人有一个很好的解决方案,很高兴,但不要告诉我,我必须用静态方法做到这一点,这只是一种解决方法,+=如果没有定义,我认为没有理由为什么C#执行实现,如果定义它将被使用.有人说没有区别++=防止错误,但这不是我自己的问题吗?

  • 算术运算符按照约定返回新实例-因此,它们通常在不可变类型上被覆盖。例如,您不能使用`list + =“ new item”`这样的运算符将新元素添加到List &lt;T&gt;中。您改为调用其“添加”方法。 (3认同)
  • 如果你真的关心性能,你就不会搞乱运算符重载,这只会让你更难分辨出调用的是什么代码.至于搞乱`+ =`的语义是否是你自己的问题......只有在没有其他人必须阅读,维护或执行你的代码时才会这样. (2认同)
  • 你好,苯扎多.在某种程度上,你是对的,但我们拥有的是一个用于创建原型应用程序的高性能计算平台.在某种程度上,我们需要有性能,另一方面我们需要一个简单的语义.事实上,我们也希望拥有比C#目前更多的运营商.在这里,我希望C#5和编译器作为一种服务技术,以便从C#语言中获得更多.然而,随着我在C++中长大,如果在C#中有更多来自C++的功能,我将不胜感激,尽管我不想再次触及C++,因为我正在做C#. (2认同)
  • 工程学就是权衡; 你想要的一切都需要付出代价. (2认同)