我们注意到,用C#(或Java)开发的软件中的许多错误都导致NullReferenceException。
为什么在语言中甚至包含了“ null”?
毕竟,如果没有“ null”,那么我就不会有错误,对吧?
换句话说,如果没有null,该语言的什么功能将无法正常工作?
我们注意到,用C#(或Java)开发的软件中的许多错误都导致NullReferenceException。
为什么在语言中甚至包含了“ null”?
毕竟,如果没有“ null”,那么我就不会有错误,对吧?
换句话说,如果没有null,该语言的什么功能将无法正常工作?
Answers:
“ C#父亲” Anders Hejlsberg在接受Computerworld采访时谈到了这一点:
例如,在类型系统中,值和引用类型以及类型的可空性之间没有分隔。这听起来有些怪异或有点技术性,但是在C#中,引用类型可以为null,例如字符串,但值类型不能为null。拥有非空引用类型肯定会很好,因此您可以声明“此字符串永远不能为null,并且我希望编译器检查我在此处永远不能命中空指针”。
人们今天遇到的错误中有50%是在我们的平台上使用C#编码的,对于Java同样如此,它们可能是null引用异常。如果我们有一个更强大的类型系统,可以让您说“此参数永远不会为空,那么您的编译器请在每次调用时通过对代码进行静态分析进行检查”。这样我们就可以消除错误类别。
Cyrus Najmabadi,C#团队的前软件设计工程师(现在Google工作)在他的博客(1st,2nd,3rd,4th)上讨论了该主题。似乎采用非空类型的最大障碍是符号会打扰程序员的习惯和代码库。大约70%的C#程序引用可能最终以不可空值引用。
如果您真的想在C#中使用不可为空的引用类型,则应尝试使用Spec# ,它是一种C#扩展名,允许使用“!”。作为不可为空的符号。
static string AcceptNotNullObject(object! s)
{
return s.ToString();
}
空是引用类型的自然结果。如果有引用,则它必须引用某个对象-或为null。如果要禁止null,则始终必须确保每个变量都使用一些非null表达式进行了初始化-即使这样,如果在初始化阶段读取了变量,也会遇到问题。
您将如何建议删除无效性的概念?
像面向对象编程中的许多事情一样,这一切都可以追溯到ALGOL。托尼·霍尔(Tony Hoare)只是称其为“十亿美元的错误”。如果有的话,那就轻描淡写了。
关于如何使可空性不是Java中的默认值,这是一个非常有趣的论点。与C#的相似之处是显而易见的。
C#中的Null大部分是C ++的遗留物,C ++的指针没有指向内存中的任何内容(或更确切地说,adress 0x00
)。在这次采访中,Anders Hejlsberg说他希望在C#中添加不可为空的引用类型。
空也有一个类型系统中的合法地位,但是,作为一个类似于在底部类型(其中object
为顶级型)。在lisp中,底部类型为NIL
,在Scala中为Nothing
。
它会一直可以设计C#没有任何零点但你不得不拿出的用途,人们通常有一个可以接受的解决方案null
,如unitialized-value
,not-found
,default-value
,undefined-value
,和None<T>
。如果C ++和Java程序员确实取得了成功,那么他们的采用率可能会降低。至少直到他们看到C#程序再也没有任何空指针异常为止。
删除null并不能解决很多问题。您需要为init上设置的大多数变量提供默认引用。代替空引用异常,您将得到意外的行为,因为该变量指向错误的对象。至少空引用会快速失败,而不是导致意外行为。
您可以查看空对象模式,以解决该问题的一部分
Null是一个非常强大的功能。如果缺少价值该怎么办?是空的!
一种思想是永不返回空值,另一种思想是永远返回空值。例如,有人说您应该返回一个有效但为空的对象。
对于我来说,我更喜欢null,它更真实地表明了它的真实含义。如果我无法从持久层中检索实体,则需要null。我不要空值。就是我
它对于基元特别方便。例如,如果我具有true或false,但是它用于安全性表单,则可以设置为Allow,Deny或未设置权限。好吧,我希望不要将其设置为null。所以我可以用布尔?
我还有很多可以继续的事情,但是我会留在那里。
毕竟,如果没有“ null”,那么我就不会有错误,对吧?
答案是否定的。问题不是C#允许null,而是问题是您碰巧用NullReferenceException表现出来的错误。如前所述,空值在语言中的用途是指示“空”引用类型或非值(空/无/未知)。
Null不会导致NullPointerExceptions ...
程序员导致NullPointerExceptions。
如果没有空值,我们将返回使用实际的任意值来确定函数或方法的返回值无效。您仍然必须检查返回的-1(或其他值),删除null不会神奇地解决惰性问题,但会对其进行模糊处理。
语言中包含“ Null”,因为我们有值类型和引用类型。这可能是副作用,但我认为这是一个很好的副作用。它为我们有效管理内存提供了强大的力量。
为什么我们为空?...
值类型存储在“堆栈”中,它们的值直接位于该内存中(即,int x = 5表示该变量的内存位置包含“ 5”)。
另一方面,引用类型在堆栈上有一个“指针”指向堆上的实际值(即字符串x =“ ello”意味着堆栈上的内存块仅包含一个指向堆上实际值的地址) )。
空值只是意味着我们在堆栈上的值不指向堆上的任何实际值-这是一个空指针。
希望我解释得足够好。
在某些情况下,null是表示未初始化引用的好方法。这在某些情况下很重要。
例如:
MyResource resource;
try
{
resource = new MyResource();
//
// Do some work
//
}
finally
{
if (resource != null)
resource.Close();
}
在大多数情况下,这是通过使用using实现的语句。但是该模式仍然被广泛使用。
关于您的NullReferenceException,通常可以通过实施编码标准(所有参数均检查有效性)来减少此类错误的原因。根据项目的性质,我发现在大多数情况下足以检查暴露成员的参数。如果参数不在预期范围内,则根据使用的错误处理模式,抛出某种ArgumentException或返回错误结果。
参数检查本身并不能消除错误,但是在测试阶段更容易定位和纠正发生的任何错误。
作为说明,安德斯·海斯伯格提到缺乏非空强制执行是C#1.0规范中的最大错误之一,现在将其包括在内是“困难的”。
如果您仍然认为静态强制的非null参考值非常重要,则可以查看spec#语言。它是C#的扩展,其中非空引用是语言的一部分。这确保了标记为非空的引用永远不会被分配空引用。
一个答复提到数据库中存在空值。是的,但是它们与C#中的null有很大不同。
在C#中,空值是不引用任何内容的引用的标记。
在数据库中,空值是不包含值的值单元的标记。值单元通常指表中行与列的交集,但是值单元的概念可以扩展到表之外。
乍一看,两者之间的区别似乎微不足道。但事实并非如此。
C#/ C ++ / Java / Ruby中提供的Null最好被视为某种晦涩的过去(Algol)的奇怪之处,这些过去以某种方式得以幸存至今。
您有两种使用方式:
如您所料,1)是导致我们使用常见命令性语言引起无尽麻烦的原因,应该早就被禁止,2)是真正的基本功能。
有一些语言可以避免1)而不阻止2)。
例如,OCaml是这种语言。
一个简单的函数返回一个从1开始的递增整数:
let counter = ref 0;;
let next_counter_value () = (counter := !counter + 1; !counter);;
关于可选性:
type distributed_computation_result = NotYetAvailable | Result of float;;
let print_result r = match r with
| Result(f) -> Printf.printf "result is %f\n" f
| NotYetAvailable -> Printf.printf "result not yet available\n";;
我无法说出您的具体问题,但听起来问题不在于null的存在。数据库中存在空值,您需要某种方法在应用程序级别解决该问题。请注意,我认为这不是它存在于.net中的唯一原因。但我认为这是原因之一。
NULL
通常表示为DBNull.Value
代替null
。
令我惊讶的是,没有人谈论数据库的答案。数据库具有可为空的字段,并且将从数据库接收数据的任何语言都需要处理该字段。这意味着具有空值。
实际上,这是如此重要,以至于对于int这样的基本类型,您可以将它们设置为可为空!
还要考虑函数的返回值,如果您想让一个函数除以几个数字并且分母可以为0,该怎么办?在这种情况下,唯一的“正确”答案将为空。(我知道,在这样一个简单的示例中,异常可能是一个更好的选择……但是在某些情况下,所有值都是正确的,但是有效数据会产生无效或无法计算的答案。不确定在这种情况下应使用异常情况...)
除了已经提到的所有原因之外,当您需要一个尚未创建的对象的占位符时,还需要NULL。例如。如果您在一对对象之间有一个循环引用,则需要null,因为您不能同时实例化这两个对象。
class A {
B fieldb;
}
class B {
A fielda;
}
A a = new A() // a.fieldb is null
B b = new B() { fielda = a } // b.fielda isnt
a.fieldb = b // now it isnt null anymore
编辑:您也许可以提取一种没有空值的语言,但是它绝对不是面向对象的语言。例如,序言没有空值。
对不起,回答了四年之后,我很惊讶,到目前为止,没有答案以这种方式回答了原始问题:
像C#和Java这样的语言(如C和之前的其他语言)都拥有,null
因此程序员可以通过高效地使用指针来编写快速,优化的代码。
首先有一点历史。null
发明的原因是为了提高效率。在汇编中进行低级编程时,没有抽象,寄存器中有值,并且您想充分利用它们。将零定义为无效的指针值是一种表示对象或什么都不表示的极好的策略。
当您可以零开销,真正快速地实现可选值模式时,为什么要浪费一个完美的内存字的大多数可能值?这就是为什么null
如此有用的原因。
从语义上讲,null
对于编程语言而言,它绝对不是必需的。例如,在像Haskell这样的经典功能语言或ML系列中,没有空值,而是名为Maybe或Option的类型。它们代表了可选价值的更高层次的概念而与生成的汇编代码的外观无关(这将是编译器的工作),而无需任何考虑。
这也非常有用,因为它使编译器可以捕获更多错误,这意味着更少的NullReferenceExceptions。
与这些非常高级的编程语言相比,C#和Java允许每种引用类型都可能为null (这是该类型的另一个名称,最终将使用指针实现)。
这似乎是一件坏事,但它的好处是程序员可以利用其幕后工作知识来创建更有效的代码(即使该语言具有垃圾回收功能)。
这就是为什么null
如今的语言仍然存在的原因:在对可选价值的一般概念的需求与对效率的永恒需求之间进行权衡。
没有null就无法工作的功能是能够表示“缺少对象”。
缺少对象是一个重要的概念。在面向对象的编程中,我们需要它来表示可选的对象之间的关联:对象A可以附加到对象B,或者A可能没有对象B。如果没有null,我们仍然可以模拟这一点:例如我们可以使用对象列表将B与A关联。该列表可以包含一个元素(一个B),也可以为空。这有点不方便,并不能真正解决任何问题。假定存在一个B的代码,例如aobj.blist.first().method()
将以类似于null引用异常的方式爆炸:(如果blist
为空,则B的行为是什么blist.first()
?什么?)
说到列表,使用null可以终止链接列表。阿ListNode
可以包含到另一个参考ListNode
,其可以为空。可以说其他动态集结构(例如树)也是如此。使用Null,您可以拥有一棵普通的二叉树,其子节点通过具有null的子引用来标记。
列表和树可以不带null来构建,但是它们必须是循环的,否则是无限/惰性的。对于大多数程序员而言,这可能被认为是无法接受的约束,他们更愿意在设计数据结构时选择。
与null引用相关的痛苦,例如由于错误和引起异常而意外产生的null引用,部分是静态类型系统的结果,该静态类型系统将null值引入每种类型:null字符串,null整数,null小部件, ...
在动态类型的语言中,可以有一个空对象,它具有自己的类型。这样做的结果是您具有null的所有代表性优点,并具有更高的安全性。例如,如果编写一个接受String参数的方法,那么可以保证该参数将是一个字符串对象,而不是null。String类中没有null引用:某些已知为String的对象不能为null。引用没有使用动态语言键入。诸如类成员或函数参数之类的存储位置包含一个值,该值可以作为对对象的引用。该对象具有类型,而不是引用。
因此,这些语言提供了一个纯净的,或多或少纯净的“空”模型,然后静态语言将其变成了科学怪人的怪兽。
如果框架允许创建某种类型的数组而未指定对新项目应执行的操作,则该类型必须具有一些默认值。对于实现可变引用语义(*)的类型,一般情况下没有明智的默认值。我认为.NET框架的一个缺点是无法指定非虚拟函数调用抑制任何空检查。通过返回诸如Length之类的属性的合理值,这将使诸如String之类的不可变类型表现为值类型。
(*)请注意,在VB.NET和C#中,可变引用语义可以通过类或结构类型来实现;一个struct类型将通过充当其持有不变引用的类对象的包装实例的代理来实现可变的引用语义。
如果可以指定一个类应具有不可为空的可变值类型语义(这暗示着(至少)实例化该类型的字段将使用默认构造函数创建一个新的对象实例),这也将有所帮助。复制该类型的字段将通过复制旧实例创建新实例(递归处理任何嵌套的值类型类)。
但是,尚不清楚应在框架中为此提供多少支持。使框架本身能够识别可变值类型,可变引用类型和不可变类型之间的区别,将允许类本身持有对来自外部类的可变和不可变类型的混合的引用,从而有效地避免制作不必要的深可变对象。
空是任何OO语言的基本要求。未分配对象引用的任何对象变量必须为null。