The composability of the iterator pattern is extremely useful in LINQ; we will discuss the subject again in Chapter 8.
Constructing an Enumerable Object
You can instantiate and populate an enumerable object in a single step. For example:
using System.Collections.Generic; ...
List<int>list = new List<int>{1, 2, 3};
The compiler translates this to the following:
using System.Collections.Generic; ...
List<int>list = new List<int>(); list.Add (1); list.Add (2); list.Add (3);
This requires that the enumerable object implements theSystem.Collections.IEnumerableinterface, and that it has anAddmethod that takes a single argument.
Reference types can represent a nonexistent value with a null reference. Value types, however, cannot ordinarily represent null values. For example:
string s = null; // OK, Reference Type int i = null; // Compile Error, Value Type cannot be null
To represent null in a value type, you must use a special construct called a nullable type. A nullable type is denoted with a value type followed by the? symbol:
int? i = null; // OK, Nullable Type Console.WriteLine (i == null); // True
Nullable<T> struct
T? translates into System.Nullable<T>. Nullable<T> is a lightweight immutable structure, having only two fields, to represent Value and HasValue. The essence ofSystem.Nullable<T>is very simple:
public struct Nullable<T> where T : struct { public T Value {get;} public bool HasValue {get;} public T GetValueOrDefault(); public T GetValueOrDefault(T defaultValue); ... }
The code:
int? i = null; Console.WriteLine (i == null); // true
translates to:
Nullable<int>i = new Nullable<int>(); Console.WriteLine (! i.HasValue); // true
Attempting to retrieveValuewhenHasValueis false throws anInvalidOperationException.GetValueOrDefault()returnsValue ifHasValueis true; otherwise, it returnsnewT()or a specified custom default value.
The default value ofT?isnull.
Implicit and explicit nullable conversions
The conversion from T to T? is implicit, and from T? to T is explicit. For example:
int? x = 5; // implicit int y = (int)x; // explicit
The explicit cast is directly equivalent to calling the nullable object’sValueproperty. Hence, anInvalidOperationExceptionis thrown ifHasValueis false.
Boxing and unboxing nullable values
When T? is boxed, the boxed value on the heap contains T, not T?. This optimization is possible because a boxed value is a reference type that can already express null.
The Nullable<T> struct does not define operators such as <, >, or even ==. Despite this, the following code compiles and executes correctly:
int? x = 5; int? y = 10; bool b = x < y; // true
This works because the compiler steals or “lifts” the less-than operator from the underlying value type. Semantically, it translates the preceding comparison expression into this:
In other words, if bothxandyhave values, it compares viaint’s less-than operator; otherwise, it returnsfalse.
Operator lifting means you can implicitly useT’s operators onT?. You can define operators forT?in order to provide special-purpose null behavior, but in the vast majority of cases, it’s best to rely on the compiler automatically applying systematic nullable logic for you. Here are some examples:
// all other operator examples Console.WriteLine(x + 5); // 10 Console.WriteLine(x + y); // null (prints empty line)
The compiler performs null logic differently depending on the category of operator. The following sections explain these different rules.
Equality operators (== !=)
The equality operators work on the principle that the behavior for nullable types works exactly as it does for reference types. This means a nonnull value is not equal to a null value, but two null values are equal.
bool a = x == y; // translation: bool a = (x != null && y != null) ? (x.Value == y.Value) : (x != null ^ y != null); // a is true
Relational operators (< <= >= >)
The relational operators work on the principle that it is meaningless to compare null operands. This means comparing a null value to either a null or nonnull value returns false.
false .The relational operators work on the principle that it is meaningless to compare null operands. This means comparing a null value to either a null or nonnull value returns false.
bool b = x < y; // translation: bool b = (x == null || y == null) ? false : (x.Value < y.Value);
// b is false
All other operators (+ - * / % & | ^ << >> + ++ - -- ! ~)
These operators work on the principle to always return “I don’t know” (i.e., null) when fed any operands that are null. This means that if any operand is null, the result is also null. This pattern should be familiar to SQL users.
int? c = x + y; // translation: int? c = (x == null || y == null) ? null : (int?)(x.Value + y.Value);
The ?? operator is the null coalescing operator, and it can be used with both nullable types and reference types. It says “If the operand is nonnull, give it to me; otherwise, give me a default value.” For example:
int? x = null; int y = x ?? 5; // y is 5
The?? operator is equivalent to callingGetValueOrDefaultwith an explicit default value.
Please check back next week for the continuation of this article.