可空类型 -- C#入门经典(第3版)

来源:互联网 发布:平顶山平高怎么样知乎 编辑:程序博客网 时间:2024/06/05 04:00

首先论述另一个较简单的泛型类型(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,应用前面提及的规则。 

原创粉丝点击