It's worth poing out that out is a rather redundant keyword these days given the existence of ValueTuple (why return a bool and an out T when you can just return (bool, T)?), but I'll continue to use out in this post because the syntax is considerably cleaner.

Here's a bit of code.

Value value;
try
{
  value = dictionary[key];
}
catch (KeyNotFoundException ex)
{
  return null;
}

return DoThingWithValue(value);

Index access can be thought of as a function, and here we have a function that will, depending on the key you try, either give you back a value or throw an exception. Most readers will probably agree that catching an exception here is probably overkill (and my personal view is that we should avoid throwing an exception because it's hardly exceptional to find a key missing in a dictionary).

Let's also quickly establish whether this is an input or output problem. A particular input might throw an exception, which suggests we need to strengthen the type of our input — except that's not quite true. The result of a dictionary lookup should probably be viewed as dependent on the dictionary itself, and not on the key we try: the dictionary could hold a value for every key possible under the type constraint, but in the case where it doesn't, that's the fault of the dictionary contents and not the key we're trying. That means it's more appropriate to fix this by altering its return type instead of its key type.

C#'s solution to the problem is, at least, interesting. C# wants us to declare a block in which an already typed key value is further typed (only semantically) as 'existing inside the given dictionary' for the duration of the block.

What on Earth does that mean? It means this.

Value value;
if (dictionary.ContainsKey(key))
{
  value = dictionary[key];
}
else
{
  return null;
}

return DoThingWithValue(value);

Inside the if block, we've declared a scope in which we can safely do a lookup on a dictionary, knowing that the call won't fail (ignoring threading concerns). Outside that scope we can't safely do the lookup because it may throw an exception. Readers will probably notice that this 'alternative' code block is effectively equivalent to what's done above; the only difference is that we're now avoiding throwing unnecessary exceptions (...ignoring threading concerns).

Of course, even C# knows this is still hideous, and offers something cleaner.

if (!dictionary.TryGetValue(key, out var value))
{    
    return null;
}

return DoThingWithValue(value);

Blocks of code like these appear thousands of times in code bases I maintain. I hate them with a passion. This reconstruction has done nothing to solve the problems raised above (though I concede we have dealt with those threading concerns) yet developers still like to claim it's the best choice for dictionary lookups.

Syntactic issues include the declaration of a variable inside a function call that's actually declared outside the scope of the if block; said variable's being accessible even when TryGetValue return false (and who knows what value it holds then); the requirement to craft and throw your own exceptions if you can't sensibly default value's value.

The higher level issues with the out-var construct are more subtle. Sure, the construct allows an obvious place for recovery, but the code doesn't require you to handle the error inside the if block, so the barely conscious developer might just use the block for logging, and then carry on with whatever value they've pulled out, even if the function returned false (with no compiler error in sight). Sure, the block is nicely self-contained and encourages you to make use of value in the space below the if, but if you have a number of dictionary lookups to do where the handling code is equivalent, you're duplicating a lot of code.

These issues alone are enough for me to conclude that I never want to use the out var construct in code again, ever.


A colleague who worked at Google in a previous life (developers, in my experience, seem to have a habit of relegating their employment histories to 'past lives') said that a running joke amongst developers there was the decision by Google of all companies to make a language whose name made it hard to find help for online. One must substitue 'golang' for 'go' in a search for 'dictionary lookup go' in order to find the following example code:

if val, ok := dict[key]; ok {
    //do something here
}

This piece of code is surprisingly strongly typed. val exists only in the scope of the block where ok is true, meaning val is appropriately populated. But that's just luck: I could just as easily have done the dictionary assignment outside the if condition and accessed val even when ok was false.

Go tutorials have the nasty habit of praising Go's apparent clarity when it comes to errors. As part of the return type (and with linters encouraging developers not to ignore error results) code often looks like this:

result, err := foo()
if err != nil {
  // handle
}

// ""happy path""

And tutorials will see this as an example of Go's error handling's working. The most offensive claim is the one that states that developers are now forced to handle errors because they are part of the return type. There is no compiler demand to do anything about it however, and there's no warning if a developer decides to access a variable that shouldn't be set because an error is set instead.

Of course, C# is a capable language, so we can emulate this Go code with either ValueTuples or clean up the syntax further with an out variable.

if (foo(out var result) is var err && err != null)
{
  // handle
}

// ""happy path""

The precise issue is with the fact that tuple return types contain multiple utterly independent results. result and err are free to hold whatever values they like, and it's just convention that states that result is undefined iff err is defined. These issues are less obvious when seen in the target language because languages have conventions that encourage developers to do the right thing (and those conventions have definitely served Go well), but when the same behaviour is seen in C# it exposes somewhat how dependent we are on convention to not make mistakes.

Go's curious approach to errors is most apparent when dealing with repetitive dangerous actions.

scanner := bufio.NewScanner(input)
for scanner.Scan() {
    token := scanner.Text()
    // process token
}
if err := scanner.Err(); err != nil {
    // process the error
}

This terrifies me. The convention is very clear (call scanner.Err()) but that's all that stands between the barely conscious developer and a very silent failure of the call to scanner.Text().

Personally, I would rather those conventions exist as compile requirements, so that I never even have the chance to access a variable in an inappropriate context.