GATTACA2011

  :: :: 博问 :: 闪存 :: 新随笔 :: :: :: 管理 ::

《深入浅出 C#》 (第3版)

========== ========== ==========
[作者] (美) Andrew Stellman (美) Jennifer Greene
[译者] (中) 徐阳 丁小峰 等译
[出版] 中国电力出版社
[版次] 2016年08月 第1版
[印次] 2018年04月 第4次 印刷
[定价] 148.00元
========== ========== ==========

【引子】

要学习编程,没有别的办法,只能通过编写大量代码。

编写代码是一种技巧,要想在这方面擅长,只能通过实践。

【第01章】

(P007)

IDE 的优点在于,它能自动地为你生成一些代码,但是它的作用仅此而已。

不过,对于编程中最困难的部分,也就是确定你的程序要做什么以及如何让它能真正做到, IDE 就无能为力了。

IDE 为你做的所有一切,包括它创建的每一行代码,增加的每一个文件,都是可以修改的,你可以直接手动地编辑文件,也可以通过 IDE 简便易用的界面来修改。

【第02章】

(P054)

IDE 是一个功能强大的工具,不过仅此而已,这只是一个可供使用的工具。

(P056)

可以认为 IDE 是一种方便的文件编辑器。它会自动为你完成缩进,改变关键字的颜色,完成括号匹配,甚至还能建议下一个可能的词是什么。不过,归根结底,IDE 所做的只是编辑文件,这些文件包含了你的程序。

(P057)

Windows Runtime 和 .NET Framework 中的工具划分为不同的命名空间 (namespace) 。

Windows 8 中的每个程序都在一个称为 Windows Runtime (Windows 运行时) 的体系结构上运行。不过在 Windows Runtime 与你的程序之间还有另外的一 “层” ,这称为通用语言运行时库 (Common Language Runtime , CLR) 。

(P060)

每个 C# 程序的代码结构几乎完全一样。所有程序都使用了命名空间、类和方法,使代码更易于管理。

每次建立一个新程序时,要为它定义一个命名空间,使程序代码与 .NET Framework 类和 Windows Store API 类区别开。

类包含程序的一部分。

类中有一个或多个方法。方法总是放在类中。

方法由语句组成。

类文件中方法的顺序并不重要。

(P065)

只有使用了 partial 关键字时才能将一个类划分到不同文件中。

(P066)

C# 使用变量类型来限制变量能存储哪些数据。

(P067)

要想确保不会忘记对变量赋值,最容易的方法是把声明变量的语句与为变量赋值的语句结合起来。

(P076)

可以使用 && 操作符或 || 操作符把单个条件测试结合为一个长测试, && 表示与 (AND) , || 表示或 (OR) 。

(P093)

每个 C# 都必须有一个名为 Main 的方法。这个方法就是代码的入口点。

运行代码时, Main() 方法中的代码最先执行。

【第03章】

(P108)

对象 (Object) 是 C# 的一个工具,可以用来处理一组类似的事物。

(P109)

类对于对象来说就像是设计蓝图。

一旦构建一个类,可以根据需要使用 new 语句创建多个对象。创建对象后,类中的各个方法都将成为对象的一部分。

(P111)

从一个类创建新对象时,这称为创建这个类的一个实例。

(P115)

静态方法不要求有实例,而非静态方法需要先有一个实例。

(P116)

方法是对象做什么,字段是对象知道什么。

对象的行为由方法定义,另外使用字段来跟踪它的状态。

(P118)

程序创建一个新对象时,会把它增加到堆中。

(P122)

类图是一种将类画在纸上的简单方法。这是一个很有意义的工具,利用类图,可以在编写代码之前设计代码。

(P133)

对象初始化方法可以节省你的时间,使代码更紧凑,更可读 ······ 而且 IDE 会帮助你编写对象初始化方法。

【第04章】

(P141)

实际上,编程中所做的几乎每一件事都是在以这样或那样的方式处理数据。

(P142)

sbyte 中的 “s” 代表 “有符号” (signed) ,表示它可以是一个负数 (“符号” 就是一个负号) 。

“u” 代表 “无符号” (unsigned) 。

(P143)

在 C# 代码中转义序列写为两个字符,不过程序会把各个转义序列作为单个字符存储在内存中。

(P144)

所有数据都会占据内存中的一定空间。

利用变量,可以在内存中预留足够的空间来存储数据。

(P147)

将一个数值赋至 float 时,需要在这个数字后面加一个 “F” ,告诉编译器这是一个 float 而不是 double 。否则,代码将不能编译。

(P148)

使用 “+” 将一个字符串与另一种类型的某个值或变量连接时,它会自动地将数字转换为字符串。

(P149)

形参 (parameter) 是方法中定义的参数。实参 (argument) 是向方法传入的参数。

(P158)

要让一个对象留在堆中,它必须被引用。对象的最后一个引用消失一段时间后,对象也会消失。

(P170)

对象使用 this 关键字时,就是指自己,这个引用指向调用 this 的对象。

(P171)

创建一个新引用但是还没有为它设置任何对象时,它也会有一个值。刚开始时会设置为 null ,这说明它不指向任何对象。

如果一个对象有一个引用,而且以后不再使用这个对象,将它的引用设置为 null 时,就会立即将这个对象标志为可以回收 (除非别处还有这个对象的一个引用) 。

(P172)

要使用对象中的方法和字段,唯一的途径就是通过引用。

变量之所以称为变量,就是因为它们总在改变。

在一个实例化对象的代码中,实例可以使用这个特殊的 this 变量,其中包含它自己的一个引用。

【第05章】

(P211)

充分考虑到你的懒惰 —— 如果没有加上 “private” 或 “public” 声明, C# 就会认为这个字段是私有的。

(P214)

一个对象要得到另一个对象私有字段中存储的数据,只有一个办法 : 就是使用能返回该数据的公共字段和方法。

写一个类时,一定要保证为其他对象提供了一个途径来得到它们需要的数据。私有字段是封装的一个很重要的部分,但是它们并不是全部。要编写一个封装性好的类,意味着要为其他对象提供一个合理的、易于使用的途径来得到它们需要的数据,但不允许它们非法截获你的类本身依赖的数据。

一个对象要得到另一个不同类型对象的私有字段中存储的数据,唯一的途径就是使用返回该数据的公共方法。

(P217)

封装是指让一个类对另一个类隐藏信息。这有助于避免程序中的 bug 。

(P219)

如果你今天能很好地封装类,明天重用这些类会容易得多。

(P221)

如果没有合适的理由,就不要将字段或方法声明为公共。如果程序中的所有字段都声明为公共字段,可能会把问题搞得一团糟。不过也不要把一切都设置为私有。先花一些时间来考虑这个问题,哪些字段确实需要公共的,而哪些不必,以后这会为你节省很多时间。

(P226)

对象初始化方法中只能初始化公共字段和属性。

(P227)

要向类增加一个构造函数,只需要增加一个与类同名的方法,而且没有返回值。

(P229)

方法中能做的,在属性中也都可以做。

属性 (获取和设置存取方法) 是一种特殊类型的 C# 方法,只有当读写属性时才会运行。

(P231)

如果在变量前增加 this 关键字,就是在告诉编译器你所指的是字段,而不是参数。

(P234)

封装可以使你的类以后更易于理解和重用。

【第06章】

(P241)

对于只有一行代码的代码块,大括号是可选的。

(P260)

使用 override 关键字向子类增加一个方法,可以替换它继承的方法。覆盖一个方法之前,需要在基类中将这个方法标志为 virtual 。

(P265)

任何方法需要一个类作为参数时,完全可以输入扩展这个类的一个子类的实例。

(P268)

如果子类只是增加一个与超类方法同名的方法,那么它只是隐藏了超类方法而不是覆盖这个方法。

(P271)

如果你想覆盖一个基类中的方法,一定要用 virtual 关键字标志这个方法,如果希望在子类中覆盖这个方法,就要使用 override 关键字。如果没有做到这一点,很可能会无意中隐藏方法。

(P272)

即使子类覆盖了基类中的方法或属性,有时仍可能希望访问基类中的这个成员。幸运的是,可以使用 base 关键字,利用这个关键字就可以访问基类中的任何方法。

【第07章】

(P296)

使用接口要求一个类包含接口中所列的全部方法和属性。如果类没有做到这一点,编译器会报错。

只要编译代码时类中有接口要求的方法和属性,接口并不关心这些方法或属性是如何得来的。

(P297)

接口不存储数据,所以不能增加任何字段。

公共接口中的所有方法都会自动成为公共方法,因为接口就是用来定义实现该接口的类的公共方法和属性。

(P298)

接口的目的是让一个类可以完成多个任务,而不依赖于继承,因为继承会带来很多额外的负担,你必须继承每一个方法、属性和字段,而不只是与处理特定任务有关的那些成员。

(P300)

在接口中不需要输入 “public” 因为会自动将接口中的各个属性和方法置为公共。

(P303)

跟踪对象时,接口引用与对象引用同样有效。

可能并不需要一个对象引用,完全可以创建一个新对象,并把它直接赋给一个接口引用变量。

(P304)

利用 “is” 可以比较接口,也可以比较其他类型。

(P305)

由于所有接口中都没有具体的方法体,所以不需要考虑调用基类构造函数或方法。继承接口 (子接口) 只需汇集它继承的接口的所有方法和属性。

(P306)

任何类可以实现任何接口,只要它能信守承诺,实现该接口的所有方法和属性。

(P307)

“is” 指出一个对象实现了什么, “as” 则告诉编译器如何看待一个对象。

(P309)

向上强制转换唯一的缺点是,只能使用基类的属性和方法。

一旦将一个子类向上强制转换为基类,由于访问对象使用的是基类引用,所以只能访问这个基类的方法和属性。

(P310)

向下强制转换的第一步是使用 “is” 关键字检查有没有这种可能。

(P312)

如果向下强制转换是非法的, as 语句只会返回 null 。

接口中不允许有任何语句。

接口就像一个清单,编译器检查这个清单来确保类实现了指定的方法。

(P315)

我们把一个类的方法、字段和属性称为它的成员 (members) 。所有成员都可以标志 public 或 private 访问修饰符。

将一个类成员标志为 private 时,只有这个类中的成员或该类的其他实例能访问这个成员。

不能把一个类标志为 private ,除非这个类位于另一个类内部,在这种情况下,它只对它的 “容器” 类的实例可用。

类成员默认为私有,如果希望它是公共的,需要明确标识为公共。

声明一个类成员时如果没有访问修饰符,就默认为 private 。

protected 对于子类表示 public ,对其他表示 private 。

子类不能访问其基类中的私有字段,另外必须使用 base 关键字才能访问基类对象的公共成员。

标志 protected 的类成员可以由该类中所有其他成员访问,另外该类子类中的所有成员也可以访问。

声明一个类或接口时,如果没有访问修饰符,默认设置为 internal 。

如果你没有使用多个程序集, internal 对于类和接口来说就相当于 public 。

(P318)

利用属性,可以让一个东西在其他对象看来像是一个字段,但由于它实际上是一个方法,这样就不会真正存储任何数据。

接口引用只知道这个接口中定义的方法和属性。

(P320)

抽象类可以有字段和方法,而且也可以继承其他类,这与正常类是一样的。

抽象类可以包含属性和方法的声明,与接口一样,子类必须实现这些属性和方法。

抽象类和具体类之间最大的区别在于,不能使用 new 创建抽象类的实例。

如果一个方法有声明但是没有语句或方法体,这称为一个抽象方法 (abstract method) 。就像继承接口一样,继承抽象类的子类必须实现所有抽象方法。

只有抽象类可以有抽象方法。如果把一个抽象方法放在一个类中,就必须标志这个类为抽象类,否则无法编译。

(P322)

将一个类标志为 abstract 时, C# 不允许你编写代码来实例化这个类。这很像接口,它就相当于一个模板,继承它的子类都要以它为模板。

为类声明增加 abstract 关键字,这就告诉 C# 这是一个抽象类,不能实例化。

(P323)

接口中的各个方法自动作为抽象方法,所以在接口中不需要像抽象类中那样使用 abstract 关键字。

抽象类可以有抽象方法,不过也可以有具体方法。

(P330)

封装是指创建一个对象,使用私有字段在内部记录它的状态,另外通过公共属性和方法使其他类只能使用它们需要看到的那部分内部数据。

(P331)

将一个类的实例用在需要其他类型 (如父类或这个类实现的接口) 的语句或方法中,这就是在使用多态。

【第08章】

(P353)

enum 数据类型只允许某个数据取某些特定的值。

大括号里的内容称为枚举项列表 (enumerator list) ,其中每一项称为枚举项 (enumerator) 。整体称为枚举 (enumeration) 。

(P354)

可以把一个 int 强制转换为一个 enum ,还可以把一个 (基于 int 的) enum 强制转换回 int 。

(P358)

用数组来存储一个固定的值或引用列表还不错,但是如果需要移动数组元素,或者要增加超出数组容量的更多元素,就有些麻烦了。

(P363)

List 有一大特点 : 创建 List 时你不需要知道它会有多长。 List 会自动伸缩来适应它的内容。

foreach 循环也能处理数组,实际上, foreach 循环适用于任何集合。

(P367)

“泛型” (generic) 是指,尽管 List 的一个特定实例只能存储一种特定类型,但 List 类适用于任何类型。

创建一个新的 List 对象时,总要提供一个类型,告诉 C# 它会存储什么类型的数据。

(P368)

集合初始化方法可以使代码更紧凑,可以把创建列表和增加一组初始项结合在一起。

(P371)

要让 List 的内置 Sort() 方法对某个类排序,只需让这个类实现 IComparable<T> 接口,并增加一个 CompareTo() 方法。

List.Sort() 方法知道如何对实现了 IComparable<T> 接口的类或类型排序。这个接口只有一个成员,即 CompareTo() 方法。 Sort() 使用一个对象的 CompareTo() 方法与其他对象比较,并使用其返回值 (一个 int) 来确定哪一个在前。

但是有时需要对没有实现 IComparable 接口的对象列表排序,对此, .NET 中的另一个接口可以提供帮助。可以向 Sort() 传入一个实现了 IComparer<T> 的类的实例。这个接口也只有一个方法。List 的 Sort() 方法使用这个比较对象的 Compare() 方法来比较一对对象,从而得出它们在有序列表中的先后顺序。

(P373)

要使用 IComparer<T> 排序时,需要创建实现这个接口的类的一个新实例。这个对象的存在是为了帮助 List.Sort() 确定如何对数组排序。

(P377)

每个 .NET 对象都有一个名为 ToString() 的方法,这个方法可以把对象转换为一个字符串。默认地,它只是返回类名。

联接字符串的 “+” 操作符会自动调用一个对象的 ToString() 。

Console.WriteLine() 或 String.Format() 也会在传入对象时自动调用这个方法,如果你想把一个对象转换为一个字符串,这会很方便。

(P379)

集合初始化方法适用于任何 IEnumerable<T> 对象,只要它有一个 Add() 方法。

集合实现 IEnumerable<T> 时,就为你提供了一种方法,可以编写循环按顺序循环处理其中的内容。

(P380)

如果你想把一个对象集合增加到一个更通用的列表中,协变就非常有用。

List.AddRange() 方法,这个方法可以把一个列表的内容增加到另一个列表中。

(P401)

队列是先入先出型 (first-in first-out) ,这说明入队列的第一个对象也就是最先取出使用的对象。

栈是先进后出型 (first-in last-out) ,最先进入栈的对象将最后一个取出。

可以使用 foreach 循环处理栈或队列,因为它们都实现了 IEnumerable 。

【第09章】

(P410)

流 (stream) 是 .NET Framework 为程序提供的读写数据的方法。

如果希望对文件读写数据,就会使用一个 Stream 对象。

(P411)

流允许读写数据。要针对所处理的数据使用适当类型的流。

(P412)

FileStream 一次只能关联一个文件。

流向文件写字节,所以需要将要写的 string 转换为一个 byte 数组。

关闭文件,使其他程序能够访问这个文件。

如果忘记关闭流,这是一个严重的问题。如果没有关闭流,文件会被锁定,其他程序在你关闭这个流之前将无法使用这个文件。

(P413)

StreamWriter 会为你自动创建和管理一个 FileStream 对象。

可以向 StreamWriter() 构造函数传入一个文件名。如果传入了文件名,书写器 (writer) 会自动打开这个文件。 StreamWriter 还有一个重载构造函数,允许你指定它的追加模式 : 传入 true 表示要把数据增加 (或追加) 到一个现有文件的末尾,传入 false 会告诉流要删除现有文件,再创建一个同名的新文件。

如果保持流打开并关联到一个文件,就会锁定这个文件一直处于打开状态,其他程序将无法使用这个文件。所以一定要关闭文件。

(P417)

StreamReader (继承自 TextReader) 是一个从流读取字符的类。它本身不是一个流。将一个文件名传入这个类的构造函数时,它会为你创建一个流,调用它的 Close() 方法时它会关闭这个流。

StreamReader 还有一个重载的构造函数,有一个 Stream 参数。

EndOfStream 属性指出文件中是否还有未读的数据。

(P418)

可以把流串起来,一个流可以写到另一个流,而这个流又可以再写到下一个流······ 最后通常是网络或者一个文件流。

(P424)

File 类完成少量操作时速度更快,而 FileInfo 更适合完成大量任务。

(P425)

StreamReader 和 StreamWriter 会为你将字节转换为字符,这称为编码和解码。

如果只是按部就班地向一个文本文件读写文本行,那么只需要 StreamReader 和 StreamWriter 就足够了。

(P429)

在一个 using 块中声明一个对象,这个对象的 Dispose() 方法会自动调用。

对于任何实现了 IDisposable 接口的类,只要调用了它的 Dispose() 方法,会给立即释放它占用的资源。通常这是结束对象处理的最后一步。

(P430)

每个流都有一个 Dispose() 方法,它会关闭这个流。所以如果在一个 using 语句中声明流,它总会自行关闭。

所有流都实现了 IDisposable ,所以只要使用流,都应当在一个 using 语句中声明。这样能确保流总是会关闭。

可以在 using 语句上再罗列其他 using 语句,不需要另外的大括号或缩进。

(P437)

switch 语句将一个变量与多个可能值进行比较。

每个 case 都必须以 “break;” 结束,这样 C# 才能知道一种情况在哪里结束,下一个在从哪里开始。

还可以用 “return” 结束一个 case ,只要一个 case 不会继续 “落入” 下一个 case ,程序就能编译。

switch 语句的体是一系列 case 语句,将 switch 关键字后面的变量与一个特定值比较。

每个 case 都包括一个 case 关键字,后面时要比较的值和一个冒号。然后是一系列语句,最后是 “break;” 。如果这个 case 中的值与比较值匹配,就会执行这些语句。

(P444)

将对象复制到文件或者从文件读出一个对象很简捷。可以完成串行化和逆串行化。

(P451)

使用 File.Create() 时,这会创建一个新文件,如果已经有这样一个文件,这个方法会把原文件删除而创建一个全新的文件。另外还有一个 File.OpenWrite() 方法,它会打开原来的文件,并从头开始覆盖。

【第10章】

(P498)

定义页面控件的 XAML 会成为一个 Page 对象,它的字段和属性包含这些 UI 控件的运用。

(P512)

XAML 中的数据绑定是指源属性与目标属性之间的一种关系,源属性是为控件提供数据的对象 (数据对象) 的一个属性,目标属性是显示该数据的控件 (控件对象) 的一个属性。要建立数据绑定,控件的数据上下文必须设置为这个数据对象的一个引用。必须将控件的绑定 (binding) 设置为一个绑定路径,就是要绑定到对象的这个属性。一旦完成这些设置,控件就会自动读取源属性,并作为控件的内容显示这个数据。

(P513)

.NET 提供了 ObservableCollection<T> ,这是一个专门为数据绑定建立的集合类。

创建 XAML 代码实现数据绑定时,它会使用一个 Binding 对象的实例来建立绑定,这个对象把目标属性的名字存储为一个字符串。

(P525)

页面中的静态资源会在页面首次加载时实例化,应用中的对象可以在任何时候使用这些静态资源。

(P526)

可以让数据对象通知它们的目标属性和绑定控件,告诉它们数据已经改变。你要做的就是实现 INotifyPropertyChanged 接口,这个接口中包含一个名为 PropertyChanged 的事件。只要一个属性有变化,就会触发这个事件,可以看到绑定控件会自动自行更新。

(P527)

要通知一个绑定控件某个属性有变化,你要做的就是调用 OnPropertyChanged() ,并提供发生变化的那个属性的名字。

【第11章】

(P544)

可以使用一个新技术重新构建你之前已经构建的一个程序,这是掌握这种新技术的一个非常好的方法。

方法中有一个 await 时,在这个方法的声明中必须有一个 async 。

(P546)

使用二进制串行化时,你写的是 “纯” 数据 : 会把内存中的实际字节连起来,写到一个文件中,另外会为二进制格式化工具提供足够的信息,使它能确定哪些字节对应对象图中的哪些类成员。

(P547)

数据契约 (data contract) 是与类关联的一个正式约定。这个契约使用 [DataContract] 和 [DataMember] 属性来定义串行化时要读写什么数据。

如果你想串行化一个类的实例,可以为它建立一个数据契约,在最上面增加 [DataContract] 属性,然后为要串行化的各个类成员增加 [DataMember] 属性。

(P557)

要让一个 async 方法调用另一个异步方法,被调用的方法的返回类型必须是 Task 类 (或者,如果这个方法需要返回一个值,返回类型也可以是它的子类 Task<T>) 。

推荐的命名约定是在使用 await 操作符调用的异步方法名末尾增加 Async 。

async 修饰符、 await 关键字以及 Task 类都是为了更容易地编写异步代码。

【第12章】

(P585)

把可能抛出异常的代码放在 try 块中。如果没有发生异常,它会正常地运行, catch 块中的语句将被忽略。不过,如果 try 块中的某个语句确实抛出了一个异常, try 块中的其余语句就不会再执行。

(P587)

Watch 窗口中完成的所有修改只会影响内存中的数据,而且只持续到程序运行结束。重启程序时,在 Watch 窗口中修改的值又会还原。

(P588)

把断点加在 try 块的开始大括号上。

(P595)

在 catch 块中指定一个异常类型时,如果提供了一个变量名,代码就可以使用这个变量名来访问这个 Exception 对象。

(P601)

要记住,在 “using” 语句中声明一个引用时,会在这个块末尾自动调用它的 Dispose() 方法。

使用 using 语句时,就是在充分利用 finally 来确保总会调用它的 Dispose() 方法。

(P602)

IDisposable 是避免常见异常和问题的一个非常有效的方法。处理实现了这个接口的类时,一定要使用 using 语句。

只有当一个类实现了 IDisposable 时,才能在 “using” 语句中使用这个类;否则,程序将不能编译。

如果想在 using 语句中使用你的对象,它必须实现 IDisposable 。

IDisposable 接口只有一个成员 : Dispose() 方法。这个方法中的所有代码都会在 using 语句的最后执行 ······ 或者手动调用 Dispose() 时执行。

实现 Dispose 的一个原则是你的 Dispose() 方法可以调用多次而没有任何副作用。

(P604)

要记住,如果你的代码没有处理一个异常,这个异常会在调用栈中向上传递。让异常向上传递是一种完全合法的异常处理方法,有时这比使用一个 try / catch 块来处理异常可能更合适。

(P606)

只要使用流就应当使用 using 块! 切记,切记,切记!

【第13章】

(P618)

最终化方法不能有参数,因为只需要告诉 .NET “搞定!” 就行了,除此之外不需要再说其他的。

(P619)

对象的最终化方法在所有引用都消失之后运行,而且是在对象被垃圾回收之前。对象的所有引用都消失时才会发生垃圾回收。不过,并不是引用一消失就进行垃圾回收。

如果不是一个 “玩具” 性的程序,就不要使用 GC.Collect() ,这是一个很糟糕的做法,这一点再强调也不为过,因为这会干扰 CLR 的垃圾回收器。但这个方法非常适合用来学习垃圾回收和最终化方法。

(P627)

struct 可以实现接口,不过不能派生其他类,另外 struct 是密封的,所以不能派生 struct 。

struct 不是对象,它们可以有方法和字段,但是不能有最终化方法。 struct 不能继承其他类或 struct ,也不能被其他的类或 struct 继承。

(P630)

设置一个 struct 等于另一个 struct 时,就是在为该 struct 中的数据创建一个全新的副本。这是因为 struct 是一个值类型。

(P634)

通过使用 out 参数,方法可以返回多个值。

每次调用一个有 out 参数的方法时,传入实参时都要使用 out 关键字。

(P636)

如果希望方法有默认值,可以使用可选参数和命名参数。

(P637)

只需要在值类型后面增加一个问号 (?) ,它就会变成一个可为空的类型,可以将它设置为 null 。

每个可为空的类型都有一个名为 Value 的属性,可以获取或设置这个值。

它们还有一个名为 HasValue 的属性,如果值不为 null 就会返回 true 。

值类型都可以转换为一个可为空的类型。

不过要把可为空的类型再赋给一个值类型,则需要进行强制转换。

如果 HasValue 为 false , Value 属性会抛出一个 InvalidOperationException 异常,强制类型转换也同样会抛出这个异常 (因为强制转换等价于使用 Value 属性) 。

(P640)

如果希望为类提供很好的封装, struct 会很有意义,因为返回一个 struct 的只读属性会建立它的一个全新副本。

(P642)

扩展方法总是静态方法,而且必须放在静态类中。

(P644)

定义扩展方法的类必须是一个静态类。

(P648)

记住, as 关键字只用于类,不能用于 struct 。

【第14章】

(P653)

使用 LINQ 调用的方法实际上就是用来扩展数组的一些扩展方法。

(P667)

Take() 从 LINQ 查询的第一个结果集中取出指定数目的数据项。可以把这些数据项放在另一个 var 中,然后再把它转换到一个列表中。

利用 LINQ ,可以编写查询使用很少的代码完成非常复杂的工作。

(P670)

from 子句会完成两个工作 : 它告诉 LINQ 查询使用哪个集合,另外为要查询的集合中的各个成员指定一个名字。

select 子句告诉 LINQ 这个序列中应该有什么。

(P679)

每个组包含一个共同的成员,称为“组键”。使用 “by” 关键字来指定组键。每个组序列有一个 Key 成员,其中包含这个组的组键。

【第15章】

(P702)

你希望对象只考虑它自己,而不考虑其他对象。这里就分离了各个对象的关注点。

(P704)

一个事件有一个发布者,而且可以有多个订购者。

(P705)

事件采用先来先服务的原则进行处理,最先订购的对象最先得到通知。

(P706)

event 关键字后面是 EventHandler 。它不是 C# 的保留字,这是 .NET 中定义的一个关键字。之所以需要这个关键字,是为了告诉订购这个事件的对象 : 它们的事件处理方法应该是什么样子。

使用 EventHandler 时,就是在告诉其他对象它们的事件处理方法应当有两个参数,一个是名为 sender 的 object ,另一个参数是一个 EventArgs 引用,名为 e 。 sender 是产生事件的那个对象的引用, e 是 EventArgs 对象的一个引用。

(P709)

EventHandler 的作用就是定义事件的签名,它告诉订购这个事件的对象该如何定义事件处理方法。

一个事件总由一个对象产生。但是一个事件可以由多个对象响应。

(P716)

EventHandler 的泛型参数必须是 EventArgs 的一个子类。

(P731)

创建一个委托时,只需要为这个委托指向的方法指定方法签名。

为工程增加一个委托时,就是在增加一个委托类型 (delegate type) 。用它创建一个字段或变量时,就是在创建这个委托类型的一个实例。

(P732)

委托总是出现在所有其他类外面。

(P738)

回调是另一种使用委托的方式。

(P739)

向工程增加一个委托,就是在创建一个新类型,可以存储方法的引用。

事件使用委托来通知对象有某些动作发生。

如果对象需要对某个对象中发生的事情做出反应,可以订购这个对象的事件。

EventHandler 是一种委托,处理事件时这个委托很常用。

可以把多个事件处理方法串链到一个事件上。因此要用 “+=” 为事件设置事件处理方法。

在使用事件或委托之前,一定要检查是否非 null ,以防止出现 NullReferenceException 异常。

一个对象将一个方法的引用传入另一个对象,使它 (只有它) 能返回信息,这就称为一个回调。

利用事件,任何方法都可以匿名地订购对象的事件,而回调允许对象控制接收哪些委托。

回调和事件都使用委托来引用和调用其他对象中的方法。

(P740)

“回调” 只是使用委托 (或事件,完全可以使用一个私有事件来建立回调) 的一种方法。回调只是在两个类之间建立一种关系,一个对象可以请求另一个对象的通知。相比之下,在事件中,则是一个对象要求得到事件通知。

【第16章】

(P749)

视图模型就像是水管,利用一些工具 (你已经知道如何使用这些工具) 把视图中的对象连接到模型中的对象。

(P764)

模型可以触发一个事件,告诉应用的其余部分某个重要的状态发生了变化,而不需要引用模型以外的类。这样更容易构建,因为它与其余的 MVVM 层是解耦合的。

(P781)

MVVM 模式将视图与视图模型解耦合,另外将视图模型与模型解耦合。

(P794)

把辅助方法放在一个静态类中,而且类名以 “Helper” 结尾可以让你的代码更易读。

(P795)

标志为 readonly (只读) 的字段只能在声明或构造函数中修改。

posted on 2020-03-31 18:35  GATTACA2011  阅读(396)  评论(0编辑  收藏  举报