影像类型为null_可以为null的值类型,详解可空值类型(一)

0x00 前言

众所周知的一点是C#语言是一种强调类型的语言,而C#作为Unity3D中的游戏脚本主流语言,在我们的开发工作中能够驾驭好它的这个特点便十分

重要。事实上,怎么强调C#的这个特点都不为过,因为它牵涉到编程的很多方面。一个很好的例子便是我们本文要介绍的内容——可空型,它是因何出现的,而它

的出现又有什么意义呢?以及如何在Unity3D游戏的开发中使用它呢?那么就请各位读者朋友带着这些疑问,通过下面的文字来寻找这些问题的答案吧。

0x01 如果没有值?

一个了解一点C#基础知识的人都知道,值类型的变量永远不会为null,因为值类型的值是其本身。而对于一个引用类型的变量来说,它的值则是对一个

对象的引用。那么空引用是表示一个值呢,还是表示没有值呢?如果表示没有值,那么没有值可以算是一种有效的值吗?如果我们根据相关标准中关于引用类型的定

义,我们其实很容易就可以发现,一个非空的引用值事实上提供了访问一个对象的途径,而空引用(null)当然也表示一个值,只不过它是一个特殊的值,即意

味着该变量没有引用任何对象。但null在本质上和其他的引用的处理方式是一样的,通过相同的方式在内存中存储,只不过内存会全部使用0来表示null,

因为这种操作的开销最低,仅仅需要将一块内存清除,这也是为何所有的引用类型的实例默认值都是null的原因。

但是,正如在本节一开始说的,值类型的值永远不能是null,但是在我们的开发工作中是否会恰巧遇到一个必须让值类型变量的值既不是负数也不是0,而是真正的不存在的情况呢?答案是是的,很常见。

一种最常见的情况是在设计数据库时,是允许将一列的数据类型定义为一个32位整数,同时映射到C#中的Int32这个数据类型。但是,数据库中的一

列值中是存在为空的可能性的,换言之在该列的某一行上的有可能是没有任何值的,即不是0也不是负无穷,而是实实在在的空。这样会带来很多的隐患,也使得

C#在处理数据库时变得十分困难,原因上文已经提到过了,在C#中无法将值类型表示为空。

当然还有很多种可能的情况,例如在开发手机游戏时需要通过移动手指来滑动选择一块区域内的游戏单位,一次拖动完成之后,显然应该将本次拖动的数据清

空,以作为开始下一次拖动的开始条件,而往往这些拖动数据在Unity3D的脚本语言中都是作为值类型出现的,因而无法直接设为空,所以也会给开发带来些

许不便。

那么如果没有一个可以让值类型直接表示空的方法出现,我们是否还有别的手段来实现类似的功能呢?下面我们就来聊聊如果没有可空类型,应该如何在逻辑上近似实现值类型表示空的功能。

0x02 表示空值的一些方案

假设如果真的没有一种可以直接表示空值的方案出现,那么我们是否能想到一些替代方案呢?所以本节就归纳一下三种用来表示空值的方案。

1.使用魔值

首先我们要知道值类型的值都是它本身,换言之每个值我们都希望是有意义的。而魔值这个概念或者说方案的出现,恰恰是违背这一原则的,即放弃一个有意

义的值,并且使用它来表示空值,这个值在我们的逻辑中与别的值不同,这便是魔值。因为它让一个有意义的值消失了,例如魔值选为-1000,那么-1000

这个值便不再表示-1000了,相反,它意味着空。

回到刚刚的例子中,在数据库中如果有映射成Int32类型的某列值中恰好有一个是空,那么我们可以选择(牺牲)一个恰当的值来表示空。这样做的好处

在于不会浪费内存,同样也不需要定义新的类型。但牺牲哪个值来作为魔值便成为了一个需要慎重考虑的事情。因为一旦做出选择,就意味着一个有意义的值的消

失。

当然,使用魔值这种方案在实际的开发中也显得很low,这是因为问题并没有被真正的解决,相反,我们只是耍了一个小聪明来实现暂时蒙混过关。因此我并不十分喜欢这种方案。

2 使用标志位

如果我们不想浪费或者说牺牲掉一个有意义的值来让它作为魔值来表示空的话,那么只用一个值类型的实例是不够的。这时候我们能想到的一个解决方案就是

使用额外的bool型变量作为一个标识,来判定对应的值类型实例是否是空值。这种方案具体操作起来有很多种方式,例如我们可以保留两个实例,一个是表示我

们所需的普通的值的变量,另一个则是标识它是否为空值的bool类型的变量。如下面这段代码所示:

//使用bool型变量作为标识

using UnityEngine;

using System;

using System.Collections.Generic;

public class Example : MonoBehaviour {

private float _realValue;

private bool _nullFlag;

private void Update()

{

this._realValue = Time.time;

this._nullFlag = false;

this.PrintNum(this._realValue);

}

private void LateUpdate()

{

this._nullFlag = true;

this.PrintNum(this._realValue);

}

// Use this for initialization

private void Start () {

}

private void PrintNum(float number)

{

if(this._nullFlag)

{

Debug.Log("传入的数字为空值");

return;

}

Debug.Log("传入的数字为:" + number);

}

}

在这段代码中,我们维护了两个变量,分别是float型的_ realValue,用来表示我们所需的值和bool型的_nullFlag,用来标识此时_ realValue所代表的值是否为空值(当然_ realValue本身不可能为空)。

这种使用额外标识的方法要比上一小节中介绍的魔值方案好一些,因为没有牺牲任何有意义的值。但同时维护两个变量,而且这两个变量的关联性很强,因此

稍有不慎可能就会造成bug,那么除了同时维护两个变量之外,还有别的具体方案可以用来实现标识是否为空值这个需求的吗?答案是有的,一个自然而然的想法

便是使用结构将这两个值类型封装到一个新的值类型中。我们为这个新的值类型取名为NullableValueStruct。下面我们来看看

NullableValueStruct值类型的定义:

//值类型NullableValueStruct的定义

using System;

using System.Collections;

using System.Collections.Generic;

public struct NullableValueStruct

{

private float _realValue;

private bool _nullFlag;

public NullableValueStruct(float value, bool isNull)

{

this._realValue = value;

this._nullFlag = isNull

}

public float Value

{

get

{

return this._realValue;

}

set

{

this._realValue = value;

}

}

public bool IsNull

{

get

{

return this._nullFlag;

}

set

{

this._nullFlag = value;

}

}

}

这样,我们就将刚刚要单独维护的两个变量封装到了一个新的类型中。而且由于这个新的类型是struct,换言之它是一个值类型因此也无需担心会产生装箱和拆箱的操作。下面我们就通过一段代码,在我们游戏中使用一下这个新的值类型吧。

using UnityEngine;

using System;

using System.Collections.Generic;

public class Example : MonoBehaviour {

private NullableValueStruct number = new NullableValueStruct(0f, false);

private void Update()

{

this.number.Value = Time.time;

this.number.IsNull = false;

this.PrintNum(this.number);

}

private void LateUpdate()

{

this.number.IsNull = true;

this.PrintNum(this.number);

}

// Use this for initialization

private void Start () {

}

private void PrintNum(NullableValueStruct number)

{

if(number.IsNull)

{

Debug.Log("传入的数字为空值");

return;

}

Debug.Log("传入的数字为:" + number.Value);

}

}

当然除了这种方式,是否还有别的方案呢?下面我们就来总结一下另一种方案,即使用引用类型来辅助值类型表示空值。

3 借助引用类型来表示值类型的空值

介绍完前两种为值类型表示空值的方案之后,我们接下来再介绍最后一种方案。当然聪明的读者朋友一定也想到了,既然值类型不能够是null,而引用类

型却可以是null,那么是否可以借助引用类型来辅助值类型表示null呢?事实上使用引用类型来帮助表示值类型的空值,是一个很好的方向,而具体而言又

可以分成两种解决思路。

如我们所知,C#语言中的所有类型(引用类型和值类型)都是自System.Object类派生而来,虽然值类型不能为null,但是

System.Object类却可以为null,因此在所有使用值类型同时有可能需要值类型表示空值的地方使用System.Object类来代替,便可

以直接使用null来表示空值了。下面让我们来看一个小例子:

using UnityEngine;

using System;

using System.Collections.Generic;

public class Example : MonoBehaviour {

private void Update()

{

this.PrintNum(Time.time);

}

// Use this for initialization

private void Start () {

}

private void PrintNum(object number)

{

if(number == null)

{

Debug.Log("传入的数字为空值");

return;

}

float realNumber = (float)number;

Debug.Log("传入的数字为:" + realNumber);

}

}

当然,使用这种方式由于会频繁的在引用类型(System.Object)和值类型直接转换,因此会涉及到十分频繁的装箱和拆箱的操作进而产生很多

垃圾而引发垃圾回收机制,会对游戏的性能产生一些影响。那么是否还有别的方案,不需要涉及到频繁的装箱和拆箱操作呢?答案是直接使用引用类型来表示值类

型,即将值类型封装成一个引用类型。

当然,这么做之后,我们相当于重新创建了一个全新的类型,在这里我们假设我们创建的这个新的类型叫做NullableValueType(当然它事

实上是引用类型),在NullableValueType类的内部保留一个值类型的实例,该值类型的实例的值便是此时NullableValueType

类所表示的值,而当需要表示空值时,只需要让NullableValueType类的实例为null即可。下面就让我们通过代码来定义一下

NullableValueType类吧。

// NullableValueType类定义

using System;

using System.Collections;

using System.Collections.Generic;

public class NullableValueType

{

private float _value;

public NullableValueType(float value)

{

this._value = value;

}

public float Value

{

get

{

return this._value;

}

set

{

this._value = value;

}

}

}

这样我们就将一个值类型(float)封装成了一个引用类型,所以理论上我们既可以使用引用类型的null来表示空值,也可以借助这个类内部的值类型实例来表示有意义的值。下面我们就使用这种封装的方式来重新实现一下上面的例子。

using UnityEngine;

using System;

using System.Collections.Generic;

public class Example : MonoBehaviour {

private NullableValueType value;

private void Update()

{

this.value.Value = Time.time;

this.PrintNum(this.value);

}

// Use this for initialization

private void Start () {

this.value = new NullableValueType(0f);

}

private void PrintNum(NullableValueType number)

{

if(number == null)

{

Debug.Log("传入的数字为空值");

return;

}

Debug.Log("传入的数字为:" + number.Value);

}

}

如刚刚所说的,在这里我们可以直接判断传入的值是否为null来确定要表达的值是否为空值,如果不是空值,则可以利用类中封装的值类型实例来表示它

所要表达的值。这样做的优点是无需进行引用类型和值类型之间的转换,换言之能够缓解装箱和拆箱操作的频率,减少垃圾的产生速度。但是缺点同样十分明显,使

用引用类型对值类型进行封装,本质上是重新定义了一个新的类型,因而代码量将会增加同时增加维护的成本。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值