簡體   English   中英

`+=` 的 C# 運算符重載?

[英]C# operator overload for `+=`?

我正在嘗試為+=執行運算符重載,但我不能。 我只能為+做一個運算符重載。

怎么來的?

編輯

這不起作用的原因是我有一個 Vector class (帶有 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:

賦值運算符不能被重載,但是+= ,例如,使用+來評估,它可以被重載。

更重要的是,沒有一個賦值運算符可以重載。 我認為這是因為垃圾收集和 memory 管理會產生影響,這是 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 所說,您確實應該將向量作為不可變的 object。 兩個向量相加的結果是一個向量,而不是第一個具有不同大小的向量。

如果由於某種原因需要更改第一個向量,則可以使用此重載(但對我而言,這是非常奇怪的行為):

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

我想你會發現這個鏈接信息豐富: Overloadable Operators

賦值運算符不能重載,但 +=,例如,使用 + 計算,可以重載。

你不能重載+=因為它不是一個真正的唯一運算符,它只是語法糖 x += y只是x = x + y的一種簡寫方式。 因為+=是根據+=運算符定義的,所以在x += yx = x + y的行為方式不完全相同的情況下,允許您單獨覆蓋它可能會產生問題。

在較低級別,C# 編譯器很可能將兩個表達式編譯為相同的字節碼,這意味着運行時很可能在程序執行期間無法區別對待它們。

我可以理解您可能希望將其視為單獨的操作:在x += 10之類的語句中,您知道您可以在適當的位置改變x object 並可能節省一些時間/內存,而不是創建新的 object x + 10在將其分配給舊參考之前。

但是考慮一下這段代碼:

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

最后應該a == b嗎? 對於大多數類型,不, ab多 10。 但是,如果您可以重載+=運算符以進行適當的變異,那么可以。 現在考慮ab可以傳遞到程序的遙遠部分。 如果您的 object 開始更改代碼不期望的位置,您可能的優化可能會產生令人困惑的錯誤。

換句話說,如果性能如此重要,那么將x += 10替換為x.increaseBy(10)之類的方法調用並不難,而且對所有相關人員來說都更加清晰。

這與賦值運算符不能重載的原因相同。 您無法編寫能夠正確執行分配的代碼。

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

這是因為這個運算符不能被重載:

賦值運算符不能重載,但 +=,例如,使用 + 計算,可以重載。

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;

這將同樣編譯和運行。

這個問題總是有相同的答案:為什么你需要+= ,如果你超載+是免費的。 但是如果我有這樣的 class 會發生什么。

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# 中進行高性能計算,您需要具有這樣的功能來減少處理時間和 memory 消耗,如果有人有一個好的解決方案,非常感謝,但不要告訴我我必須使用 ZA81259CEF45659C2947EDF1 方法來做到這一點,這只是一種解決方法,我看不出為什么 C# 沒有定義+=實現,如果它被定義,它將被使用。 有人說++=之間沒有區別可以防止錯誤,但這不是我自己的問題嗎?

我有完全相同的問題,我不可能比這個人回答得更好

更好的設計方法是顯式鑄造。 您絕對可以重載 Casting。

暫無
暫無

聲明:本站的技術帖子網頁,遵循CC BY-SA 4.0協議,如果您需要轉載,請注明本站網址或者原文地址。任何問題請咨詢:yoyou2525@163.com.

 
粵ICP備18138465號  © 2020-2024 STACKOOM.COM