Why is the order of evaluation different for Collections than for other types using the conditional operator

StackOverflow https://stackoverflow.com/questions/20729992

Question

While trying to implement a GetHashCode override similar to Jon's Skeet's suggestion in What is the best algorithm for an overridden System.Object.GetHashCode? I noticed some odd behavior in the order of evaluation that is causing a syntax error when doing a null check on a collection property in the class using the conditional operator.

Consider the following:

public class Foo
{

    public String Name { get; private set; }
    public List<String> Bar { get; private set; }

    public override Int32 GetHashCode()
    {
        unchecked
        {
            int hash = 17;
            hash = hash * 23 + this.Name == null ? 0 : this.Name.GetHashCode();
            hash = hash * 23 + this.Bar == null ? 0 : this.Bar.GetHashCode();
            return hash;
        }
    }
}

This code will not compile as you get a syntax error on hash = hash * 23 + this.Bar == null ? 0 : this.Bar.GetHashCode();, specifically pointing too the hash * 23 + this.Bar segment.

The error is

Operator '+' cannot be applied to operands of type 'int' and 'System.Collections.Generic.List'

You do not get the same error on hash = hash * 23 + this.Name == null ? 0 : this.Name.GetHashCode(); although the only difference is Name is a string and Bar is a List<>.

Wrapping the entire conditional operation in a set of parenthesis does remove the error, but that still doesn't explain why the collection property is being treated differently than the string property.

Is there a reason that I'm not aware of that cause the operation to be evaluated differently for different types?

Was it helpful?

Solution

The difference is that hash * 23 + this.Name == null is a valid expression, whereas hash * 23 + this.Bar == null is not. + is used both for arithmetic and for string concatenation, but not for adding elements to a list. In both cases, the whole of hash * 23 + this.... == null is taken as the condition of the ?: operator.

OTHER TIPS

Take a look at this function:

void Crash() {
    int hash = 123;
    int crash = hash * 23 + this.Name == null ? 0 : this.Name.GetHashCode();
}

It is similar to yours - I removed the part that does not compile.

This function will crash when Name is set to null (demo), in the same way that your GetHashCode() would. This appears odd, because at the first glance the check for null-ness is there.

However, it is a wrong thing that's being checked for null: C# compares to null this entire expression:

hash * 23 + this.Name

This is a valid string concatenation.

This provides a clue to solving your "mystery": it isn't the fact that Bar is a collection that leads to a compile error by mistake, but the fact that Name is a string that avoids a compile error by mistake.

Licensed under: CC-BY-SA with attribution
Not affiliated with StackOverflow
scroll top