Is it guaranteed that outer checked/unchecked context influences the behavior of lambdas created inside it?

139 Views Asked by At

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])?

3

There are 3 best solutions below

1
InBetween On BEST ANSWER

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.

2
Damien_The_Unbeliever On

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.

0
supercat On

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.