`+ =`에 대한 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 2011

답변:


147

MSDN의 Overloadable Operators :

할당 연산자는 오버로드 할 수 없지만 +=예를 들어는 +오버로드 될 수있는를 사용하여 평가됩니다 .

더욱이 할당 연산자는 오버로드 될 수 없습니다. 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 2011

1
@Jouke van der Maas 그리고 그것이 불가능한 이유에 대해 어떻게 대답하기를 원하십니까? 설계 상 불가능합니다. 또 뭐라고 말할 수 있나요?
VMAtm 2011

2
왜 이렇게 설계했는지; 그게 질문입니다. 다른 답변을 참조하십시오.
Jouke van der Maas 2011

2
"이상한 행동"은 C #에서 "태어난"프로그래밍을 한 경우에만 발생합니다. 하지만 답이 정확하고 잘 설명되어 있으므로 내 +1도 얻을 수 있습니다.)
ThunderGr

5
@ThunderGr 아니요, 어떤 언어를보고 있더라도 꽤 이상합니다. 문하려면 v3 = v1 + v2;에 결과가 v1아니라으로 변경되는 것은 v3이례적인 일이다
Assimilater

17

이 링크가 유익하다고 생각합니다. Overloadable Operators

할당 연산자는 오버로드 할 수 없지만, 예를 들어 + =는 오버로드 될 수있는 +를 사용하여 평가됩니다.


2
@pickypg-이 댓글은이 스레드와 관련이 없습니다. 답변 삭제를 취소 하세요. 내 질문에 대한 답변입니다. 귀하의 방법을 사용할 수밖에 없다고 생각합니다. 문제를 해결하는 더 좋은 방법이 있다고 생각합니다.
Shimmy Weitzhandler 2012 년

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. 때문에 +=의 관점에서 정의 +하고 =당신이 경우에 문제를 만들 수 별도로 오버라이드 (override) 할 수 있도록 사업자 x += yx = x + y동일한 방식으로 행동하지 않았다.

낮은 수준에서는 C # 컴파일러가 두 식을 동일한 바이트 코드로 컴파일 할 가능성이 매우 높습니다. 즉, 프로그램 실행 중에 런타임에서 이러한 식을 다르게 처리 할 수 없을 가능성이 매우 높습니다 .

나는 당신이 그것을 별도의 작업처럼 취급하고 싶을 수도 있다는 것을 이해할 수 있습니다 : x += 10당신이 알고있는 것과 같은 문장에서, 당신 이 이전 참조 위에 그것을 할당하기 전에 x새로운 객체 x + 10를 생성하는 것보다 제자리 에서 객체를 변형 하고 아마도 약간의 시간 / 메모리를 절약 할 수 있다는 것을 알 수 있습니다. .

그러나 다음 코드를 고려하십시오.

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

해야 a == b끝에? 대부분의 유형에서 아니오 ab. 그러나 +=연산자를 오버로드하여 제자리에서 변경할 수 있다면 그렇습니다. 이제을 고려 a하고 b프로그램의 먼 부분에 건네받을 수 있습니다. 가능한 최적화는 개체가 코드에서 예상하지 않은 위치에서 변경되기 시작하면 혼란스러운 버그를 만들 수 있습니다.

즉, 성능이 그토록 중요하다면 x += 10과 같은 메서드 호출 로 대체 하는 것이 그리 어렵지 않으며 x.increaseBy(10)관련된 모든 사람에게 훨씬 더 명확합니다.


2
개인적으로 나는 다음과 같이 변경 it's just syntactic sugar합니다 it's just syntactic sugar in C#. 그렇지 않으면 너무 일반적으로 들리지만 일부 프로그래밍 언어에서는 구문 적 설탕 일뿐만 아니라 실제로 성능 이점을 제공 할 수 있습니다.
Sebastian Mach 2011

간단한 (int, float 등) 유형의 경우 +=산술이 간단하기 때문에 스마트 컴파일러로 최적화 할 수 있습니다. 그러나 일단 물건을 다루면 모든 베팅이 해제됩니다. 모든 언어는 거의 동일한 문제에 직면합니다. 이것이 연산자 오버로딩이 나쁜 이유입니다.
benzado 2011

@SebastianMach이 질문은 특히 c # 및 dotnet 태그로 태그가 지정됩니다. 예를 들어 C ++에서 '+'와 '+ ='(그리고 심지어 '=')는 개별적으로 오버로드 될 수 있습니다.
Bojidar Stanchev

1
@BojidarStanchev : 사실입니다. 9 살이 된 저에게 사과드립니다 :-D
Sebastian Mach

9

이것은이 연산자가 오버로드 될 수 없기 때문입니다.

할당 연산자는 오버로드 할 수 없지만, 예를 들어 + =는 오버로드 될 수있는 +를 사용하여 평가됩니다.

MSDN

오버로드 +연산자 때문에

x += y 동일 x = x + y



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 2011

2
안녕하세요, 벤 자도. 어떤면에서는 당신 말이 맞지만 우리가 가지고있는 것은 프로토 타입 애플리케이션을 만들기위한 고성능 컴퓨팅을위한 플랫폼입니다. 어떤면에서는 성능이 필요하고 다른면에서는 단순한 의미 체계가 필요합니다. 사실 우리는 또한 현재 C #이 제공하는 것보다 더 많은 연산자를 원합니다. 여기에서는 C # 5와 컴파일러를 서비스 기술로 사용하여 C # 언어를 최대한 활용하기를 바랍니다. 그럼에도 불구하고 C ++로 자랐고 C #에서 C ++의 기능이 조금 더 있으면 감사하겠습니다.하지만 C #을하고 있기 때문에 C ++를 다시 만지고 싶지는 않습니다.
msedi

2
엔지니어링은 트레이드 오프에 관한 것입니다. 당신이 원하는 모든 것은 가격과 함께 제공됩니다.
benzado 2011

3
산술 연산자는 규칙에 따라 새 인스턴스를 반환하므로 일반적으로 변경 불가능한 유형에서 재정의됩니다. 예를 List<T>들어와 같은 연산자를 사용하여에 새 요소를 추가 할 수 없습니다 list += "new item". Add대신 메서드를 호출합니다 .
Şafak Gür


0

더 나은 설계 방법은 Explicit Casting입니다. 확실히 캐스팅에 과부하가 걸릴 수 있습니다.

당사 사이트를 사용함과 동시에 당사의 쿠키 정책개인정보 보호정책을 읽고 이해하였음을 인정하는 것으로 간주합니다.
Licensed under cc by-sa 3.0 with attribution required.