我总是使用Nullable<>。HasValue,因为我喜欢这个语义。然而,最近我正在研究别人的现有代码库,他们专门使用Nullable<> != null。
有理由使用其中一种而不是另一种吗,还是纯粹的偏好?
int ?一个; 如果(a.HasValue) / /……
vs.
int ?b; If (b != null) / /……
我总是使用Nullable<>。HasValue,因为我喜欢这个语义。然而,最近我正在研究别人的现有代码库,他们专门使用Nullable<> != null。
有理由使用其中一种而不是另一种吗,还是纯粹的偏好?
int ?一个; 如果(a.HasValue) / /……
vs.
int ?b; If (b != null) / /……
当前回答
第二种方法会有效很多倍(主要是因为编译器内联和装箱,但数字仍然非常有表现力):
public static bool CheckObjectImpl(object o)
{
return o != null;
}
public static bool CheckNullableImpl<T>(T? o) where T: struct
{
return o.HasValue;
}
基准测试:
BenchmarkDotNet=v0.10.5, OS=Windows 10.0.14393
Processor=Intel Core i5-2500K CPU 3.30GHz (Sandy Bridge), ProcessorCount=4
Frequency=3233539 Hz, Resolution=309.2587 ns, Timer=TSC
[Host] : Clr 4.0.30319.42000, 64bit RyuJIT-v4.6.1648.0
Clr : Clr 4.0.30319.42000, 64bit RyuJIT-v4.6.1648.0
Core : .NET Core 4.6.25009.03, 64bit RyuJIT
Method | Job | Runtime | Mean | Error | StdDev | Min | Max | Median | Rank | Gen 0 | Allocated |
-------------- |----- |-------- |-----------:|----------:|----------:|-----------:|-----------:|-----------:|-----:|-------:|----------:|
CheckObject | Clr | Clr | 80.6416 ns | 1.1983 ns | 1.0622 ns | 79.5528 ns | 83.0417 ns | 80.1797 ns | 3 | 0.0060 | 24 B |
CheckNullable | Clr | Clr | 0.0029 ns | 0.0088 ns | 0.0082 ns | 0.0000 ns | 0.0315 ns | 0.0000 ns | 1 | - | 0 B |
CheckObject | Core | Core | 77.2614 ns | 0.5703 ns | 0.4763 ns | 76.4205 ns | 77.9400 ns | 77.3586 ns | 2 | 0.0060 | 24 B |
CheckNullable | Core | Core | 0.0007 ns | 0.0021 ns | 0.0016 ns | 0.0000 ns | 0.0054 ns | 0.0000 ns | 1 | - | 0 B |
基准测试代码:
public class BenchmarkNullableCheck
{
static int? x = (new Random()).Next();
public static bool CheckObjectImpl(object o)
{
return o != null;
}
public static bool CheckNullableImpl<T>(T? o) where T: struct
{
return o.HasValue;
}
[Benchmark]
public bool CheckObject()
{
return CheckObjectImpl(x);
}
[Benchmark]
public bool CheckNullable()
{
return CheckNullableImpl(x);
}
}
使用https://github.com/dotnet/BenchmarkDotNet
因此,如果你有一个选项(例如编写自定义序列化器)来处理Nullable在不同的管道与对象-并使用他们的特定属性-这样做,并使用Nullable特定的属性。 因此,从一致的思维角度来看,HasValue应该是首选。一致的思考可以帮助你写出更好的代码,不要在细节上花费太多时间。
附言:人们说“因为思维一致,所以更喜欢HasValue”的建议是毫无意义的。你能预测它的性能吗?
public static bool CheckNullableGenericImpl<T>(T? t) where T: struct
{
return t != null; // or t.HasValue?
}
PPS人们继续减,似乎没有人试图预测CheckNullableGenericImpl的性能。我会告诉你:编译器不会帮助你用HasValue替换!=null。如果对性能感兴趣,应该直接使用HasValue。
其他回答
在VB。Net,当你可以使用. hasvalue时,不要使用IsNot Nothing。我刚刚解决了一个“操作可能会破坏运行时”的中等信任错误,将IsNot Nothing替换为. hasvalue。我不太明白为什么,但是在编译器中发生了一些不同的事情。我认为c#中的!= null可能有同样的问题。
我更喜欢(a != null),以便语法匹配引用类型。
如果你使用linq并且想要保持你的代码简短,我建议总是使用!=null
原因如下:
假设我们有一个类Foo,它有一个可空的双变量SomeDouble
public class Foo
{
public double? SomeDouble;
//some other properties
}
如果在代码中的某个地方,我们想从Foo集合中获取所有具有非空SomeDouble值的Foo(假设集合中的一些Foo也可以为空),我们最终至少有三种方法来编写我们的函数(如果我们使用c# 6):
public IEnumerable<Foo> GetNonNullFoosWithSomeDoubleValues(IEnumerable<Foo> foos)
{
return foos.Where(foo => foo?.SomeDouble != null);
return foos.Where(foo=>foo?.SomeDouble.HasValue); // compile time error
return foos.Where(foo=>foo?.SomeDouble.HasValue == true);
return foos.Where(foo=>foo != null && foo.SomeDouble.HasValue); //if we don't use C#6
}
在这种情况下,我建议总是选择较短的那条
第二种方法会有效很多倍(主要是因为编译器内联和装箱,但数字仍然非常有表现力):
public static bool CheckObjectImpl(object o)
{
return o != null;
}
public static bool CheckNullableImpl<T>(T? o) where T: struct
{
return o.HasValue;
}
基准测试:
BenchmarkDotNet=v0.10.5, OS=Windows 10.0.14393
Processor=Intel Core i5-2500K CPU 3.30GHz (Sandy Bridge), ProcessorCount=4
Frequency=3233539 Hz, Resolution=309.2587 ns, Timer=TSC
[Host] : Clr 4.0.30319.42000, 64bit RyuJIT-v4.6.1648.0
Clr : Clr 4.0.30319.42000, 64bit RyuJIT-v4.6.1648.0
Core : .NET Core 4.6.25009.03, 64bit RyuJIT
Method | Job | Runtime | Mean | Error | StdDev | Min | Max | Median | Rank | Gen 0 | Allocated |
-------------- |----- |-------- |-----------:|----------:|----------:|-----------:|-----------:|-----------:|-----:|-------:|----------:|
CheckObject | Clr | Clr | 80.6416 ns | 1.1983 ns | 1.0622 ns | 79.5528 ns | 83.0417 ns | 80.1797 ns | 3 | 0.0060 | 24 B |
CheckNullable | Clr | Clr | 0.0029 ns | 0.0088 ns | 0.0082 ns | 0.0000 ns | 0.0315 ns | 0.0000 ns | 1 | - | 0 B |
CheckObject | Core | Core | 77.2614 ns | 0.5703 ns | 0.4763 ns | 76.4205 ns | 77.9400 ns | 77.3586 ns | 2 | 0.0060 | 24 B |
CheckNullable | Core | Core | 0.0007 ns | 0.0021 ns | 0.0016 ns | 0.0000 ns | 0.0054 ns | 0.0000 ns | 1 | - | 0 B |
基准测试代码:
public class BenchmarkNullableCheck
{
static int? x = (new Random()).Next();
public static bool CheckObjectImpl(object o)
{
return o != null;
}
public static bool CheckNullableImpl<T>(T? o) where T: struct
{
return o.HasValue;
}
[Benchmark]
public bool CheckObject()
{
return CheckObjectImpl(x);
}
[Benchmark]
public bool CheckNullable()
{
return CheckNullableImpl(x);
}
}
使用https://github.com/dotnet/BenchmarkDotNet
因此,如果你有一个选项(例如编写自定义序列化器)来处理Nullable在不同的管道与对象-并使用他们的特定属性-这样做,并使用Nullable特定的属性。 因此,从一致的思维角度来看,HasValue应该是首选。一致的思考可以帮助你写出更好的代码,不要在细节上花费太多时间。
附言:人们说“因为思维一致,所以更喜欢HasValue”的建议是毫无意义的。你能预测它的性能吗?
public static bool CheckNullableGenericImpl<T>(T? t) where T: struct
{
return t != null; // or t.HasValue?
}
PPS人们继续减,似乎没有人试图预测CheckNullableGenericImpl的性能。我会告诉你:编译器不会帮助你用HasValue替换!=null。如果对性能感兴趣,应该直接使用HasValue。
编译器将null比较替换为对HasValue的调用,因此没有真正的区别。选择对你和你的同事来说更有可读性/更有意义的就行。