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.
Related
This question already has an answer here:
Casting an object using the "as" keyword returns null
(1 answer)
Closed 3 years ago.
long? i = 10000;
int? s = i as int?;
Why is it giving me a null in s variable? I know that casting it using int? s = (int?)i; will work fine but why can't I use an as operator?
From MSDN:
The code is equivalent to the following expression except that the expression variable is evaluated only one time.
expression is type ? (type)expression : (type)null
Since i is int? is false the as keyword will return (int?)null.
The types aren't equivalent and you get null, that is just how as works
However, you could just direct Cast
long? i = 10000;
var s = (int?)i;
// or even better make sure its *checked*
var s = checked((int?)i);
Why does this work?
C# Language Specification 11.6.1 Nullable Conversions
...
Evaluation of a nullable conversion based on an underlying conversion
from S to T proceeds as follows:
If the nullable conversion is from S? to T?:
If the source value is null (HasValue property is false), the result is the null value of type T?.
Otherwise, the conversion is evaluated as an unwrapping from S? to S, followed by the underlying conversion from S to T, followed by a
wrapping from T to T?.
...
as does not do this, it checks the run-time types, if they are not equal returns null T?
Additional Resources
checked (C# Reference)
The checked keyword is used to explicitly enable overflow checking for
integral-type arithmetic operations and conversions.
#Update from comments
I got that we can't convert and why we can't do I but why they are
suggesting it is there any scenario where it will be useful i'e some
random image link of Compiler Error CS0077 "The as operator must be used with a reference type or nullable type ('int' is a non-nullable value type)."
The reason is (in your image example) a value type can't be null it could never work with it. The thing you are missing is there are more complex examples of what could happen, you can define implicit operators on custom types, etc.
The compiler cannot implicitly convert 'long' to 'int'
And as per MSDN
The as operator is like a cast operation. However, if the conversion
isn't possible, as returns null instead of raising an exception.
ref: as (C# Reference)
So with your code, conversion is not possible, as is returning Null.
In C# there are 2 ways of casting:
foo as int
(int)foo
Why does the first line not compile and the second does?
Console.Write(49 as char);
Console.Write((char)49);
From MSDN:
You can use the as operator to perform certain types of conversions between compatible reference types or nullable types.
char is neither a reference type nor a nullable type. It can't set the output variable of 49 to null (when the conversion fails) since it isn't nullable. It would work with char? though, although useless in this situation.
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.
I defined the following struct:
public struct Call
{
public SourceFile caller;
public SourceFile callee;
public Call(SourceFile caller, SourceFile callee)
{
this.caller = caller;
this.callee = callee;
}
}
Later, I assign it to the Tag property of another object:
line.Tag = new Call(sf1, sf2);
But when I try to retrieve the Tag property like so,
Call call = line.Tag as Call;
Visual Studio gives the following compile-time error:
The operator as must be used within a
reference type or nullable type
What is the meaning of that? And how can I solve it?
Some of the existing answers aren't quite right. You can't use non-nullable types with as, because the result of as is the null value of the type if the first operand isn't actually of an appropriate type.
However, you can use as with value types... if they're nullable:
int a = 10;
object o = a;
int? x = o as int?; // x is a Nullable<int> with value 10
long? y = o as long?; // y is a Nullable<long> with the null value
So you could use:
Call? call = line.Tag as Call?;
Then you can use it as:
if (call != null)
{
// Do stuff with call.Value
}
Two caveats though:
In my experience this is slower than just using is followed by a cast
You should seriously reconsider your current Call type:
It's exposing public fields, which is generally poor encapsulation
It's a mutable value type, which is almost certainly a mistake
I would strongly suggest you make it a class instead - at which point this problem goes away anyway.
Another thought: if the tag should always be a Call, then it's better to cast it:
Call call = (Call) line.Tag;
That way, if the data doesn't match your expectation (i.e. there's some bug such that the Tag isn't a Call) then you get to find out about it early, rather than after you've potentially done some other work. Note that this cast will behave differently depending on whether Call is a struct or a class, if Tag is null - you can cast a null value to a variable of a reference type (or a nullable value type), but not to a non-nullable value type.
A struct is a value type, so it cannot be used with the as operator. The as operator must be able to assign a value of null if the cast fails. This is only possible with a reference type or a nullable value type.
There are a couple ways to solve this, but your best bet is to change your Call type from a struct to a class. This will essentially change your type from a value type to a reference type, which allows the as operator to assign a value of null if the cast fails.
For more information on value types vs. reference types, this is a decent article. Also, have a look on MSDN:
value types
reference types
as-operator
nullable types.
From the C# Spec
§7.10.11 The as operator is used to
explicitly convert a value to a given
reference type or nullable type. Unlike a cast expression
(§7.7.6), the as operator never throws
an exception. Instead, if the
indicated conversion is not possible,
the resulting value is null.
References and nullable types can be null. Stucts are value types so they can't be null.
Call? call = line.Tag as Call?;
It's a limitation of C#. If the type were a reference type, then if the cast failed it would simply return 'null', but since it's a value type, it doesn't know what to return when the cast fails.
You must replace your use of as with two: 'is' and 'as'
if (line.Tag is Call) {
call = (Call)line.Tag;
} else {
// Do whatever you would do if as returned null.
}
What is the meaning - As stated, structures are value types.
How can I solve it - Change it to
Call call = line.Tag;
What is the behind-the-scenes difference between int? and int data types?
Is int? somehow a reference type?
? wraps the value type (T) in a Nullable<T> struct:
http://msdn.microsoft.com/en-us/library/b3h38hb0.aspx
In addition to "int?" being a shortcut for "Nullable", there was also infrastructure put into the CLR in order to implicitly and silently convert between "int?" and "int". This also means that any boxing operation will implicitly box the actual value (i.e., it's impossible to box Nullable as Nullable, it always results in either the boxed value of T or a null object).
I ran into many of these issues when trying to create Nullable when you don't know T at compile time (you only know it at runtime). http://bradwilson.typepad.com/blog/2008/07/creating-nullab.html
For one of the better "behind the scenes" discussions about Nullable types you should look at CLR Via C# by Jeffrey Richter.
The whole of Chapter 18 is devoted to discussing in detail Nullable types. This book is also excellent for many other areas of the .NET CLR internals.
I learned that you must explicitly cast a nullable value type to a none-nullable value type, as the following example shows:
int? n = null;
//int m1 = n; // Doesn't compile
int n2 = (int)n; // Compiles, but throws an exception if n is null
MS Document