Search code examples
c#linqtryparse

LINQ, output arguments, and 'Use of Unassigned Local Variable' error


I have some code similar to the following.

class MyClass<TEnum> where TEnum : struct
{
    public IEnumerable<TEnum> Roles { get; protected set; }

    public MyClass()
    {
        IEnumerable<string> roles = ... ;

        TEnum value;
        Roles = from r in roles
                where Enum.TryParse(r, out value)
                select value;   // <---- ERROR HERE!
    }
}

However, on the line indicated above, I get the error:

Use of unassigned local variable 'value'

Seems to me that value will always be initialized in this case since it is an out parameter to Enum.TryParse.

Is this a bug with the C# compiler?


Solution

  • TL;DR: Error says the variable is (provably) unassigned -- FALSE. Reality, variable is not provably assigned (using the proof theorems available to the compiler).


    LINQ is designed with the assumption of pure functions... those that return outputs based on the input and have no side effects.

    Once this is rewritten, it will be:

    roles.Where(r => Enum.TryParse(r, out value)).Select(r => value);
    

    and rewritten again to

    Enumerable.Select(Enumerable.Where(roles, r => Enum.TryParse(r, out value)), r => value);
    

    These LINQ functions will call the filter lambda before any calls to the selection lambda, but the compiler can't know that (at least, not without either special-casing or cross-module dataflow analysis). More problematically, if a different implementation of Where were chosen by overload resolution, then possibly the lambda with TryParse would NOT be called.

    The compiler's rules for definite assignment are very simplistic, and err on the side of safety.

    Here is another example:

    bool flag = Blah();
    int value;
    if (flag) value = 5;
    return flag? value: -1;
    

    It is impossible for an uninitialized value to be used, yet the language rules for dataflow analysis lead to a compile error that value is used without being "definitely assigned".

    The compiler error is worded poorly, however. Not being "definitely assigned" is not the same as definitely being "unassigned", as the error purports.