Перегрузка оператора C # для `+ =`? - PullRequest
106 голосов
/ 05 июля 2011

Я пытаюсь перегрузить оператор для +=, но не могу.Я могу сделать перегрузку оператора только для +.

Почему?

Редактировать

Причина, по которой это не работает, заключается в том, что у меня естьКласс 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 станет брендомНовый вектор также по ссылке.

Ответы [ 10 ]

141 голосов
/ 05 июля 2011

Операторы с перегрузкой , от MSDN:

Операторы присваивания не могут быть перегружены, но, например, += оценивается с использованием +, который может быть перегружен.

Более того, ни один из операторов присваивания не может быть перегружен. Я думаю, это потому, что это повлияет на сборку мусора и управление памятью, что является потенциальной дырой в безопасности в мире строгой типизации CLR.

Тем не менее, давайте посмотрим, что такое оператор. Согласно известной книге Джеффри Рихтера , каждый язык программирования имеет свой собственный список операторов, которые компилируются в вызовы специальных методов, а сам 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;
}
17 голосов
/ 05 июля 2011

Я думаю, вы найдете эту ссылку информативной: Перегрузка операторов

Операторы присваивания не могут быть перегружен, но + =, например, оценивается с помощью +, который может быть перегружен.

16 голосов
/ 06 июля 2011

Вы не можете перегрузить +=, потому что это на самом деле не уникальный оператор, это просто синтаксический сахар .x += y это просто сокращенный способ написания x = x + y.Поскольку += определяется в терминах операторов + и =, разрешение переопределить его отдельно может создать проблемы в тех случаях, когда x += y и x = x + y не ведут себя точно так же.

На более низком уровне весьма вероятно, что компилятор C # компилирует оба выражения в один и тот же байт-код, а это означает, что вполне вероятно, что среда выполнения не сможет по-разному обработать их во время выполнения программы.

Я могу понять, что вы можете рассматривать это как отдельную операцию: в таком выражении, как x += 10, вы знаете, что вы можете изменить объект x на месте и, возможно, сэкономить некоторое время / память, а нечем создание нового объекта x + 10 перед его назначением поверх старой ссылки.

Но рассмотрим этот код:

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

Должен ли a == b в конце?Для большинства типов нет, a на 10 больше, чем b.Но если вы можете перегрузить оператор +=, чтобы изменить его, тогда да.Теперь учтите, что a и b могут передаваться в удаленные части программы.Ваша возможная оптимизация может привести к сбивающим с толку ошибкам, если ваш объект начинает изменяться там, где код этого не ожидает.

Другими словами, если производительность так важна, заменить x += 10 на метод не сложно.звоните как x.increaseBy(10), и это будет намного понятнее для всех участников.

16 голосов
/ 05 июля 2011

Это по той же причине, по которой оператор присваивания не может быть перегружен.Вы не можете написать код, который бы правильно выполнял присваивание.

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 .

9 голосов
/ 05 июля 2011

Это связано с тем, что этот оператор не может быть перегружен:

Операторы присваивания не могут быть перегружены, но, например, + = оценивается с помощью +, который может быть перегружен.

MSDN

Просто оператор перегрузки +, поскольку

x += y равно x = x + y

6 голосов
/ 06 июля 2011

На эту проблему всегда один и тот же ответ: зачем вам +=, если вы получаете его бесплатно, если вы перегружаете +. Но что произойдет, если у меня будет такой класс?

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 # выполняет реализацию +=, если она не определена, и если она определена, она будет использоваться. Некоторые люди говорят, что отсутствие разницы между + и += предотвращает ошибки, но разве это не моя собственная проблема?

6 голосов
/ 05 июля 2011

Если вы перегрузите оператор +, например, так:

class Foo
{
    public static Foo operator + (Foo c1, int x)
    {
        // implementation
    }
}

вы можете сделать

 Foo foo = new Foo();
 foo += 10;

или

 foo = foo + 10;

Это скомпилируется и будет работать одинаково.

6 голосов
/ 05 июля 2011

Перегрузка оператора для + используется в операторе +=, A += B равно A = operator+(A, B).

3 голосов
/ 06 июля 2011

У меня был точно такой же вопрос, и я не могу ответить на него лучше, чем у этого человека

0 голосов
/ 03 декабря 2018

Лучшим методом проектирования является явное приведение. Вы можете определенно перегружать кастинг.

...