1

Assuming default arithmetic overflow (not)checking, the following code

Action<Int32[]> action;

checked
{
    action = array =>
        Console.WriteLine(array[0] + array[1]);
}

var items = new[]
{
    Int32.MaxValue,
    Int32.MaxValue
};
action(items);

will result in

System.OverflowException: Arithmetic operation resulted in an overflow..

If we set project settings to /checked, and replace checked { with unchecked {, the exception won't be thrown.

So, can we rely on this behavior, or is it safer to array => unchecked (array[0] + array[1])?

Community
  • 1
  • 1
Eugene Podskal
  • 10,270
  • 5
  • 31
  • 53
  • You want a warranty that the C# compiler does not have any bugs?? The github site lists *ten thousand* issues, a third of which are still open. Just happened to run into [this one](https://github.com/dotnet/roslyn/issues/18446) when I looked just now :) – Hans Passant Apr 05 '17 at 08:29
  • @HansPassant As far as I can see there is no bug here - everything works fine and quite logically. But whether one can rely on this behavior as a standard is what troubles me. Specification mentioned in [@InBeetween's answer](http://stackoverflow.com/a/43225470/3745022) is a bit vague and whether lambdas are guaranteed to be affected or not is still slightly doubtful. – Eugene Podskal Apr 05 '17 at 10:57
  • Come back when it doesn't work and you have a concrete problem to show us, SO is always around. – Hans Passant Apr 05 '17 at 11:01
  • @HansPassant I do not exactly agree that this question is off-topic. It is just more of a language-lawyer question, than a specific "my code doesn't work" question. – Eugene Podskal Apr 05 '17 at 11:04

3 Answers3

1

In the last officially published C# spec, it says the following:

8.11 (...) The checked statement causes all expressions in the block to be evaluated in a checked context, and the unchecked statement causes all expressions in the block to be evaluated in an unchecked context. (...)

I'd say pretty confidently that action will always be evaluated in a checked/ unchecked context which is the current behavior you are seeing and I wouldn't expect this to change in the future.

To expand a little more on my answer, if you check the compiled code, you'll see that Console.WriteLine(array[0] + array[1]) inside the checked statement is actually compiled as the equivalent of Console.WriteLine(checked (array[0] + array[1])) so there is really no need to do it yourself, the compiler will do it anyways.

InBetween
  • 32,319
  • 3
  • 50
  • 90
  • Well, it seems that we can take `8.11` literally, so with lambda's body being in checked context it should be affected by it. Thanks, though I would probably continue to apply `(un)checked` inside the lambda - it should be reliable for sure and it is more localized/shorter. – Eugene Podskal Apr 05 '17 at 11:01
0

Bear in mind that checked and unchecked change the instructions that the compiler emits. E.g. there are two (actually more) variants of an add instruction in IL, where one variant ignores overflow and the other checks for overflow.

Since it changes the emitted IL, it has to apply.


E.g. this code:

    static void Main(string[] args)
    {
        int i = 0;
        int j = 1;
        int k;
        checked
        {
            k = i + j;
        }
        unchecked
        {
            k = i + j;
        }
        Console.ReadLine();
    }

Emits this IL:

.method private hidebysig static void Main(string[] args) cil managed
{
    .entrypoint
    .maxstack 2
    .locals init (
        [0] int32 num,
        [1] int32 num2,
        [2] int32 num3)
    L_0000: nop 
    L_0001: ldc.i4.0 
    L_0002: stloc.0 
    L_0003: ldc.i4.1 
    L_0004: stloc.1 
    L_0005: nop 
    L_0006: ldloc.0 
    L_0007: ldloc.1 

    L_0008: add.ovf 

    L_0009: stloc.2 
    L_000a: nop 
    L_000b: nop 
    L_000c: ldloc.0 
    L_000d: ldloc.1 

    L_000e: add 

    L_000f: stloc.2 
    L_0010: nop 
    L_0011: call string [mscorlib]System.Console::ReadLine()
    L_0016: pop 
    L_0017: ret 
}

Where you can see the two different instructions emitted.

Damien_The_Unbeliever
  • 234,701
  • 27
  • 340
  • 448
  • Yes, I know how checked/unchecked works. I just want to know whether it is guaranteed to affect in the same way methods generated for lambdas in (un)checked context. – Eugene Podskal Apr 05 '17 at 08:22
  • @EugenePodskal - then I've misunderstood the confusion. I could understand the confusion if someone was thinking that `checked` was, say, like a `try`/`catch` that hid overflow exceptions; that is it was implemented at runtime and thus mattered that the code in the lambda wasn't executing in the same *temporal scope* as the `checked` block. – Damien_The_Unbeliever Apr 05 '17 at 08:25
0

It is perhaps best to think of C# as having two sets of integer operators, one of which performs overflow checking and one of which doesn't; whether the "+" operator binds to the "overflow-checked addition" operator or the "wrapping addition" addition operator is controlled by whether it appears within a checked or unchecked context. The only way in which the operators affect program execution is by selecting which operators get bound to tokens like "+"; such binding takes place when the code is examined by the compiler--not when it is run.

supercat
  • 77,689
  • 9
  • 166
  • 211