Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Generic type and ?: operator doesn't work

Can anyone explain why this code fails when using ? operator but works with if statement?

The code below compiles without errors. It throws an exception when I run it.

    var myClass = new MyClass<string>();

    string tString;
    //this works fine
    if (myClass.HasValue)
    {
        tString = myClass;
    }
    else
    {
        tString = null;
    }
    //this throws Object reference not set to an instance of an object.
    tString = myClass.HasValue ? myClass : null;

    class MyClass<T>
    {
        private T value;
        public T Value
        {
            get
            {
                if(value == null)
                {
                    throw new Exception("Value cannot be null");
                }

                return value;
            }
            set { this.value = value; }
        }
        public static implicit operator T(MyClass<T> x)
        {
            return x.value;
        }
        public bool HasValue
        {
            get { return value != null; }
        }
    }
like image 697
cinek Avatar asked Dec 17 '22 18:12

cinek


1 Answers

tString = myClass.HasValue ? myClass : null;

Here, we've got a ternary. A ternary has a type, which is determined from the two arguments. The compiler looks at myClass and null, sees that they're compatible (they can both be converted to a MyClass<string>), and determines that the type of the ternary is MyClass<string>.

If myClass.HasValue is false, then we hit the null branch of the ternary. We then get ourselves a MyClass<string> instance which is null. We then need to convert that to a string: to do this, the compiler calls your implicit operator but passes in null. That causes your NullReferenceException because you access x.value but x is null.

This doesn't happen with the if/else because we never construct a MyClass<string> which is null. Instead, we assign null directly to the string.

This simpler example causes the same exception for the same reason:

MyClass<string> myClass = null;
string s = myClass;

You can also see this by forcing the type of the ternary to be string rather than MyClass<string>, by doing:

tString = myClass.HasValue ? myClass : (string)null;

or

tString = myClass.HasValue ? (string)myClass : null;

In this case the exception doesn't occur.


The OP commented that this doesn't happen for an explicit operator. This is wrong: it does.

tString = (string)(myClass.HasValue ? myClass : null);

This throws the same exception, for the same reason.

If instead you do:

tString = myClass.HasValue ? (string)myClass : null;

Then you're falling into the same case as I described earlier, because you never create a MyClass<string> which is null, and so you never try and convert this null MyClass<string> into a string.

like image 62
canton7 Avatar answered Dec 20 '22 06:12

canton7