C#中可空类型的替代方案
我正在编写处理一系列数字数据的算法,有时,系列中的值必须为空。 但是,由于此应用程序对性能至关重要,因此我避免使用可空类型。 我已经对这些算法进行了性能测试,专门比较了使用可空类型和非可空类型的性能,在最好的情况下,可空类型的速度慢了2倍,但往往差得多。
最常用的数据类型是double,目前所选的null替代是double.NaN。 但是我知道这不是NaN值的确切用途,所以我不确定这是否有任何问题,我无法预见,最佳做法是什么。
我有兴趣找出以下数据类型的最佳空替代品:double / float,decimal,DateTime,int / long(尽管其他数据类型非常受欢迎)
编辑:我想我需要澄清我对性能的要求。 数字数据的演出通过这些算法在几个小时的时间内处理。 因此,虽然例如10ms或20ms之间的差异通常是微不足道的,但在这种情况下它确实对所花费的时间产生了重大影响。
好吧,如果你已经排除了Nullable
,那么你将获得域名值 – 即你认为是空值的幻数。 虽然这并不理想 ,但这种情况并不少见 – 例如,许多主框架代码将DateTime.MinValue
视为null。 这至少使损害远离共同的价值……
编辑以仅突出显示没有NaN的位置
所以在没有NaN
,也许可以使用.MinValue
– 但只记得如果你不小心使用同一个值意味着相同的数字会发生什么样的祸害……
显然,对于未签名的数据,您需要.MaxValue
(避免零!!!)。
就个人而言,我会尝试使用Nullable
来更安全地表达我的意图……可能有一些方法可以优化你的Nullable
代码。 而且 – 当你在所有需要的位置检查神奇数字时,也许它不会比Nullable
快得多?
在这个特定的边缘情况下,我有点不同意Gravell:Null-ed变量被认为是“未定义”,它没有值。 因此无论用什么信号都可以:即使是魔术数字,但是你必须考虑到一个神奇的数字将来会在你突然成为一个“有效”值时困扰你。 使用Double.NaN,您不必为此担心:它永远不会成为有效的双倍。 但是,您必须考虑到双精度序列意义上的NaN只能用作“未定义”的标记,显然,您也不能将它用作序列中的错误代码。
因此,无论用什么标记’未定义’:必须在值集的上下文中清楚地表明该特定值被视为’未定义’的值,并且将来不会改变。
如果Nullable给你带来太多麻烦,可以使用NaN或其他任何东西,只要你考虑后果:选择的值代表’undefined’并且将保留。
我正在开发一个使用NaN作为null
值的大型项目。 我对此并不十分满意 – 出于与你类似的原因:不知道会出现什么问题。 到目前为止,我们没有遇到任何实际问题,但请注意以下事项:
NaN算术 – 虽然大多数时候,“NaN促销”是一件好事,但它可能并不总是你所期望的。
比较 – 如果你想让NaN比较相等,价值的比较会变得相当昂贵。 现在,测试浮点数是否相等并不简单,但是排序(a
代码感染 – 我看到许多算术代码需要特定处理NaN才是正确的。 因此,出于性能原因,您最终会得到“接受NaN的function”和“不接受NaN的function”。
其他非有限的 NaN是唯一的非有限值。 应该牢记……
禁用时, 浮点exception不是问题。 直到某人启用它们。 真实故事:ActiveX控件中NaN的静态初始化。 听起来不可怕,直到你改变安装使用InnoSetup,它使用Pascal / Delphi(?)核心,默认情况下启用了FPUexception。 我花了一段时间才弄明白。
所以,总而言之,没有什么是严重的,尽管我不想经常考虑NaNs。
我会尽可能经常使用Nullable类型,除非它们(已certificate是)性能/资源约束。 一种情况可能是具有偶尔NaN的大型矢量/矩阵,或者大型命名的单个值,其中默认的NaN行为是正确的 。
或者,您可以使用矢量和矩阵的索引向量,标准“稀疏矩阵”实现或单独的bool /位向量。
部分答案:
Float和Double提供NaN(非数字)。 NaN有点棘手,因为根据规格,NaN!= NaN。 如果你想知道一个数字是否是NaN,你需要使用Double.IsNaN()。
另请参见二进制浮点和.NET 。
调用Nullable的一个成员或属性(拳击)时,可能会发生显着的性能下降。
尝试使用带有double +布尔值的结构来告知是否指定了值。
通过定义自己的结构,可以避免与Nullable
相关的一些性能下降
struct MaybeValid { public bool isValue; public T Value; }
如果需要,可以定义构造函数或从T
到MaybeValid
等的转换运算符,但是过度使用这些东西可能会产生次优性能。 如果避免不必要的数据复制,暴露场结构可以是有效的。 有些人可能会对暴露场的概念不以为然,但它们可以大大提高性能。 如果一个返回T
的函数需要一个T
类型的变量来保存它的返回值,那么使用MaybeValid
只需要增加4个要返回的东西的大小。 相反,使用Nullable
将要求函数首先计算Foo
,然后将其副本传递给Nullable
的构造函数。 此外,返回Nullable
将要求任何想要使用返回值的代码必须至少为Foo
类型的存储位置(变量或临时)创建一个额外的副本,然后才能对其执行任何有用的操作。 相比之下,代码可以使用Foo
类型变量的Value
字段与任何其他变量一样高效。