可空类型:在C#中检查null或零的更好方法

111

我正在开发一个项目,在很多地方我都需要检查以下内容:

if(item.Rate == 0 || item.Rate == null) { }

仅作为好奇,最好的方法是如何检查这两种情况?

我添加了一个帮助方法,它是:

public static bool nz(object obj)
{
    var parsedInt = 0;
    var parsed = int.TryParse(obj.ToString(), out parsedInt);
    return IsNull(obj) || (parsed && parsedInt == 0);
}

有更好的方法吗?

13个回答

187

我喜欢 if ((item.Rate ?? 0) == 0) { }

更新1:

你也可以定义一个扩展方法,例如:

public static bool IsNullOrValue(this double? value, double valueToCheck)
{
    return (value??valueToCheck) == valueToCheck;
}

使用方法如下:

if(item.IsNullOrValue(0)){} // 但你不会从中得到太多信息


3
谢谢 - 非常简洁!我担心可读性,但得出结论,如果我真正理解了“??”操作符,那么它将是完全可读的。 - nailitdown
2
你应该使用扩展方法;虽然在编写时它很易读,但这些小代码块需要一点思考,这意味着如果你尝试阅读代码并使用它,你会分心于你的主要问题。 - configurator
11
@nailitdown:不完全是这样,你只需要为Nullable<T>编写一个扩展方法。double?是Nullable<double>的别名。你的签名将如下所示:public static bool IsNullOrValue<T>(this Nullable<T> value, T valueToCheck) where T : struct - Joshua Shannon
3
@nailitdown:(第二部分)然后你的返回语句看起来就像 return (value ?? default(T)).Equals(valueToCheck); - Joshua Shannon
3
如果你将它缩写为“IsNullOr(0)”,那自然会被理解为“是null或零”。 - ChrisFox
显示剩余4条评论

59

虽然我很喜欢被接受的答案,但我认为为了完整起见,也应该提及这个选项:

if (item.Rate.GetValueOrDefault() == 0) { }

这个解决方案:


¹ 不过,这不应该影响你的决定,因为这些微小的优化不太可能有任何差异。


46
使用泛型:
static bool IsNullOrDefault<T>(T value)
{
    return object.Equals(value, default(T));
}

//...
double d = 0;
IsNullOrDefault(d); // true
MyClass c = null;
IsNullOrDefault(c); // true

如果T是一个引用类型,将会把valuenulldefault(T))进行比较;否则,如果T是一个值类型,比如double,则default(t)为0d,对于bool类型是false,对于char类型是'\0'等等...

2
扩展方法:public static bool IsNullOrValue(this T? value, T valueToCheck) where T : struct { return (value ?? default(T)).Equals(valueToCheck); } - Behzad Ebrahimi
1
我喜欢这个解决方案的简洁性。请记住,default(int?)null 而不是 0。所有可空值类型(整数数字)的默认值都是 null,并且如果在此方法中传递 0,则会返回 false。详情请参阅:https://learn.microsoft.com/en-us/dotnet/csharp/language-reference/builtin-types/default-values - ttugates

29

C#9: 你可以编写

if (item.Rate is null or 0)

1
对我来说,这看起来比任何 ?? 解决方案都更好、更易读。 - FreshOuttaTheBag

20

这实际上只是Freddy Rios已接受答案的扩展,只不过使用了泛型。

public static bool IsNullOrDefault<T>(this Nullable<T> value) where T : struct
{
    return default(T).Equals( value.GetValueOrDefault() );
}

public static bool IsValue<T>(this Nullable<T> value, T valueToCheck) where T : struct
{
    return valueToCheck.Equals((value ?? valueToCheck));
}

注意:我们不需要检查default(T)是否为null,因为我们处理的是值类型或结构体!这也意味着我们可以安全地假设T valueToCheck不会为null;在此请记住,T?是Nullable<T>的简写,因此通过向Nullable<T>添加扩展,我们得到了int?、double?、bool?等中的方法。

示例:

double? x = null;
x.IsNullOrDefault(); //true

int? y = 3;
y.IsNullOrDefault(); //false

bool? z = false;
z.IsNullOrDefault(); //true

3
使用C# 7.0或更高版本,您可以使用is关键字来匹配对象与模式,如下所示:(参见is operator - C# reference | Microsoft
if (item is { Rate: 0 or null })
{
  // Do something
}

这个答案,或者非常相似的内容,已经被Eric Bole-Feysot发布过了。 - LordWilmore

2

我赞成使用 ?? 运算符。

如果你在处理字符串,使用 if(String.IsNullOrEmpty(myStr))


2
您的代码示例将会失败。如果obj为空,那么obj.ToString()将会导致空引用异常。我建议在您的辅助函数开头检查一个空的obj。至于您真正的问题是,您要检查什么类型的null或者zero?对于字符串,有一个很好的IsNullOrEmpty函数,似乎这是实现在int?类型上的一个IsNullOrZero方法的扩展方法的好用途。
编辑:请记住,'?'只是INullable类型的编译器糖,所以您可能可以将INullable作为参数,然后只需将其与null(parm == null)进行比较,如果不是null,则将其与零进行比较。

感谢你发现了那个 bug - 在这个项目中,我需要检查 int?、double?、decimal?等类型,这就是为什么我使用“object”的原因。 - nailitdown
请记住,'?' 只是编译器对 INullable<T> 类型的语法糖,因此您可以将 INullable<T> 作为参数,然后仅将其与 null 进行比较(parm == null),如果不是 null,则将其与零进行比较。 - Walden Leverich

2
如果您真的在寻找更好的方法,可以在Rate之上添加另一层抽象。这里有一些我刚刚使用Nullable设计模式想出来的东西。
using System;
using System.Collections.Generic;
namespace NullObjectPatternTest { public class Program { public static void Main(string[] args) { var items = new List { new Item(RateFactory.Create(20)), new Item(RateFactory.Create(null)) };
PrintPricesForItems(items); }
private static void PrintPricesForItems(IEnumerable items) { foreach (var item in items) Console.WriteLine("Item Price: {0:C}", item.GetPrice()); } }
public abstract class ItemBase { public abstract Rate Rate { get; } public int GetPrice() { //无需检查Rate == 0或者Rate == null return 1 * Rate.Value; } }
public class Item : ItemBase { private readonly Rate _Rate; public override Rate Rate { get { return _Rate; } } public Item(Rate rate) { _Rate = rate; } }
public sealed class RateFactory { public static Rate Create(int? rateValue) { if (!rateValue || rateValue == 0) return new NullRate(); return new Rate(rateValue); } }
public class Rate { public int Value { get; set; } public virtual bool HasValue { get { return (Value > 0); } } public Rate(int value) { Value = value; } }
public class NullRate : Rate { public override bool HasValue { get { return false; } } public NullRate() : base(0) { } } }

1
我认为你说得对,在较早的阶段消除空值可能是正确的做法。 - nailitdown
不完全是。有一个叫做“重构”的概念。您可以将代码重构为更好的模式或更好的结构。您始终可以在后期消除可空值。 - dance2die

0
public static bool nz(object obj)
{
    return obj == null || obj.Equals(Activator.CreateInstance(obj.GetType()));
}

1
反射是慢的,要小心使用这样的解决方案。我不反对反射,但我不会期望在一个“简单”的函数中使用它,这会让我措手不及。 - Walden Leverich
这实际上检查零吗? - nailitdown
实际上不是这样的。我相信对于可空类型,Activator.CreateInstance(obj.GetType())将返回null。 - configurator
@configurator:当可空类型被装箱时,它们会被装箱为基础结构体,也就是说,这不会构造一个空值可空类型,而是构造一个默认值非空结构体。 - Eamon Nerbonne

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接