Nullable types and assignment operator - c#

I always thought that the nullable types of the .NET framework where nothing but a nice construct that the framework gave us, but wasn't anything new added to the language itself.
That is, until today, for purposes of a demonstration, I tried to create my own Nullable struct.
I get everything I need, except the capability of doing this:
var myInt = new Nullable<int>(1);
myInt = 1;
The problem is the second statement, as I cannot, by any means, overload the assignment operator.
My question is: Is this a special case in the language where the assignment operator has an overload? If not, how would you approach making that previous example work?
Thanks!

The assignment is question is handled using an implicit operator declared as:
public static implicit operator Nullable<T> (T value)
This is handled without any language-specific features.
The main change to the langauge for nullable support is the ability to write this as:
int? myInt = 1;
You could implement this in your type via:
public static implicit operator Nullable<T> (T value)
{
return new Nullable<T>(value);
}
That being sad, there is a lot of features related to Nullable<T> the C# language and CLR does that can't be duplicated in your code.

Ditto on the other answers, but note that Nullable<T> does do some magic as well.
Quoting from that answer by Lippert
C# automatically lifts operators to nullable. There's no way to say
"automatically lift operators to MyNullable". You can get pretty close
by writing your own user-defined operators though.
C# has special rules for null literals -- you can assign them to
nullable variables, and compare them to nullable values, and the
compiler generates special code for them.
The boxing semantics of nullables are deeply weird and baked into the
runtime. There is no way to emulate them.
Nullable semantics for the is, as and coalescing operators are baked
in to the language.
Nullables do not satisfy the struct constraint. There is no way to
emulate that.
And so on.

Not the assignment operator is overloaded, but the class Nullable<T> specifies an implicit conversion operator which is able to convert T into Nullable<T>.
You can easily add this functionality to your class:
class MyInteger {
private int value;
public MyInteger(int value) { this.value = value; }
public static implicit operator MyInteger(int value) {
return new MyInteger(value);
}
}
And then use the operator in the same way:
MyInteger obj = 123;

According to this page Operator overloading
Note that the assignment operator itself (=) cannot be overloaded.
You can define an implicit operator

Related

When to use implicit and explicit operators in C#

Writing implicit and explicit type conversion operators is trivial.
I can find lots of documentation about how to write them, but very little about when, or why to write them.
I've done some investigations into existing implementations; for example, BigInteger from .NET's reference source:
public struct BigInteger : IFormattable, IComparable, IComparable<BigInteger>, IEquatable<BigInteger>
{
public static implicit operator BigInteger(Byte value)
{
return new BigInteger(value);
}
public static explicit operator Byte(BigInteger value)
{
return checked((byte)((int)value));
}
}
Given the excerpt above, what is the rational for using an implicit operator when converting from Byte to BigInteger, but using an explicit operator when converting from BigInteger to Byte?
As I mentioned in my comment, my assumption was that implicit operators should always be considered safe, whereas explicit operators may be safe but in some cases may need to be handled.
I found the following documentation on pre-defined implicit operators:
The pre-defined implicit conversions always succeed and never cause
exceptions to be thrown.
Note: Properly designed user-defined implicit conversions should
exhibit these characteristics as well. end note
In addition, the documentation for explicit conversions does not give the same guarantees:
The explicit conversions that are not implicit conversions are
conversions that cannot be proven always to succeed, conversions that
are known possibly to lose information, and conversions across domains
of types sufficiently different to merit explicit notation.
This clearly backs up my assumption that implicit operators must ALWAYS be safe and never require exception handling, whereas explicit operators can and do throw exceptions, as in your example of the explicit operator that is checked.

Cannot convert null to struct when defining two nullable implicit operators

The following does not compile:
public struct Foo
{
public static implicit operator Foo(string bar)
{
return new Foo();
}
public static implicit operator Foo(long? bar)
{
return new Foo();
}
public static void Test()
{
Foo bar = 0;
Foo bar2 = (long?)null;
Foo bar3 = "";
Foo bar4 = null; // Cannot convert null to 'Foo' because it is a non-nullable value type
}
}
'Foo bar4 = null' fails, presumably because the compiler doesn't know which implicit operator to use, because changing the operator from long? to long causes that line to compile but instead 'Foo bar2 = (long?)null' fails instead (requiring an explicit cast).
My question is; Is there a way to make 'Foo bar4 = null' work as well, or is this just a limitation of the language (that I cannot add a 'null' operator or tell it which one to use for null for instance)?
I realize I could change the struct to a class, but I don't want it to be null, I want to be able to have null create an instance of it.
EDIT: I should add that I understand there are a lot of ways to get around this, but since '= null' (doing 'new Foo()' essentially) works with only one of the implicit operators, I'm just wondering if it's possible to have it still work with both of them there (I feel like there should be a way in the language to do this - either now or in the future, no?).
My question is; Is there a way to make 'Foo bar4 = null' work as well, or is this just a limitation of the language (that I cannot add a 'null' operator or tell it which one to use for null for instance)?
Based on this question and your edit, you are basically asking
Why does Foo bar4 = null compile when it does not if I add another implicit cast operator?
The answer is simple. null without any context is typeless and so the compiler does not know which operator to use. Why? Well the overload resolution algorithm underpinning the language does not examine the type of the thing you are trying to assign null to, so it doesn't know which operator you intended.
You could argue that a future language spec could be modified to do this extra analysis, but the team probably considers that not worth the effort or it would be a breaking change.
The best that you can do is to avoid the casting in this case. You can, depending on your C# level, do either of these:
Foo bar4 = default;
Foo bar4 = default(Foo);
which results in a useable Foo. The two default expressions and new Foo() are all equivalent. They all result in a struct with all its fields zeroed out.
For more information you can see the default value expressions section of the C# programming guide.
And finally, while you can get away with a single implicit cast operator that casts a null to a struct, it doesn't mean you should. Someone reading that code not knowing about the cast would probably be questioning their sanity for a few minutes. It's best not to stray from idiomatic code if you can.
Your struct isn´t nullable because you introduce a cast that enables to cast a null-value to that type. A cast in itself is just a member that doesn´t change the types semantics.
In fact a struct by itself is never null, however references to it may be, but only when they are of type Nullable<Foo>. So bar4 needs to be of type Foo?, which is the same as Nullable<Foo>.
If I'm not mistaken, a struct can never be assigned a value of null because it's a value-type, similar to int, bool, and DateTime.
You could, however, use a Nullable<T> like so:
Foo? bar4 = null;
or
Nullable<Foo> bar4 = null;
Make sure you treat it like any other Nullable<T> and check .HasValue before referencing bar4.Value to avoid the wonderful NullReferenceException.

Why is Nullable<T> nullable? Why it cannot be reproduced?

When I write
Nullable<Nullable<DateTime>> test = null;
I get a compilation error:
The type 'System.Datetime?' must be a non-nullable value type in order to use it as a paramreter 'T' in the generic type or method 'System.Nullable<T>'
But Nullable<T> is a struct so it's supposed to be non-nullable.
So I tried to create this struct:
public struct Foo<T> where T : struct
{
private T value;
public Foo(T value)
{
this.value = value;
}
public static explicit operator Foo<T>(T? value)
{
return new Foo<T>(value.Value);
}
public static implicit operator T?(Foo<T> value)
{
return new Nullable<T>(value.value);
}
}
Now when I write
Nullable<Foo<DateTime>> test1 = null;
Foo<Nullable<DateTime>> test2 = null;
Foo<DateTime> test3 = null;
The first line is ok but for the second and third lines I get the two following compilation error:
The type 'System.DateTime?' must be a non-nullable value type in order to use it as a parameter 'T' in the generic type or method 'MyProject.Foo<T>' (second line only)
and
Cannot convert null to 'MyProject.Foo<System.DateTime?> because it is a non-nullable value type'
Foo<Nullable<DateTime>> test = new Foo<DateTime?>();
doesn't work neither event if Nullable<DateTime> is a struct.
Conceptually, I can understand why Nullable<T> is nullable, it avoids having stuffs like DateTime?????????? however I can still have List<List<List<List<List<DateTime>>>>>...
So why this limitation and why can't I reproduce this behavior in Foo<T>? Is this limitation enforced by the compiler or is it intrinsic in Nullable<T> code?
I read this question but it just says that it is not possible none of the answers say fundamentally why it's not possible.
But Nullable is a struct so it's supposed to be non-nullable.
Nullable<T> is indeed a struct, but the precise meaning of the generic struct constraint as stated in the docs is:
The type argument must be a value type. Any value type except Nullable can be specified. See Using Nullable Types (C# Programming Guide) for more information.
For the same reason, your line
Foo<Nullable<DateTime>> test2 = null;
results in the compiler error you are seeing, because your generic struct constraint restricts your generic T argument in a way so Nullable<DateTime> must not be specified as an actual argument.
A rationale for this may have been to make calls such as
Nullable<Nullable<DateTime>> test = null;
less ambiguous: Does that mean you want to set test.HasValue to false, or do you actually want to set test.HasValue to true and test.Value.HasValue to false? With the given restriction to non-nullable type arguments, this confusion does not occur.
Lastly, the null assignment works with Nullable<T> because - as implied by the selected answers and their comments to this SO question and this SO question - the Nullable<T> type is supported by some compiler magic.
The error is saying that the type-parameter of Nullable should be not-nullable.
What you're doing, is creating a Nullable type which has a nullable type-parameter, which is not allowed:
Nullable<Nullable<DateTime>>
is the same as
Nullable<DateTime?>
Which is quite pointless. Why do you want to have a nullable type for a type that is already nullable ?
Nullable is just a type that has been introduced in .NET 2.0 so that you are able to use ' nullable value types'. For instance, if you have a method wich has a datetime-parameter that is optional; instead of passing a 'magic value' like DateTime.MinValue, you can now pass null to that method if you do not want to use that parameter.
In generic classes where T: struct means that type T cannot be null.
However Nullable types are designed to add nullability to structs. Technically they are structs, but they behave like they may contain null value. Because of this ambiguity the use of nullables is not allowed with where T: struct constraint - see Constraints on Type Parameters
Nullable types are not just generic structs with special C# compiler support. Nullable types are supported by CLR itself (see CLR via C# by Jeffrey Richter), and looks like this special CLR support makes them non-recursive.
CLR supports special boxing/unboxing rules int? i = 1; object o = i will put int value into variable o and not Nullable<int> value. In case of multiple nullables - should o = (int??)1; contain int or int? value?
CLR has special support for calling GetType and interface members - it calls methods of underlying type. This actually leads to situation when Nullable.GetType() throws a NullObjectReference exception, when it has NullValueFlag.
As for C#, there are a lot of features in C# that are hard-coded for nullable types.
Based on this article Nullable Types (C# Programming Guide) the primary goal of introducing nullable types is to add null support for the types that do not support nulls. Logically, since DateTime? already supports nulls it shouldn't be allowed to be "more" nullable.
This document also plainly states that
Nested nullable types are not allowed. The following line will not compile: Nullable<Nullable<int>> n;
Special C# features of nullable types:
C# has special ?? operator. Should (int???)null ?? (int)1 resolve to (int??)1 or to (int)1 value?
Nullables have special System.Nullable.GetValueOrDefault property. What should it return for nested nullables?
Special processing for ? == null and ? != null operators. If the Nullable<Nullable<T>> contains Nullable<T> value, but this value is null, what should HasValue property return? What should be the result of comparison with null?
Special implicit conversions. Should int?? i = 10 be implicitly convertible?
Explicit conversions. Should be int i = (int??)10; supported?
Special support for bool? type Using nullable types. E.g. (bool?)null | (bool?)true == true.
So, should CLR support recursive GetType() call? Should it remove Nullable wrappers when boxing value? If it should do for one-level values, why don't for all other levels as well? Too many options to consider, too many recursive processing.
The easiest solution is to make Nullable<Nullable<T>> non-compilable.

Why can the as operator be used with Nullable<T>?

According to the documentation of the as operator, as "is used to perform certain types of conversions between compatible reference types". Since Nullable is actually a value type, I would expect as not to work with it. However, this code compiles and runs:
object o = 7;
int i = o as int? ?? -1;
Console.WriteLine(i); // output: 7
Is this correct behavior? Is the documentation for as wrong? Am I missing something?
Is this correct behavior?
Yes.
Is the documentation for as wrong?
Yes. I have informed the documentation manager. Thanks for bringing this to my attention, and apologies for the error. Obviously no one remembered to update this page when nullable types were added to the language in C# 2.0.
Am I missing something?
You might consider reading the actual C# specification rather than the MSDN documentation; it is more definitive.
I read:
Note that the as operator only performs reference conversions and boxing conversions. The as operator cannot perform other conversions, such as user-defined conversions, which should instead be performed by using cast expressions.
And boxing conversions.....
Just a guess, but I'd say it boxes o as an integer and then converts it to a nullable.
From the documentation about the as keyword:
It is equivalent to the following expression except that expression is evaluated only one time.
expression is type ? (type)expression : (type)null
The reference for is use also states it works with reference types, however, you can also do stuff like this:
int? temp = null;
if (temp is int?)
{
// Do something
}
I'm guessing it is just an inaccuracy in the reference documentation in that the type must be nullable (ie a nullable type or a reference type) instead of just a reference type.
Apparently the MSDN documentation on the as operator needs to be updated.
object o = 7;
int i = o as **int** ?? -1;
Console.WriteLine(i);
If you try the following code where we use the as operator with the value type int, you get the appropriate compiler error message that
The as operator must be used with a reference type or nullable type ('int' is a non-nullable value type)
There is an update though on the link in Community Content section that quotes:
The as operator must be used with a reference type or nullable type.
You're applying the 'as' to Object, which is a reference type. It could be null, in which case the CLR has special support for unboxing the reference 'null' to a nullable value type. This special unboxing is not supported for any other value type, so, even though Nullable is a value type, it does have certain special privledges.

Where are operators defined (in C#)?

Just wondering where the rules for operators in C# are actually defined.
E.g. where can I see the code which says that == checks the references of two objects?
I can see the operator overloads in e.g. the String class but now i'm interested in seeing the 'base' case. Is it just something that the compiler explicitly knows what to do with and therefore there is no code which we can view using tools such as Reflector.
You can't see it in code (except maybe in the SSCLI, I haven't checked).
You'll need to look at the C# language specification. For example:
7.10.6 Reference type equality operators
The predefined reference type equality
operators are:
bool operator ==(object x, object y);
bool operator !=(object x, object y);
The operators return the result of
comparing the two references for
equality or non-equality.
Since the predefined reference type
equality operators accept operands of
type object, they apply to all types
that do not declare applicable
operator == and operator !=
members. Conversely, any applicable
user-defined equality operators
effectively hide the predefined
reference type equality operators.
The == operator compiles down to a call to the ceq IL instruction.

Categories

Resources