Reputation: 21
I'm implementing a C# variant of Haskell's Maybe and came across a weird issue where null
and default
has different implication on the value returned from an implicit
conversion.
public class TestClass
{
public void Test()
{
Maybe<string> valueDefault = default;
Maybe<string> valueNull = null;
Maybe<string> valueFoobar = "foobar";
Console.WriteLine($"Default: (Some {valueDefault.Some}, None {valueDefault.None}");
Console.WriteLine($"Null: (Some {valueNull.Some}, None {valueNull.None}");
Console.WriteLine($"Foobar: (Some {valueFoobar.Some}, None {valueFoobar.None}");
}
}
public struct Maybe<T>
{
public T Some { get; private set; }
public bool None { get; private set; }
public static implicit operator Maybe<T>(T value)
{
return new Maybe<T>() {
Some = value,
None = value == null
};
}
}
The output being:
Default: (Some , None False)
Null: (Some , None True)
Foobar: (Some foobar, None False)
I was expecting both valueDefault
and valueNull
to be equal. But seems that null
is converted while default
isn't. I fixed the issue by replacing None with HasSome with a reversed boolean condition, but still the question remains.
Why is null and default treated differently?
Upvotes: 1
Views: 286
Reputation: 273510
Every type has a default value, including Maybe<T>
. See this page for a list.
Maybe<string> valueDefault = default;
will assign the default value of Maybe<string>
to valueDefault
. What's the default value of Maybe<string>
? According to that page, since Maybe<string>
is a struct, its default value is:
The value produced by setting all value-type fields to their default values and all reference-type fields to null.
So it's an instance of Maybe<string>
with Some
being null
and None
being false
. false
is the default value of bool
.
The compiler doesn't try to use the default value of string
, since that requires a further conversion to Maybe<string>
. If it can just use the default value of Maybe<string>
, why go the extra trouble, right?
You can force it to though:
Maybe<string> valueDefault = default(string);
null
, on the other hand, gets converted to Maybe<string>
because null
is not a valid value of Maybe<string>
(structs can't be null!), so the compiler deduces that you must mean null as string
, and does the implicit conversion.
You might know this already, but you seem to be reinventing Nullable<T>
Upvotes: 3
Reputation: 11163
default
always fills the memory of the struct with zero bytes. null
is not a valid value for a value type, so the compiler discovers the implicit (Maybe<string>)(string)null
cast.
Perhaps you could replace with;
public struct Maybe<T>
{
public T Some { get; private set; }
public bool None => Some == null;
...
Upvotes: -1