-1

Writing the code for the mathematical operations between my custom classes of vectors and matrices I have faced a strange (at least for me) problem.

The overloaded operators between types give different results in different places. I have dug out that the result of the operation is sensitive on how the new instance is created inside the "operator +(...)".

I have created the simple code showing the difference (see below). Two classes are making the same thing and the same result is expected, but in reality not the same. The overloaded operator of Type2 and the function "test" modify the variable. I have not found any warning about this in MSDN. This behavior is not marked directly by syntax (f.e. by the "ref" keyword).

Can anyone recommend a link about this problem? Is it predicted behavior or a mistake in the C# syntax?

using System;

namespace test_operator_overload
{
    class Program
    {
        static void Main()
        {
            Type1 A1 = new Type1(1);
            Console.WriteLine(A1);          // 1 - OK
            Console.WriteLine(A1 + A1);     // 2 - OK
            Console.WriteLine(A1);          // 1 - OK

            Console.WriteLine();

            Type2 B1 = new Type2(1);
            Console.WriteLine(B1);          // 1 - OK
            Console.WriteLine(B1 + B1);     // 2 - OK
            Console.WriteLine(B1);          // 2 - Not OK

            Console.WriteLine();

            Type2 C1 = new Type2(1);
            Console.WriteLine(C1);          // 1 - OK
            Console.WriteLine(test(C1));    // 2 - OK
            Console.WriteLine(C1);          // 2 - Not OK
        }

        static Type2 test(Type2 a)
        {
            a.x += 1;
            return a;
        }
    }



    class Type1
    {
        public double x;

        public Type1(double x)
        {
            this.x = x;
        }

        public static Type1 operator +(Type1 a, Type1 b)
        {
            return new Type1(a.x + b.x);
        }

        public override string ToString() { return GetType().Name + " (" + x + ")"; }
    }


    class Type2
    {
        public double x;

        public Type2(double x)
        {
            this.x = x;
        }

        public static Type2 operator +(Type2 a, Type2 b)
        {
            a.x += b.x;
            return a;
        }

        public override string ToString() { return GetType().Name + " (" + x + ")"; }
    }
}
Dennis
  • 47
  • 6
  • Questions seeking debugging help ("why isn't this code working?") must include the desired behavior, a specific problem or error and the shortest code necessary to reproduce it in the question itself. Questions without a clear problem statement are not useful to other readers. See: How to create a Minimal, Complete, and Verifiable example. – mybirthname Feb 04 '16 at 22:47
  • 3
    Mutating the arguments in operators is bound to cause confusion. No one expects those kinds of side effects from operators. – Mike Zboray Feb 04 '16 at 22:53
  • 2
    Your operators for `clsB` are not creating a new instance. They are *changing* the properties of your operands during the evaluation of the expression. Therefore, `B2`'s properties, which is used twice in its expression, have different values for the two different operations. – Glorin Oakenfoot Feb 04 '16 at 22:53
  • @mybirthname side note: for comments use `[ MCVE ]` (no spaces) instead to get link with text. Also gives you extra characters :) – Alexei Levenkov Feb 04 '16 at 22:56
  • On topic: please check out [Why mutable structs are evil](http://stackoverflow.com/questions/441309/why-are-mutable-structs-evil) - as @mikez said it is very bad idea and completely different from regular operators on `float` and other .numeric types. – Alexei Levenkov Feb 04 '16 at 22:59
  • What is bound to happen with class B when you type `B1 = 4 * B2 + B1 * B2`: First of all, B2 is multiplied with 4, so it changes from (0.3, 2.1) to (1.2, 8.4). Second, B1 is multiplied with the new B2, meaning (1, 1) * (1.2, 8.4) = (1.2, 8.4). And finally, the addition is performed between the two returned vectors on both sides, which are both (1.2, 8.4), resulting in (2.4, 16.8). It really is a question on side affects in de-facto assignment operators. – hschmauder Feb 04 '16 at 23:08
  • I find this an interesting question, I am unsure why the down votes. I understand the excellent explanation from @hschmauder, but I think that only answers half of @Dennis's question: why does this happen for `clsB` and not `strD`. I imagine the answer is that classes and structs are passed differently (by reference/by value)? – Ian Feb 04 '16 at 23:11
  • @Ian I cannot test it at the moment, but from my understanding, that's exactly the point. Since D is a struct, it will not be passed as a reference to the operator method; B is an actual class and the x and y values of B1 and B2 get changed throughout the operator invocations. Since with D, only copies of D1 and D2 are passed to the operators, it does not influence the rest of the arithmetics (since only the values on the copied structs, not the original ones are modified). – hschmauder Feb 04 '16 at 23:16
  • I explained my question a little bit more clear and simplified the example code. – Dennis Feb 05 '16 at 13:01
  • You should not write operators for classes, and at the very least not for mutable types. In any case, you should **never** modify the inputs to an operator. – Lasse V. Karlsen Feb 05 '16 at 13:15
  • I strongly suggest you post that as an answer @hschmauder, combining both comments (first about modifying the class, and second about passing by value/reference). I'll upvote it, and I believe it should be the accepted answer. – Ian Feb 08 '16 at 00:03

1 Answers1

1

Your + operator for Type2 is borked, you should not modify the inputs in the operators.

Operators operate on the input by combining the inputs and returning the new results.

Lasse V. Karlsen
  • 380,855
  • 102
  • 628
  • 825
  • Yes, I have already applied struct to all my classes used the overloaded operators. Now it's about 30% faster. But the problem what I explained in the example is still not clear. As mentioned in MSDN: [link](https://msdn.microsoft.com/EN-US/library/8f1hz171(v=VS.140,d=hv.2).aspx) "... the changed value will not be retained when control passes back to the calling procedure." In my example when I use the overloaded operator or call the function "test", the parameter value is changed after the control passed back to the calling procedure. So. What's wrong? – Dennis Feb 06 '16 at 00:04
  • Well it's doing excactly what you asked of it. add the inputs, then set one of the input values equal to the results.. operators are not different from functions, in that they get inputs via references, and can modify the value of them. – Henrik Jun 13 '16 at 13:30
  • The document @Dennis refers to talks about passing by value vs. passing by reference, not "passing *a* reference", so while the documentation he cites is technically correct, it isn't relevant to his problem. – Lasse V. Karlsen Jun 13 '16 at 15:34