2014年1月15日 星期三

C# - Null型別

程式中,Null是一個很常用來判別的一個型別,Null代表的是一個空的型別,跟Undefined有很相似之處,一般初學者很常會在程式判斷上,會搞不清楚什麼時候該用Null,什麼時候要用Undefined去判別。不過在程式設計上,筆者會建議在變數的宣告時,盡可能給予一個基本值,因為在Null和Undefined的判斷上,大部分都會是判斷發生的例外,這樣會比較減少用過多的if去吃掉運算效能,不過在C#裡,Null是一個很特別的型別,微軟為它創造了一個許多屬於它處理的獨立個體。以下是微軟為Null下的定義:
可為 Null 的型別有下列特性:
  • 可為 Null 的型別代表能夠指派 null 值的實值型別變數。您無法根據參考型別建立可為 Null 的型別 (參考型別已支援 null 值)
  • 語法 T? 是 Nullable<T> 的簡略表示法,其中 T 是實值型別。兩種格式可以互相變更
  • 就像針對一般實值型別一樣,將值指派至可為 Null 的型別,例如 int? x = 10; 或 double? d = 4.108;
  • 使用 Nullable<T>.GetValueOrDefault 方法傳回指派的值;若值為 null,則傳回基礎型別的預設值,例如 int j = x.GetValueOrDefault();
  • 使用 HasValue 和 Value 唯讀屬性測試 null 並擷取值,例如 if(x.HasValue) j = x.Value;
    • 如果變數包含值 HasValue 屬性會傳回 true,而如果變數是 null 則傳回 false
    • 如果有指派值,Value 屬性便會傳回值。否則,會擲回 System.InvalidOperationException
    • 可為 Null 的型別變數預設值會將 HasValue 設定為 falseValue 則未定義
  • 當目前值為 null 的可為 Null 型別指派至不可為 Null 的型別時,請使用 ?? 運算子指派將套用的預設值,例如 int? x = null; int y = x ?? -1;
  • 不允許巢狀式可為 Null 的型別。不會編譯下列程式碼行:Nullable<Nullable<int>> n;
可為 Null 的型別可以代表基礎型別的所有值,也可以代表另一個 null 值。您可以使用下列其中一種方法宣告可為 Null 的型別:
System.Nullable<T> variable
-或-
T? variable
T 是可為 Null 之型別的基礎型別,T 可以是任何實值型別 (包括 struct,但不能是參考型別)。
何時該使用可為 null 的型別,可舉一般的布林變數為例,這種變數有兩個可能的值:true 和 false。但沒有值可以代表「未定義」。在許多設計程式的應用程式中,特別是資料庫互動的程式,變數有可能是以未定義的狀態存在。例如,資料庫中的某個欄位可能包含 true 或 false 值,但也可能不包含任何值。同樣地,參考型別可以設定為 null 表示其並非初始化的。
由於這些值的性質不同,就需要在程式上另做設計,包括使用其他變數存放狀態資訊、使用特殊值等等。可為 null 的型別修飾詞能夠讓 C# 建立表示未定義之值的實值型別 (Value Type) 變數。

可為 Null 的型別範例


任何實值型別都可以當做可為 null 的型別的基礎,例如:
int? i = 10;

double? d1 = 3.14;

bool? flag = null;

char? letter = 'a';

int?[] arr = new int?[10];


可為 Null 的型別成員
可為 null 之型別的每個執行個體 (Instance) 有兩個公用唯讀屬性:
  • HasValue
    HasValue 屬於 bool 型別。當變數包含非 null 的值時,該屬性會設定為 true
  • Value
    Value 的型別與基礎型別相同。如果 HasValue 為 trueValue 會包含有意義的值。如果 HasValue 為 false,存取 Value 將會擲回 InvalidOperationException
在這個範例中,會先使用 HasValue 成員測試變數是否包含值,然後才會嘗試顯示該值。
int? x = 10;

if (x.HasValue)
{
    System.Console.WriteLine(x.Value);
}
else
{
    System.Console.WriteLine("Undefined");
}
也可使用下列範例完成測試值的作業:
int? y = 10;

if (y != null)
{
    System.Console.WriteLine(y.Value);
}
else
{
    System.Console.WriteLine("Undefined");
}
明確轉換
您可以明確使用轉型或使用 Value 屬性,將可為 null 的型別轉換成標準型別。例如:
int? n = null;

//int m1 = n;      // Will not compile.

int m2 = (int)n;   // Compiles, but will create an exception if x is null.
int m3 = n.Value;  // Compiles, but will create an exception if x is null.
如果在兩資料型別之間定義了使用者定義轉換,這兩個資料型別的可為 Null 的型別版本也可以使用該轉換。

隱含轉換
可為 Null 的型別 (Nullable Type) 可使用 null 關鍵字設為 null,如下列範例所示:
int? n1 = null;
從一般型別轉換成可為 null 的型別是隱含轉換。
int? n2;
n2 = 10;

可為 Null 的型別也可以使用預先定義的一元和二元運算子,以及現有實值型別的任何使用者定義運算子。如果運算元都是 null,這些運算子就會產生 null 值,否則運算子會使用所包含的值計算結果。例如:
int? a = 10;
int? b = null;

a++;

a = a * 10;
a = a + b;
使用可為 Null 的型別執行比較時,如果其中一個可為 Null 的型別是 null,比較的評估結果一定會是 false。因此,請千萬不要認為比較結果為 false 時,反之就是 true。例如:
int? num1 = 10;
int? num2 = null;

if (num1 >= num2)
{
    System.Console.WriteLine("num1 is greater than or equal to num1");
}
else
{
}
因為 num2 是 null,導致其中並未包含值,所以上述 else 陳述式中的結果是無效的。
兩個皆為 null 之可為 Null 的型別,其比較的評估結果將會是 true

?? 運算子會定義當可為 null 的型別指派給不可為 null 的型別時,要傳回的預設值。
int? c = null;
int d = c ?? -1;
這個運算子也可以搭配多個可為 null 的型別使用。例如:
int? e = null;
int? f = null;
int g = e ?? f ?? -1;

可為 null 的 bool? 型別可包含三種不同的值:truefalse 和 null。如需從 bool? 轉換為 bool 的詳細資訊,請參閱 HOW TO:從 bool? 安全轉型至 bool (C# 程式設計手冊)
可為 Null 的布林值就像 SQL 中使用的布林值變數型別。為確保 & 和 | 運算子產生的結果與 SQL 的三值布林型別一致,提供下列預先定義的運算子:
booloperator &(bool? x, bool? y)
booloperator |(bool? x, bool? y)
這些運算子的結果列於下表:
X
y
x&y
x|y
true
true
true
true
true
false
false
true
true
null
null
true
false
true
false
true
false
false
false
false
false
null
false
null
null
true
null
true
null
false
false
null
null
null
null
null
只有當以可為 Null 的型別 (Nullable Type) 為基礎的物件不是 Null 時,該物件才會是 boxed。如果 HasValue 為 false,則會將物件參考指派為 null,而不會做 boxing 處理。例如:
bool? b = null;
object o = b;
如果物件為非 null (若 HasValue 是 true),便會進行 boxing 處理,但只有可為 Null 的物件所依據的基礎型別會是 boxed。對不可為 Null 的數值型別執行 box 處理就會 box 數值型別本身,而非包裝實值型別的 System.Nullable<T>。例如:
bool? b = false;
int? i = 44;
object bBoxed = b;
object iBoxed = i;
這兩個 boxed 物件與透過對不可為 Null 型別執行 box 處理而建立的物件相同。而且就像不可為 null 的 boxed 型別一樣,可以 unboxed 成可為 null 的型別,如下列範例所示:
bool? b2 = (bool?)bBoxed;
int? i2 = (int?)iBoxed;


在執行 box 的動作時,可為 Null 型別的行為提供了兩個優點:
  1. 可以測試可為 Null 的物件及其 boxed 對應物件是否為 null:
      bool? b = null;
      object boxedB = b;
      if (b == null){//結果為True}
      if (boxedB == null){//結果為True}
  2. Boxed 可為 Null 型別完全支援基礎型別的功能:
    double? d = 44.4;
      object iBoxed = d;
      // Access IConvertible interface implemented by double.
      IConvertible ic = (IConvertible)iBoxed;
      int i = ic.ToInt32(null);
      string str = ic.ToString();

-雲遊山水為知已逍遙一生而忘齡- 電腦神手

沒有留言:

張貼留言