索引器,指针类型
1,可以使用索引器方法 this[Type param] 来自定义一个类型的索引器。C#编译器为索引器方法创建了一个名为 Item 的属性来映射正确的获取/设置方法。举例:
public Person this[int index] { get { return (Person)list[index]; set { list.Insert(index, value); }
2,索引器方法可以被重载,也可以可以实现多维的索引器,也可以在接口中定义索引器。
3,运算符重载:[], (), 以及简写赋值运算符(+=,-=等)不可重载,而 true, flase 也可以用作运算符。C#中如果一个类型重载了相关的二元运算符,与之对应的简写赋值运算符会自动具有相应的新功能。同样,我们也需要重载前增++或前减--就能自动获得后增或后减的功能。在C#中重载==运算符时必须同时重载!=运算符(如果不这么做,编译器会提示的),这规则适用于所以基于比较的运算符(>, <, >=, <=)。重载运算符在内部是通过隐藏方法来表示的。比如:op_Addition(), op_Equality()等。我们也可以在其他不支持重载运算符的语言中调用这些静态隐藏方法。
4,C#还支持自定义转换,转换方法必须定义为静态的。如将Rectangle 转换为 Square。与重载运算符一样,包含 implicit 或 explicit 关键字的方法在 CIL 中分别对应专门的指令 op_Implicit 和 op_Explicit。
public static explicit operator Square(Rectangle r) { Square s; s.Length = r.Height; return s; }
5,C#也支持原始的指针类型,以及相关的指针操作,我们应该明确设定编译器的 /unsafe 设置,以表示支持不安全编码。我们使用 unsafe 关键字来特别声明一个代码区块(包括结构,类,类型成员或参数)为不安全代码。如下关键字用于不安全编码:stackalloc(直接从调用栈分配内存),fixed(用于将不安全上下文内存中的引用类型变量地址固定,这样在执行过程中垃圾回收器就不会重置该变量的地址)以及 sizeof。
6,预处理指令:在C#中没有独立的预处理步骤,而是在编译器的词法分析阶段处理预处理指令。除了传统的#define, #if系列,C#还增加了用于标记代码区块的 #region, #endregion预处理指令。
隐式类型
7,var 关键字(准确地说 var 不是关键字,我们可以声明名为 var 的变量,参数或字段而编译器不会报错):我们可以使用 var 替代正式的数据类型名来局部变量,参数和字段,编译器会根据用于初始化局部变量的初始值推断变量的数据类型。隐式类型局部变量是强类型数据。如:
static void DeclareImplicitVars() { var myInt = 0; var myBool = true; var myString = "It's a string"; }
使用 var 定义隐式类型局部数组时,数组初始化式中的各数组成员必须为同一可推断的类型。隐式类型局部变量不会默认设置为 System.Object。
var d = new[] {1, 10, 100, 100};
8,使用 var 有一些限制:首先,显式类型只能应用于方法或者属性内局部变量的声明,不能使用 var 来定义返回值,参数的类型或者类型的数据成员。其次 var 进行声明的局部变量必须在声明时同时赋值,并且不能以null作为初始值。再次,不能使用C#的?标识来定义可空隐式类型局部变量。
9,隐式类型局部变量的作用:LINQ技术使用查询表达式,它可以动态根据查询本身的格式来创建结果集,这样我们可以使用 var,从而不需要显式定义查询可能会返回的类型。
自动属性
10,自动属性:为了简化简单字段数据封装的过程,C#3.0开始提供自动属性语法,这个特性能让我们使用新的语法为编译器定义支持字段和相关的C#属性。如果我们想要定义抽象属性的话,还需要使用 abstract 关键字。注意:如果要定义自动属性,就必须提供读和写两个功能,我们不能构建只读或只写的自动属性。示例:
class Car { public sting Name { get; protected set; }; }
扩展方法
11,C#3.0中,我们可以把方法定义为扩展方法。简单地说,扩展方法允许现存已编译的类型(如类,结构,接口)和当前即将被编译的类型(如包含扩展方法的类型)在不需要被直接更新的情况下,获得功能上的扩展。这样通过扩展方法,我们可以为预编译的类型(甚至我们并不拥有该类型的代码)添加功能,同时这些方法将独立分开存放。
12,使用扩展方法有一些限制:第一,扩展方法必须是静态的;第二,所有的扩展方法都需要使用关键字 this 对第一个参数(并且仅对第一个参数)进行修饰;第三,每一个扩展方法只可以被内存中正确的实例调用,或者通过其所处的静态类被调用。示例:该扩展允许所以System.Int32将自己的值倒置。
static class MyExtensions { public static int ReverseDigits(this int i) { char[] digits = i.ToString().ToCharArray(); Array.Reverse(digits); string newDigits = new string(digits); return int.Parse(newDigits); } }
13,在使用扩展方法的背后编译器仅仅以普通的方式调用静态方法,即把调用方法的变量作为调用参数(就是 this)。如上面的例子,编译器会在背后转换为 MyExtensions.ReverseDigits(aInt),因此我们可以使用普通C#语法像调用普通静态方法那样调用扩展方法。
14,由于扩展方法的静态性,我们不能在扩展方法中直接访问它扩展的类型的成员,但我们可以使用 this 来方法要扩展类型的所以公共成员(仅仅是公共成员)。
15,扩展方法的作用域:把包含扩展方法的静态类型放到独立的命名空间中后,处于同一程序集的其他命名空间可以使用标准的C#关键字 using 导入这些静态类型和它们包含的扩展方法。需要牢记的是,如果没有显示地导入正确的命名空间,扩展方法对当前C#代码文件是不可用的。
16,微软推荐把扩展方法放在独立的程序集(独立的命名空间)-扩展库,从而方便代码的模块化与管理。我们不仅可以扩展类还可以扩展接口。扩展接口稍微有点特别,我们扩展一个接口使其具有新成员的时候,必须提供这些成员的实现,这似乎已经脱离接口类型的本质(实际上是实现该接口的所以类型都具有静态的新成员实现)。
分部方法(partial)
17,我们可以使用 partial 关键字来构建分部类定义,这样我们可以跨多个代码文件实现类型的语法,只有每一个分部类型具有同样的完全限定名。在C#3.0起,我们可以把 partial 应用到方法级别,它允许我们在一个文件中构建方法原型,而在另一个文件中实现,但这样做有许多限制。第一,分部方法只可以定义在分部类中;第二,分部方法必须返回 void;第三,分部方法可以是静态或实例级别的;第四,分部方法可以有参数(包括被 this,ref或params修饰的参数,但不能具有 out 修饰符);第五,分部方法总是隐式私有的;第六,分部方法可能会放在已编译的程序集中,也可能不会,因为编译器会根据方法体是否实现来决定方法是否应该放到程序集中来,如果没有方法体的话,所以方法的使用痕迹(调用,元数据描述以及原型)都会在编译时去除。
18,由于分部方法必须是隐式私有的并总是返回 void,所以分部方法的用途并不大。通过用 partial 修饰符标记语法,其他类的构建者可以选择提供实现细节,这样,分部方法就提供了比使用预处理指令更简洁的方案,为虚方法提供了虚拟实现或抛出NotImplementException异常。此外使用分部方法来定义轻量级事件是很常见的,这使得类设计者可以提供方法挂钩,就像事件处理程序一样,开发人员可以选择实现或不实现来决定是否需要响应某事件。根据命名约定,这样的轻量级事件处理方法使用On前缀。
初始化
19,对象初始化器是以从左到右方式进行的。对于容器,我们可以使用集合初始化语法,此语法使我们可以像初始化普通数组一样初始化容器。
匿名类型
20,匿名类型是匿名方法的自然延伸。当定义一个匿名类型,需要使用新的关键字 var 和前面介绍的对象初始化语法。比如:
static void Main(string[] args) { // 构建一个匿名对象表示一辆汽车 var myCar = new { Color = "White", Make = "Saab", CurrentSpeed = 60 }; Console.WriteLine(" >> My car is a {0} {1}.", myCar.Color, myCar.Make); }
21,所有的匿名类型都自动继承 System.Object,因此它们都支持基类的每一个成员,因此我们可以在其上调用 GetType(), ToString()等方法。匿名类的类型名完全由编译器决定的,使用对象初始化语法定义的每一个名称/值对分别被映射为拥有相同名字的属性以及对应被该属性封装的私有数据成员。判断匿名类型的对象相等(Equals())是基于值的语义,即比较两个对象的每一个数据成员的值,但匿名类型并没有重载C#的相等运算符(=和!=),因此相等运算符还是基于比较引用的。
22,匿名类型的用途:应该谨慎地使用匿名类型,尤其是使用 LINQ 技术时(快速构建一个实体而不需要定义其功能)。匿名类型有很多限制:第一,你不能控制匿名类型的名称;第二,匿名类型继承自 System.Object;第三,匿名类型不支持事件,自定义方法,自定义运算符和自定义重写;第四,匿名类型是隐式封闭的(sealed);第五,匿名类型的实例创建只使用默认构造函数。