C#中可空类型的替代方案

时间:2009-05-18 07:46:14

标签: c# nullable nan non-nullable

我正在编写处理一系列数字数据的算法,有时,系列中的值必须为null。但是,由于此应用程序对性能至关重要,因此我避免使用可空类型。我已经对这些算法进行了性能测试,专门比较了使用可空类型和非可空类型的性能,在最好的情况下,可空类型的速度慢了2倍,但往往差得多。

最常用的数据类型是double,目前选择null的替代方法是double.NaN。但是我知道这不是NaN值的确切用途,所以我不确定是否有任何问题,我无法预见,最佳做法是什么。

我感兴趣的是找出以下数据类型的最佳空替代方法:double / float,decimal,DateTime,int / long(尽管其他数据类型非常受欢迎)

编辑:我想我需要澄清我对性能的要求。数字数据的演出通过这些算法在几个小时的时间内处理。因此,虽然例如10ms或20ms之间的差异通常是微不足道的,但在这种情况下它确实对所花费的时间产生了重大影响。

6 个答案:

答案 0 :(得分:18)

好吧,如果你排除了Nullable<T>,你就会得到域名值 - 即你认为是空的幻数。虽然这不是理想,但它并不罕见 - 例如,许多主框架代码将DateTime.MinValue视为null。这至少会使损害远离共同的价值......

编辑以仅突出显示没有NaN的位置

所以,如果没有NaN,也许可以使用.MinValue - 但请记住,如果您不小心使用相同的值意味着相同的数字,会发生什么样的祸害... < / p>

显然,对于未签名的数据,您需要.MaxValue(避免零!!!)。

就个人而言,我会尝试使用Nullable<T>来更安全地表达我的意图......可能有一些方法可以优化您的Nullable<T>代码。而且 - 当你在所有需要的位置检查神奇数字时,也许它不会比Nullable<T>快得多?

答案 1 :(得分:4)

我对Gravell在这个特定的边缘情况上有些不同意:Null-ed变量被认为是“未定义”,它没有值。因此,无论用什么信号都可以:即使是魔术数字,但是你必须考虑到一个神奇的数字在将来突然成为一个“有效”值时会困扰你。使用Double.NaN,您不必为此担心:它永远不会成为有效的双倍。但是,您必须考虑到双精度序列意义上的NaN只能用作“未定义”的标记,显然,您也不能将它用作序列中的错误代码。

所以用于标记'undefined'的任何东西:必须在值集的上下文中清楚地表明该特定值被认为是'undefined'的值,并且将来不会改变。

如果Nullable给你带来太多麻烦,可以使用NaN或其他任何东西,只要你考虑后果:选择的值代表'undefined'并且将保留。

答案 2 :(得分:4)

我正在开发一个使用NaN作为null值的大型项目。我对此并不十分满意 - 出于与你类似的原因:不知道会出现什么问题。到目前为止,我们还没有遇到任何实际问题,但要注意以下几点:

NaN算术 - 虽然大多数时候,“NaN促销”是一件好事,但它可能并不总是你期望的。

比较 - 如果您希望NaN比较相等,则值的比较会变得相当昂贵。现在,测试浮点数是否相等并不简单,但排序(a&lt; b)可能会变得非常难看,因为nan有时需要更小,有时需要大于正常值。

代码感染 - 我看到许多算术代码需要特定处理NaN才是正确的。因此,出于性能原因,您最终会得到“接受NaN的功能”和“不接受NaN的功能”。

其他非finites NaN是唯一的非有限值。应该牢记......

禁用时,

浮点例外不是问题。直到某人启用它们。真实故事:ActiveX控件中NaN的静态初始化。听起来不可怕,直到你改变安装使用InnoSetup,它使用Pascal / Delphi(?)核心,默认情况下启用了FPU异常。我花了一段时间才弄明白。

所以,总而言之,没有什么是严肃的,尽管我不想经常考虑NaN。


我尽可能经常使用Nullable类型,除非它们(已证明是)性能/资源约束。一种情况可能是具有偶尔NaN的大型矢量/矩阵,或者大型命名的单个值,其中默认的NaN行为是正确的


或者,您可以使用矢量和矩阵的索引向量,标准“稀疏矩阵”实现或单独的bool /位向量。

答案 3 :(得分:2)

部分答案:

Float和Double提供NaN(非数字)。 NaN有点棘手,因为根据规格,NaN!= NaN。如果你想知道一个数字是否是NaN,你需要使用Double.IsNaN()。

另见Binary floating point and .NET

答案 4 :(得分:0)

当调用Nullable的一个成员或属性(拳击)时,可能会发生显着的性能下降。

尝试使用带有double + a布尔值的结构来判断是否指定了值。

答案 5 :(得分:0)

通过定义自己的结构,可以避免与Nullable<T>相关的性能下降

struct MaybeValid<T>
{
    public bool isValue;
    public T Value;
}

如果需要,可以定义构造函数或从TMaybeValid<T>等的转换运算符,但过度使用此类事物可能会产生次优性能。如果避免不必要的数据复制,暴露场结构可以是有效的。有些人可能会对外露场的概念不以为然,但它们可以大大提高性能。如果返回T的函数需要具有类型T的变量来保存其返回值,则使用MaybeValid<Foo>只需将要返回的事物的大小增加4。相反,使用Nullable<Foo>将要求函数首先计算Foo,然后将其副本传递给Nullable<Foo>的构造函数。此外,返回Nullable<Foo>将要求任何想要使用返回值的代码必须至少为类型Foo的存储位置(变量或临时)创建一个额外的副本才能执行任何有用的操作用它。相比之下,代码可以使用Value类型变量的Foo字段与任何其他变量一样高效。