A dynamic definite assignment puzzle

I’ve often noted that “dynamic” in C# is just “object” with a funny hat on, but there are some subtleties to that. Here’s a little puzzle; see if you can figure it out. I’ll post the answer next time. Suppose we have this trivial little program:

class Program
{
  static void Main()
  {
    object obj = GetObject();
    string str;
    if (obj != null && GetString(out str))
      System.Console.WriteLine(obj + str);
  }
  static object GetObject() => “hello”
  static bool GetString(out string str)
  {
    str = “goodbye”;
    return true;
  }
}

There’s no problem here. C# knows that Main‘s str is definitely assigned on every code path that reads it, because we do not enter the consequence of the if statement unless GetString returned normally, and methods that take out parameters are required to write to the parameter before they return normally.

Now make a small change:

    dynamic obj = GetObject();

C# now gives error CS0165: Use of unassigned local variable ‘str’

Is the C# compiler wrong to give this error? Why or why not?

Next time on FAIC: the solution to the puzzle.

27 thoughts on “A dynamic definite assignment puzzle

  1. I remember this from Stack Overflow! (The line “we are not sensible people today” made my day.)

    Anyway, I also remember the answer, so I’ll keep it short: the compiler is of course correct, and I’ll leave you to explain why in your next post.

  2. Here’s my guess: If obj is a Foo. And there is an operator overload on !=(Foo, object) that returns a Bar. And there is an operator overload on &(Bar, bool), and true(Bar) and false(Bar), then the && operator could return true in short-circuit manner (without evaluating GetString()). So the compiler can’t rely on the && operator to work normally in the presence of dynamic.

    • I think I vaguely remember this having to do with operator true/false, which are very strange when you don’t know they exist. Though, it still feels like a violation of logic for && to be able to evaluate to true without evaluating both of its inputs.

      I also wonder why this couldn’t be a problem for any type that might have overloaded these.

      • When the type of the variable isn’t `dynamic`, the object’s type does not affect operator overloads – only the type of the variable does. So if we had `Foo obj = …;`, the compiler would just check whether `Foo` overloads these operators. Since it’s `object obj`, the compiler knows there are no such overloads.

      • Given the specific program in question, it cannot be a Foo, and “Foo” isn’t even a thing in the context of this program. However, the compiler has to operate by the rules of the language, which means that any function calls performed with a variable of type “dynamic” are not bound until run time, so it cannot make any assumptions about what the variable contains. In general, the kinds of analysis that are simple for a programmer to do looking at a simple program are not available to a compiler that is intended to operate on arbitrary legal programs of any length and complexity and intended to produce consistent reliable code prior to the heat-death of the universe.

        • So if I get this right, because obj is declared “dynamic”, the compiler only looks at the signature of the function, not the implementation, so it cannot reason anything from the actual implementation of GetObject() even if it has access to it. But it can do so if obj was declared as “object”. Is that correct?

          • No, the implementation of a called function is never considered in the definite assignment checking that the compiler performs. Eric has said that he will explain this topic next time, and his explanation are reliably lucid, so we have that to look forward to. But a short—and perhaps opaque—explanation is that when the variable is declared to be of type object, the compiler will always bind the operator calls based on the fact that the variable is an object, regardless of the type of object that the variable references (if any). When the variable is declared to be of type dynamic, then no binding is done at compile time for the operator calls performed with that variable, and thus no assumptions can be made about the operators that are called. The overloads of the operators will be selected when the referenced object’s type is known at runtime.

          • No. Remember, the question is about how the “if” statement is analyzed. That statement is analyzed based solely on the compile-time type of variable “obj”. The only relevant fact about GetObject is whether or not it returns something that can be assigned to variable obj.

            The question is: if we change the compile-time type of obj from “object” to “dynamic”, why does the analysis of the definite assignment of “str” change from “definitely assigned” to “not definitely assigned”?

            Put another way: we could replace “GetObject()” with “null” and the compiler’s behaviour would not change. “GetObject()” has nothing to do with the analysis.

  3. I say that it’s wrong but it’s right if I understand how the compiler works it will completely ignore the expression that is using dynamic and read the next line. So it’s as if that if statement isn’t even there. I’ve ran into this a few time were, in this instance I would have to set str equal to null explicitly. The reason that I say it is wrong is that the null check is still there so just as if when it’s an object it should know only if dynamic is null that GetString would be call. But if it ignores the expression shouldn’t shouldn’t it also consider everything in the condition block as well? It’s almost as if it resolved the if statement as always true

    • It does assume that the if statement could be always true, but do you see why the compiler is correct to do so? Someone else has already posted the solution in a comment, so don’t read the comments if you want to figure it out for yourself.

  4. (this is my (wrong) guess which I already tried posting but seems to be in limbo or something. hopefully I’m not duplicating my post.)

    It’s because the result of the comparison (obj != null) is itself dynamic, and I guess the compiler can’t tell what the internal value of the dynamic boolean is or isn’t. This problem doesn’t seem to occur with pattern matching (!(obj is null)).

    Still though, it “should” be able to figure out that it either will be true or false, and both of those results are ok for the usage of str, but it doesn’t get that far. So I think the compiler is incorrect. It could/should do better. (As it does with pattern matching.)

  5. This brings up an interesting question. Is this language feature really all that useful or was it included in the language to make new developers coming from other languages “feel at home”? Most of the time I see it in code is in these kind “compiler must be wrong” quizzes. I am by no means an expert developer but I still have to come up with a problem where this is needed or review code where this was used.

    • The scenario for dynamic that motivated its design was not at all “making new developers feel at home”. There were two main scenarios that motivated the feature:

      (1) Developers who are using APIs designed for dynamic languages from C#, a statically typed language. For example, maybe you’ve got a library of functions designed to work with JavaScript in the HTML DOM, or a library of Python functions, or some such thing. Such a library might reasonably have extremely weak typing, it might have expando objects whose members vary dynamically, and so on. It has historically been very difficult to work with such libraries in C#. We would like .NET to be a multi-language, multi-paradigm platform, so having a dynamic subset in C# reduces friction.

      (2) This is in a sense a special case of (1), but the scenario is large enough and complex enough to warrant being called out: developers who are using “legacy” APIs designed for OLE Automation from their C# programs. In particular, OLEAut libraries designed for use from VB6. In particular, the Word and Excel object models. I worked on Visual Studio Tools For Office before I joined the C# team, and let me tell you, it is extremely painful to call a method in the Word PIA from C# without dynamic.

      In short, the purpose is to extend the reach of dynamic and legacy libraries into C#. The *purpose* of the feature was *not* to create new kinds of dynamic dispatch in C#; dynamic dispatch was *necessary* to make it work in a principled manner, but it was not *motivating*. Meeting the needs of developers who must use legacy or dynamic libraries in their line-of-business programming was the motivating scenario.

      If you are not a developer who uses legacy or dynamic libraries in your line-of-business programming, then this feature will likely feel unnecessary to you, because it is unnecessary.

      • Sorry Eric, I should have been clearer. I was referring to the ability to overload the logical operators. I’m aware of the uses of `dynamic` and have used it often, mostly interoperating with Excel.

        • Ah, yes, well, the question of whether operator overloading is a good feature is a bit of a peeve of mine. I really, really like being able to define your own BigRational or Complex or Quaternion types and then have + – * / operators work as you expect. And I really, really hate it when people make “cute” operator overloads, like Customer + Product = PurchaseOrder or some such nonsense.

          I think the tendency to abuse operator overloads may originate from the bad decision to make <> into streaming operators in C++ for no reason other than they look like arrows. In retrospect, C++ missed a huge opportunity; they could have invented an entirely new operator that means “bind this value onto the end of this workflow” and then all sorts of possibilities open up.

          If you are going to overload the logical operators, I think the mechanism that C# came up with is really good, but it is somewhat confusing. Similarly, I think the mechanism that they came up with to overload ++ was also really good, but it is confusing to C++ developers who are used to the weird way C++ does it.

          • > could have invented an entirely new operator that means “bind this value onto the end of this workflow”

            That operator would have to be called >>= and that would be an abuse of operator “bit shift right with assignment” :]

          • Frankly, and I know I’m repeating myself,, I find the ability to overload the logical operators a misfeature, I’m not sure what problem this feature is meant to solve. and, because its so seldomly used (or never), its behavior, when you run into it, is rather unnexpeted.

  6. The compiler is right but the language design is wrong … it should not be possible for && to evaluate to true without evaluating both branches.

    • There are a great many things that are arguably “wrong” about C# design. For example, it is possible that x == y is true, x < y is false, but x <= y is false if x and y are nullable ints, so <= does not mean "x < y || x == y".

      What you're describing is a *desirable property*. The problem is that programming languages have desirable properties which *contradict each other* and therefore the job of the designer is to come up with compromise positions that weigh different properties against each other to achieve a workable solution.

      Thus, just saying "it's wrong" is unhelpful. Propose a better design! How would you have overridden the && operator such that your desired property is maintained? What desirable properties did you have to abandon in order to achieve that goal?

  7. Pingback: Dew Drop - November 15, 2018 (#2845) - Morning Dew

  8. class Program
    {
        static void Main()
        {
            dynamic obj = GetObject();
            string str = " OOPS";
            if (obj != null && GetString(out str))
                System.Console.WriteLine(obj + str);
        }
        static object GetObject() => new EvilBool();
        static bool GetString(out string str)
        {
            str = "goodbye";
            return true;
        }
    }
    
    class EvilBool
    {
        private bool value = true;
        private static bool M(EvilBool b)
        {
            b.value = !b.value;
            return b.value;
        }
    
        public static bool operator true(EvilBool b) => M(b);
        public static bool operator false(EvilBool b) => !M(b);
        public static EvilBool operator !=(EvilBool b, object other) => b;
        public static EvilBool operator ==(EvilBool b, object other) => b;
    }

Leave a reply to admin Cancel reply