C# in depth ( 第二章 C#1.0所搭建的核心基础)
2.1委托
不需要直接指定一个要执行的行为,而是将这种行为用某种方式“包含”在一个对象中。这个对象可以像其他任何对象一样使用。在该对象中,可以执行封装的操作。可以将委托类型看作只定义了一个方法的接口,将委托实例看作实现了那个接口的对象。
类似遗嘱,遗嘱由一系列指令组成,比如:“付帐单,捐善款,其余财产留给猫”。去世后,律师会执行这些指令。 C#中的委托和现实世界的遗嘱一样,也是要在恰当的时间执行一些列操作。如果代码想要执行操作,但不知道操作细节,一般可以使用委托。
2.1.1 简单委托的构成
- 声明委托类型 (注意委托类型和委托实例的区别)
delegate void StringProcessor (string input)
- 必须有一个方法包含了要执行的代码
void PrintString(string s) void PrintObject(object o) 逆变,C#1.0不支持Delegate的逆变,C#2.0支持
- 必须创建一个委托实例
StringProcessor proc1,proc2 proc1 = new StringProcessor(StaticMethods.PrintString) InstanceMethods instance = new InstanceMethods() proc2= new StringProcessor(instance.PrintString)
最终的垃圾(或者不是,视情况而定)
必须注意,假如委托实例本身不能被回收,委托实例会阻止它的目标被作为垃圾回收。这可能造成明显的内存泄露(Leak),尤其是假如某“短命”对象调用了一个“长命”对象中的事件,并用它自身作为目标。“长命”对象间接容纳了对“短命”对象的一个引用,延长了“短命”对象的寿命。 - 必须调用(invoke)委托实例
proc1("Hello") 编译成 proc1.Invoke("Hello") 在执行时调用 PrintString ("Hello")
2.1.2 合并和删除委托
- System.Delegate类型的静态方法Combine和Remove负责创建新的委托实例,Combine负责将来个委托实例的调用列表连接到一起,而Remove负责从一个实例中删除另一个实例的调用列表。
- 委托是Immutable的 创建了委托实例后,有关它的一切就不能改变,因此就可以安全地传递委托实例的引用,并把它们与其他委托实例合并,同时不必担心一致性,线程安全性,或者是否有其他人试图更改它。在这一点上委托实例和string是一样的 Delegate.Combine和string.Contact很像。
- 调用委托实例时,它的所有操作都顺序执行,如果委托的签名具有一个非void的返回类型,则Invoke的返回值是最后一个操作的返回值。很少有非Void的委托在它的调用列表中指定多个操作,因为这意味着其它所有操作的返回值永远都不可见。除非每次调用代码使用Delegate.GetInvocationList获取操作列表时,都显示调用某个委托。如果调用列表中的任何操作抛出一个异常,都会阻止执行后续的操作。
2.1.3 对事件的简单讨论
2.1.4 委托总结
- 委托封装了包含特殊返回类型和一组参数的行为,类似包含单一方法的接口;
- 委托类型声明中所描述的类型签名决定了哪个方法可用于创建委托实例,同时决定了调用的签名。
- 为了创建委托实例,需要一个方法以及(对于实例方法来说)调用方法的目标。
- 委托实例是Immutable的
- 每个委托实例都包含一个调用列表----一个操作列表
- 委托实例可以合并到一起,也可以从一个委托实例中删除另一个
- 事件不是委托实例-----只是成对的add/remove方法(类似于属性的取值方法/赋值方法)。
2.2 系统类型的特征
2.2.1 C#在类型系统世界中的位置
- 静态类型和动态类型 (静态类型:每个变量都有一个特定的类型,而且该类型在编译时是已知的,只有类型已知的操作才是允许的,这一点由编译器强制生效。动态类型:动态类型的实质是变量中含有值,但那些值并不限于特定的类型)
- 显示类型和隐式类型 (显示类型和隐式类型(var 编译器自己推断)的区别只有在静态语言中才有意义)
- 类型安全与类型不安全(不同类型之间不能随意的转换---叫做类型安全,反之就是类型不安全)
2.2.2 C#1.0的类型系统何时不够
- 集合,强和弱
- 数组(强类型,引用类型支持协变)
string[] strings = new strings[5]; object[] objects = strings; objects[0]= new Button(); 编译时正常,运行时会抛一个ArrayTypeMismatchException异常 这是由于从string[] 转换成object[]会返回原是使用,无论strings还是objects都引用同一个数组。数组本身知道它是一个字符串数组,所以会拒绝存储对非字符串的引用。数组协变时会派上用场,但代价是一些类型安全性在执行时才能实现,而不能在编译时实现。
- System.Collections命名空间中的弱类型集合(ArrayList)
- System.Collections.Specialized命名空间中的强类型集合(List)
- 缺乏协变的返回类型
- C#1.0不支持参数的逆变和返回类型的协变 (这里指的是函数的重载)
2.2.3 类型系统特征总结
- C#1.0是静态类型的-----编译器知道你能使用哪些成员
- C#1.0是显示的------必须告诉编译器变量具有什么类型。
- C#1.0是安全的------除非存在真实的转换关系,否则不能将一种类型当作另一种类型。
- 静态类型仍然不允许一个集合成为强类型的"字符串“列表或者”整数列表“ (不支持泛型)
- 方法覆盖和接口实现不允许协变性/逆变性
2.3 值类型和引用类型
2.3.1现实世界中的值和引用 (值类型:报纸, 引用类型:超链接)
2.3.2值类型和引用类型基础知识
- String.Empty的值不是一个空字符串,而是对空字符串的一个引用
- 变量的值在它声明时的位置存储:局部变量的值总是存储的栈(Stack)中,实例变量的值总是存储在实例本身存储的地方。引用类型实例(对象)总是存储在堆(heap)中,静态变量也是。
- 值类型不可派生出其他类型,导致值类型不需要额外的信息来描述值实际是什么类型。
- 对于引用类型来说,每个对象的开头都包含一个数据,它标示了对象的实际类型,同时还提供了一些其他信息。
2.3.3走出误区
- 误区1:"结构是轻量级的类"
- (DateTime作为值类型来提供是很有道理的,因为它非常适合作为和数字或字符相似的一个基本单位来使用。总之取决于需要的是值类型的语义还是饮用类型的语义。而不是取决于这个类型简单于否
- 性能上也各有千秋,值类型不需要垃圾回收,但是在作为参数被传递给函数的时候就需要完全复制(而引用类型则只需要传递4或8字节(取决于运行的是32位还是64位的CLR)
- 误区2:"引用类型都保存在堆上,值类型都保存在栈上”
- 引用类型确实都保存在堆上。
- 但是值类型就得分情况。变量的值总是在它声明的位置存储的。假定一个类中有一个int类型的实例变量,那么在这个类的任何对象中,该变量的值总是和对象中的其他数据在一起。也就是在堆上。
- 只有局部变量(方法内部声明的变量)和方法参数在栈上。
- 对于C#2.0及更高版本,很多局部变量并不完全存放在栈中,第5章中的匿名方法会讲到这一点。
- 误区3:“对象在C#中默认是通过引用传递的"
- 假如引用传递的方式来传送一个变量,那么调用的方法可以通过更改其参数值来改变调用者的变量值。
- 引用类型变量的值是引用,而不是对象本身。
- C#方法默认按值传递参数,在传递的过程中,值会被复制,引用类型变量的值是引用,即该引用会被复制一份,即传递参数后会有两个引用指向同一对象,随后对此对象的改动(修改属性等,只要不是把改引用指向完全不同的对象)都会影响到最原先的引用。
void AppendHello(StringBuilder sb) { sb = new StringBuilder(); sb.Append("Hello"); } void AppendHello(ref StringBuilder sb) { sb = new StringBuilder(); sb.Append("Hello"); }
以上两个AppendHello,如果没有new 这一步,对于调用者来说结果是一样的,但是在new过之后上一个函数里的sb的变化将不会再影响到调用者,因为这个sb是按值传递的,已经指向了全新的一个对象。
2.3.4 装箱和拆箱
2.4 C#1.0之外:构建于坚实基础之上的新特性
2.4.1与委托有关的特性
- C#1.0使用的语法很笨拙,即使要创建的事情非常简单,也必须要有一个专门做这件事情的方法,才能为这个方法创建一个委托实例。
- C#2.0用匿名方法进行了修正。另外,还引入了一个简单的语法,用于仍然要用一个普通的方法为委托提供操作的情况。最后,还可以使用具有兼容签名的方法来创建委托实例----方法签名不需要和委托的声明完全一致。
- 泛型(泛型委托类型)
- 创建委托实例时使用的表达式
- 匿名方法
- 委托协变性/逆变性
static void HandleDemoEvent(object sender, EventArgs e) { Console.WriteLine("Handled by HandleDemoEvent"); } static void Main() { EventHandler handler; handler = new EventHandler(HandleDemoEvent); //指定委托类型和方法 handler(null, EventArgs.Empty); handler = HandleDemoEvent; //隐式转换成委托实例 handler(null, EventArgs.Empty); handler = delegate(object sender, EventArgs e)//用一个匿名方法来指定操作 { Console.WriteLine("Handled anonymously"); }; handler(null, EventArgs.Empty); handler = delegate { Console.WriteLine("Handled anonymously"); };//使用匿名方法的简写形式 handler(null, EventArgs.Empty); MouseEventHandler mouseHandler = HandleDemoEvent; mouseHandler(null, new MouseEventArgs(MouseButtons.None, //使用委托逆变性 (C#1.0不支持) 0, 0, 0, 0)); }
- C#3.0Lambda表达式像是改进后的匿名方法
Func<int, int, string> func = (x, y) => (x * y).ToString(); Console.WriteLine(func(5, 20));
2.4.2 与类型系统有关的特性
- C#2.0泛型(不包括泛型委托和接口的协变逆变)
- C#3.0匿名类型,隐式类型的局部变量以及扩展方法 (依然是静态的)
var jon = new { Name = "Jon", Age = 36 }; //var 隐式类型, new {}匿名对象 var tom = new { Name = "Tom", Age = 9 }; Console.WriteLine("{0} is {1}", jon.Name, jon.Age); Console.WriteLine("{0} is {1}", tom.Name, tom.Age);
- C#4.0泛型委托和接口的协变及逆变,动态类型(dynamic)---个人认为没啥卵用。
dynamic o="hello"; Console.WriteLine(o.Length); o= new string[]{"hi", "there"}; Console.WriteLine(o.Length);
2.4.3 与值类型有关的特性
- 泛型 List<object> ----> List<byte>
- 可空类型
static void Main() { int? x = null; x = 5; if (x != null) { int y = x.Value; Console.WriteLine (y); } int z = x ?? 10; }