Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Don't understand pre decrement operator behavior with Nullable type

Tags:

c#

nullable

Ok, this might be obvious for some of you but I am stumped with the behavior I'm getting from this rather simple code:

public static void Main(string[] args)
{
    int? n = 1;
    int i = 1;
    n = ++n - --i;
    Console.WriteLine("Without Nullable<int> n = {0}", n); //outputs n = 2

    n = 1;
    i = 1;
    n = ++n - new Nullable<int>(--i);
    Console.WriteLine("With Nullable<int> n = {0}", n); //outputs n = 3
    Console.ReadKey();
}

I exepcted both outputs to be the same and equal to 2 but strangely enough they aren't. Can someone explain why?

EDIT: Although the code to generate this "weird" behavior is admittedly contrived, it does look like a bug in the C# compiler though seemingly unimportant and the reason seems to be the inlined new as James pointed out initially. But the behavior is not limited to operations. Method calls behave exactly the same way, that is, they are called twice when they should only be called once.

Consider the following repro:

public static void Main()
    {
        int? n = 1;
        int i = 1;
        n = n - new Nullable<int>(sideEffect(ref i));
        Console.WriteLine("With Nullable<int> n = {0}", n);
        Console.ReadKey();
    }

    private static int sideEffect(ref int i)
    {
        Console.WriteLine("sideEffect({0}) called", i);
        return --i;
    }

Sure enough, output is 2 when it should be 1 and "sideEffect(i) called" is printed out twice.

like image 222
InBetween Avatar asked May 29 '13 16:05

InBetween


2 Answers

EDIT: This has been confirmed as a bug in the compiler by the team. It is fixed in Roslyn. As a workaround, use a cast (int?)(--i) to stop the bug appearing, or don't explicitly cast it to a Nullable<int> in the first place.

The first code block generates the following in reflector:

int? nullable3;
int? nullable = 1;
int num = 1;
int? nullable2 = nullable;
nullable2 = nullable = nullable2.HasValue
    ? new int?(nullable2.GetValueOrDefault() + 1)    
   : ((int?) (nullable3 = null));
int num2 = --num;
nullable = nullable2.HasValue
    ? new int?(nullable2.GetValueOrDefault() - num2)
    : ((int?) (nullable3 = null));
Console.WriteLine("Without Nullable<int> n = {0}", nullable);

The second the following:

nullable = 1;
num = 1;
nullable2 = nullable;
nullable2 = nullable = nullable2.HasValue
    ? new int?(nullable2.GetValueOrDefault() + 1)
    : ((int?) (nullable3 = null));
num2 = --num;
nullable = nullable2.HasValue
    ? new int?(nullable2.GetValueOrDefault() - --num)
    : null;
Console.WriteLine("With Nullable<int> n = {0}", nullable);

They're more or less the same, up to the assignment to nullable. It's running --num twice, causing it to run 2 - -1, resulting in 3.

It also does the same with expressions like i = ~i, but not with method call expressions...

like image 130
thecoop Avatar answered Nov 09 '22 03:11

thecoop


This is quite an interesting problem, from what I can see the compiler appears to evaluate the --/++ statements more than once. For example, the following:

n = ++n - new Nullable<int>(i++)

results in n becoming 0 (which you would expect) but i is now 3 (which you would expect to be 2). However, if I do

n = ++n - new Nullable<int>(i);

Then I get the expected result (n = 1 and i = 1)

I can only assume this is somehow related to the new Nullable call being in-line. I don't really see this as being much of an issue as this probably wouldn't be considered your everyday sort of code, however, in my opinion it does appear to be a bug with the compiler.

like image 4
James Avatar answered Nov 09 '22 03:11

James