“+=”的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。
可重载运算符,来自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 += y
和x = x + y
的行为方式不完全相同的情况下,允许您分别重写它可能会产生问题。
在较低的级别上,C#编译器很可能将两个表达式编译为相同的字节码,这意味着运行时很可能在程序执行期间不能区别对待它们。
我可以理解,您可能希望将其视为一个单独的操作:在像x += 10
这样的语句中,您知道可以在适当的位置更改x
对象,也许可以节省一些时间/内存,而不是在将其分配给旧引用之前创建一个新对象x + 10
。
但考虑一下这个代码:
a = ...
b = a;
a += 10;
a == b
应该在最后吗?对于大多数类型,否,a
比b
多10。但是,如果您可以重载+=
运算符,使其在适当的位置发生突变,那么是的。现在考虑一下a
和b
可以传递到程序的较远部分。如果你的对象在代码不希望的地方开始改变,你可能的优化可能会产生令人困惑的错误
换言之,如果性能如此重要,那么用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#为什么要实现+=
,并且如果定义了它,则将使用它。有人说+
和+=
之间没有差异可以防止错误,但这不是我自己的问题吗?
我有完全相同的问题,我不可能比这个人的回答得更好
一个更好的设计方法是显式铸造。你肯定可以超载施法。