可空類型 -- C#入門經典(第3版)


首先論述另一個較簡單的泛型類型(nullable type):可空類型,解決值類型的一個小問題。

12.2.1 可空類型

在前面的章節中,介紹了值類型(大多數基本類型,例如int、double和所有的結構)區別於引用類型(string和所有的類)的一種方式:值類型必須包含一個值,它們可以在聲明之后、賦值之前,在未賦值的狀態下存在,但不能以任何方式使用。而引用類型可以是null。

有時讓值類型為空是很有用的,泛型使用System.Nullable<T>類型提供了使值類型為空的一種方式。例如:

System.Nullable<int> nullableInt;

這行代碼聲明了一個變量nullableInt,它可以擁有int變量能包含的任意值,還可以擁有值null。所以可以編寫下面的代碼:

nullableInt = null;

如果nullableInt是一個int類型的變量,上面的代碼是不能編譯的。

前面的賦值等價於:

nullableInt = new System.Nullable<int>();

與其他變量一樣,無論是初始化為null(使用上面的語法),還是通過給它賦值來初始化,都不能在初始化之前使用它。

可以像測試引用類型一樣,測試可空類型,看看它們是否為null:

if (nullableInt == null)

{

...

}

另外,可以使用HasValue屬性:

if (nullableInt.HasValue)

{

...

}

這不適用於引用類型,即使引用類型有一個HasValue屬性,也不能使用這種方法,因為引用類型的變量值為null,就表示不存在對象,當然就不能通過對象來訪問這個屬性,此時會拋出一個異常。

使用Value屬性可以查看引用類型的值。如果HasValue是true,就說明Value屬性有一個非空值。但如果HasValue是false,就說明變量被賦予了null,訪問Value屬性會拋出System. InvalidOperationException類型的異常。

可空類型要注意的一點是,它們非常有用,以致於修改了C#語法。上面可空類型的變量不使用上述語法,而是使用下面的語法:

int? nullableInt;

int ?是System.Nullable<int>的縮寫,但可讀性更高。在后面的章節中就使用這個語法。

1. 運算符和可空類型

對於簡單類型如int,可以使用+、–等運算符來處理值。而對於可空類型,這是沒有區別的:包含在可空類型中的值會隱式轉換為需要的類型,使用適當的運算符。這也適用於結構和自己提供的運算符。例如:

int? op1 = 5;

int? result = op1 * 2;

注意其中result變量的類型也是int?。下面的代碼不會編譯:

int? op1 = 5;

int result = op1 * 2;

為了使上面的代碼正常工作,必須進行顯式轉換:

int? op1 = 5;

int result = (int)op1 * 2;

只要op1有一個值,上面的代碼就可以正常運行,如果op1是null,就會生成System.Invalid OperationException類型的異常。

這就引出了下一個問題:當運算等式中的一個或兩個值是null時,例如上面代碼中的op1,會發生什么情況?答案是:對於除了bool?之外的所有簡單可空類型,該操作的結果是null,可以把它解釋為“不能計算”。對於結構,可以定義自己的運算符來處理這種情況(詳見本章后面的內容)。對於bool?,為&和 | 定義的運算符會得到非空返回值,如表12-1所示。

表 12-1

op1

op2

op1 & op2

op1 | op2

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就不重要。

2. ??運算符

為了進一步減少處理可空類型所需的代碼量,使可空變量的處理變得更簡單,可以使用??運算符。這個運算符允許提供可空類型是null和不是null時的默認值,其用法如下:

int? op1 = null;

int result = op1 * 2 ?? 5;

在這個示例中,op1是null,所以op1*2也是null。但是,??運算符檢測到這個情況,並把值5賦予result。這里特別要注意,在結果中放入int類型的變量result不需要顯式轉換。??運算符會自動處理這個轉換。可以把??等式的結果放在int?中:

int? result = op1 * 2 ?? 5;

在處理可空變量時,??運算符有許多用途,它也是提供默認值的一種方便方式,不需要使用if結構中的代碼塊。

在下面的示例中,將介紹可空類型Vector。

試試看:可空類型

(1) 在目錄C:/BegVCSharp/Chapter12下創建一個新控制台應用程序項目Ch12Ex01。

(2) 使用VS快捷方式,在文件Vector.cs中添加一個新類Vector。

(3) 修改Vector.cs中的代碼,如下所示:

public class Vector

{

public double? R = null;

public double? Theta = null;

 

public double? ThetaRadians

{

get

{

// Convert degrees to radians.

return (Theta * Math.PI / 180.0);

}

}

 

public Vector(double? r, double? theta)

{

// Normalize.

if (r < 0)

{

r = -r;

theta += 180;

}

theta = theta % 360;

 

// Assign fields.

R = r;

Theta = theta;

}

 

public static Vector operator +(Vector op1, Vector op2)

{

try

{

// Get (x, y) coordinates for new vector.

double newX = op1.R.Value * Math.Sin(op1.ThetaRadians.Value)

+ op2.R.Value * Math.Sin(op2.ThetaRadians.Value);

double newY = op1.R.Value * Math.Cos(op1.ThetaRadians.Value)

+ op2.R.Value * Math.Cos(op2.ThetaRadians.Value);

 

// Convert to (r, theta).

double newR = Math.Sqrt(newX * newX + newY * newY);

double newTheta = Math.Atan2(newX, newY) * 180.0 / Math.PI;

 

// Return result.

return new Vector(newR, newTheta);

}

catch

{

// Return "null" vector.

return new Vector(null, null);

}

}

 

public static Vector operator -(Vector op1)

{

return new Vector(-op1.R, op1.Theta);

}

 

public static Vector operator -(Vector op1, Vector op2)

{

return op1 + (-op2);

}

 

public override string ToString()

{

// Get string representation of coordinates.

string rString = R.HasValue ? R.ToString() : "null";

string thetaString = Theta.HasValue ? Theta.ToString() : "null";

 

// Return (r, theta) string.

return string.Format("({0}, {1})", rString, thetaString);

}

}

(4) 修改Program.cs中的代碼,如下所示:

class Program

{

public static void Main(string[] args)

{

Vector v1 = GetVector("vector1");

Vector v2 = GetVector("vector1");

Console.WriteLine("{0} + {1} = {2}", v1, v2, v1 + v2);

Console.WriteLine("{0} - {1} = {2}", v1, v2, v1 - v2);

Console.ReadKey();

}

 

public static Vector GetVector(string name)

{

Console.WriteLine("Input {0} magnitude:", name);

double? r = GetNullableDouble();

Console.WriteLine("Input {0} angle (in degrees):", name);

double? theta = GetNullableDouble();

return new Vector(r, theta);

}

 

public static double? GetNullableDouble()

{

double? result;

string userInput = Console.ReadLine();

try

{

result = double.Parse(userInput);

}

catch

{

result = null;

}

return result;

}

}

(5) 執行應用程序,給兩個矢量(vector)輸入值,結果如圖12-1所示。

圖 12-1

(6) 再次執行應用程序,這次跳過四個值中的至少一個,結果如圖12-2所示。

圖 12-2

示例的說明

在這個示例中,創建了一個類Vector,它表示帶極坐標(有一個幅值和一個角度)的矢量,如圖12-3所示。

圖 12-3

坐標r和_在代碼中用公共字段R和Theta表示,其中Theta的單位是度(°)。ThetaRad用於獲取Theta的弧度值,這是必須的,因為Math類在其靜態方法中使用弧度。R和Theta的類型都是double?,所以它們可以為空。

public class Vector

{

public double? R = null;

public double? Theta = null;

 

public double? ThetaRadians

{

get

{

// Convert degrees to radians.

return (Theta * Math.PI / 180.0);

}

}

Vector的構造函數標准化R和Theta的初始值,然后賦予公共字段。

public Vector(double? r, double? theta)

{

// Normalize.

if (r < 0)

{

r = -r;

theta += 180;

}

theta = theta % 360;

 

// Assign fields.

R = r;

Theta = theta;

}

Vector類的主要功能是使用運算符重載對矢量進行相加和相減,這需要一些非常基本的三角函數知識,這里不解釋它們。在代碼中,重要的是,如果在獲取R或ThetaRad的Value屬性時拋出了異常,即其中一個是null,就返回“空”矢量。

public static Vector operator +(Vector op1, Vector op2)

{

try

{

// Get (x, y) coordinates for new vector.

...

}

catch

{

// Return "null" vector.

return new Vector(null, null);

}

}

如果組成矢量的坐標是null,該矢量就是無效的,這里用R和Theta都可為null的Vector類來表示。

Vector類的其他代碼重寫了其他運算符,把相加的功能擴展到相減上,再重寫ToString(),獲取Vector對象的字符串表示。

Program.cs中的代碼測試Vector類,讓用戶初始化兩個矢量,再對它們進行相加和相減。如果用戶省略了一個值,該值就解釋為null,應用前面提及的規則。 


注意!

本站转载的文章为个人学习借鉴使用,本站对版权不负任何法律责任。如果侵犯了您的隐私权益,请联系我们删除。



 
粤ICP备14056181号  © 2014-2021 ITdaan.com