ตัวดำเนินการ 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
คุณอธิบายได้ไหมว่าทำไมคุณถึงพยายามทำสิ่งนี้ คุณจะได้รับโอเปอเรเตอร์ "+ =" มากเกินไปฟรีเมื่อคุณใช้งานเกิน "+" มีสถานการณ์บางอย่างที่คุณไม่ต้องการ "+ =" จะได้รับมากเกินไป แต่ทำไม่ได้ต้องการ "+" ที่จะมากเกินไป?
Eric Lippert

3
มาจาก C ++ นี่รู้สึกผิด แต่ใน C # มันสมเหตุสมผลจริงๆ
Jon Purdy


12
@Mathias: การอัปเดตของคุณ: เวกเตอร์ควรทำงานเหมือนวัตถุทางคณิตศาสตร์ที่ไม่เปลี่ยนรูป เมื่อคุณเพิ่ม 2 ถึง 3 คุณจะไม่กลายพันธุ์วัตถุ 3 ลงในวัตถุ 5 คุณสร้างวัตถุใหม่ทั้งหมด 5. จุดที่ตัวดำเนินการเพิ่มมากเกินไปคือการสร้างวัตถุทางคณิตศาสตร์ของคุณเอง ทำให้พวกเขาทำงานที่ไม่แน่นอนกับเป้าหมายนั้น ฉันจะทำให้ประเภทเวกเตอร์ของคุณเป็นประเภทค่าที่ไม่เปลี่ยนรูป
Eric Lippert

คำตอบ:


147

ตัวดำเนินการที่โอเวอร์โหลดจาก 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;
}

2
การระบุว่าเป็นกรณีนี้ไม่ตอบโจทย์ว่าทำไม
Jouke van der Maas

1
@Jouke van der Maas และคุณต้องการให้ฉันตอบอย่างไรว่าทำไมถึงเป็นไปไม่ได้? การออกแบบเป็นไปไม่ได้ฉันจะพูดอะไรได้อีก?
VMAtm

2
ทำไมพวกเขาถึงออกแบบด้วยวิธีนี้ นั่นคือสิ่งที่เป็นคำถามจริงๆ ดูคำตอบอื่น ๆ
Jouke van der Maas

2
"พฤติกรรมแปลก ๆ " ก็ต่อเมื่อคุณ "เกิด" การเขียนโปรแกรมในภาษา C #: p แต่เนื่องจากคำตอบนั้นถูกต้องและอธิบายได้ดีมากคุณจึงได้รับ +1 ของฉันเช่นกัน)
ThunderGr

5
@ThunderGr ไม่มันค่อนข้างแปลกไม่ว่าคุณจะดูภาษาไหน เพื่อให้คำสั่งที่v3 = v1 + v2;ส่งผลให้เกิดv1การเปลี่ยนแปลงเช่นเดียวกับv3เป็นเรื่องผิดปกติ
Assimilater

17

ฉันคิดว่าคุณจะพบลิงค์นี้ให้ข้อมูล: ตัวดำเนินการที่โอเวอร์โหลดได้

ไม่สามารถโอเวอร์โหลดโอเปอเรเตอร์การมอบหมายงานได้ แต่ตัวอย่างเช่น + = ถูกประเมินโดยใช้ + ซึ่งสามารถโอเวอร์โหลดได้


2
@pickypg - ความคิดเห็นนี้ไม่เกี่ยวข้องกับหัวข้อนี้: โปรดยกเลิกการลบคำตอบของคุณมันตอบคำถามของฉันฉันคิดว่าฉันไม่มีทางเลือกนอกจากใช้วิธีของคุณฉันคิดว่ามีวิธีที่ดีกว่าในการแก้ปัญหานี้
Shimmy Weitzhandler

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 += yและx = x + yไม่ได้ทำงานในลักษณะเดียวกัน

ในระดับที่ต่ำกว่านั้นมีความเป็นไปได้สูงที่คอมไพเลอร์ C # จะรวบรวมนิพจน์ทั้งสองให้เป็น bytecode เดียวกันซึ่งหมายความว่าเป็นไปได้มากที่รันไทม์จะไม่สามารถปฏิบัติกับพวกมันแตกต่างกันได้ในระหว่างการทำงานของโปรแกรม

ฉันเข้าใจว่าคุณอาจต้องการปฏิบัติเหมือนเป็นการดำเนินการแยกต่างหาก: ในข้อความเช่นx += 10คุณรู้ว่าคุณสามารถกลายพันธุ์xวัตถุได้และอาจช่วยประหยัดเวลา / หน่วยความจำได้บ้างแทนที่จะสร้างวัตถุใหม่x + 10ก่อนกำหนดให้กับข้อมูลอ้างอิงเก่า .

แต่พิจารณารหัสนี้:

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

a == bในตอนท้ายควรหรือไม่? ชนิดส่วนใหญ่ไม่aเป็น 10 bกว่า แต่ถ้าคุณสามารถโอเวอร์โหลดตัว+=ดำเนินการเพื่อกลายพันธุ์ได้ใช่ ลองพิจารณาสิ่งนั้นaและbสามารถส่งต่อไปยังส่วนที่ห่างไกลของโปรแกรมได้ การเพิ่มประสิทธิภาพที่เป็นไปได้ของคุณอาจสร้างจุดบกพร่องที่สับสนหากวัตถุของคุณเริ่มเปลี่ยนแปลงโดยที่โค้ดไม่คาดคิด

กล่าวอีกนัยหนึ่งก็คือหากประสิทธิภาพมีความสำคัญก็ไม่ยากที่จะแทนที่x += 10ด้วยวิธีการเรียกx.increaseBy(10)ไลค์และมันชัดเจนกว่ามากสำหรับทุกคนที่เกี่ยวข้อง


2
ส่วนตัวฉันจะเปลี่ยนit's just syntactic sugarเป็นit's just syntactic sugar in C#; มิฉะนั้นมันฟังดูกว้างเกินไป แต่ในภาษาโปรแกรมบางภาษามันไม่ได้เป็นเพียงแค่น้ำตาลในวากยสัมพันธ์เท่านั้น แต่อาจให้ประโยชน์ด้านประสิทธิภาพ
Sebastian Mach

สำหรับประเภทอย่างง่าย (int, float ฯลฯ ) +=อาจได้รับการปรับให้เหมาะสมโดยคอมไพเลอร์อัจฉริยะเนื่องจากการคำนวณทางคณิตศาสตร์นั้นง่าย แต่เมื่อคุณจัดการกับวัตถุการเดิมพันทั้งหมดจะปิดลง ภาษาใด ๆ ก็ประสบปัญหาเดียวกัน นี่คือสาเหตุที่การทำงานหนักเกินไปของตัวดำเนินการเป็นสิ่งชั่วร้าย
benzado

@SebastianMach คำถามถูกแท็กโดยเฉพาะด้วยแท็ก c # และดอทเน็ต เห็นได้ชัดว่าใน c ++ เช่น '+' และ '+ =' (และแม้แต่ '=') สามารถโอเวอร์โหลดแยกกันได้
โบจิดาร์สแตนเชฟ

1
@BojidarStanchev: จริงอย่างนั้น ฉันขอโทษสำหรับตัวเองที่อายุน้อยกว่า 9 ปีของฉัน :-D
Sebastian Mach

9

เนื่องจากตัวดำเนินการนี้ไม่สามารถโอเวอร์โหลดได้:

ไม่สามารถโอเวอร์โหลดโอเปอเรเตอร์การมอบหมายงานได้ แต่ตัวอย่างเช่น + = ถูกประเมินโดยใช้ + ซึ่งสามารถโอเวอร์โหลดได้

MSDN

เพียงแค่โอเวอร์โหลด+ตัวดำเนินการเนื่องจาก

x += y เท่ากับ x = x + y


6

ผู้ประกอบการเกินสำหรับ+ใช้ในการ+=ดำเนินการเท่ากับA += BA = operator+(A, B)


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

2
สวัสดีครับ benzado ในทางใดทางหนึ่งคุณคิดถูก แต่สิ่งที่เรามีคือแพลตฟอร์มสำหรับการประมวลผลประสิทธิภาพสูงเพื่อสร้างแอปพลิเคชันต้นแบบ ในทางหนึ่งเราต้องมีประสิทธิภาพในอีกด้านหนึ่งเราต้องการความหมายง่ายๆ ในความเป็นจริงเราต้องการให้มีตัวดำเนินการมากกว่าที่ C # ให้บริการในปัจจุบัน ที่นี่ฉันหวังว่า C # 5 และคอมไพเลอร์เป็นเทคนิคการบริการเพื่อให้ได้ประโยชน์จากภาษา C # มากขึ้น อย่างไรก็ตามเมื่อฉันเติบโตมาพร้อมกับ C ++ และจะขอบคุณถ้ามีคุณสมบัติเพิ่มเติมเล็กน้อยจาก C ++ ใน C # แม้ว่าฉันจะไม่อยากสัมผัส C ++ อีกเลยตั้งแต่ฉัน # m ทำ C #
msedi

2
วิศวกรรมเป็นเรื่องของการแลกเปลี่ยน ทุกสิ่งที่คุณต้องการมาพร้อมกับราคา
benzado

3
ตัวดำเนินการทางคณิตศาสตร์จะส่งคืนอินสแตนซ์ใหม่ตามแบบแผนดังนั้นจึงมักจะถูกแทนที่ด้วยประเภทที่ไม่เปลี่ยนรูป คุณไม่สามารถเพิ่มองค์ประกอบใหม่ในการList<T>ใช้ตัวดำเนินlist += "new item"การเช่น คุณเรียกAddวิธีการแทน
ŞafakGür


0

วิธีการออกแบบที่ดีกว่าคือ Explicit Casting คุณสามารถหล่อเกินพิกัดได้แน่นอน

โดยการใช้ไซต์ของเรา หมายความว่าคุณได้อ่านและทำความเข้าใจนโยบายคุกกี้และนโยบายความเป็นส่วนตัวของเราแล้ว
Licensed under cc by-sa 3.0 with attribution required.