带着问题读CLR via C#(七)方法
Q1: 类中的实例构造函数可否被继承?
A1: 实例构造函数永远不能被继承。
Q2: 抽象类可否包含有参实例构造函数,如果可以,何时会被调用?
A2: 可以。抽象类有参构造函数通过其子实体类的构造函数调用,且若该抽象类没有提供无参构造函数,其子实体类必须显式调用一个它的有参构造函数,否则编译器会报错。代码如下:
1 abstract class AbstractClass 2 { 3 public int Id { get; set; } 4 public AbstractClass(int id) 5 { 6 Id = id; 7 } 8 } 9 10 class ErrorSon : AbstractClass 11 { 12 public string Name { get; set; } 13 14 // This line will throw an exception when compile the code: 'Test.AbstractClass' does not contain a constructor that takes 0 arguments 15 public ErrorSon(string name) 16 { 17 Name = name; 18 } 19 } 20 21 class CorrectSon : AbstractClass 22 { 23 public string Name { get; set; } 24 25 // Call the constructor of the abstract class explicitly 26 public CorrectSon(int id, string name) 27 : base(id) 28 { 29 Name = name; 30 } 31 }
Q3: 值类型可否定义构造函数?
A3: CLR允许值类型定义构造函数,但C#编译器不允许值类型定义无参构造函数,执行值类型构造函数的唯一可能是显式去调用。CLR不会为值类型生成默认的无参构造函数,值类型的任何构造函数都必须初始化值类型所有字段。代码如下:
1 struct Point 2 { 3 public int X; 4 public int Y; 5 public Point(int x, int y) 6 { 7 X = x; 8 Y = y; 9 } 10 } 11 12 class Test 13 { 14 public Point point { get; set; } 15 public Test() 16 { 17 point = new Point(5, 5); 18 } 19 }
Q4: 静态构造函数有什么特点?
A4: 1)只在第一次用到该类时执行一次;2)只能定义一次且不能包含参数;3)只能访问类型的静态字段;4)不能包含访问修饰符,默认private.
Q5: 多个线程同时访问一个类,静态构造函数如何确保不被多次执行?
A5: 在调用静态构造函数时,调用线程会获取一个互斥线程同步锁,这样如果多个线程想要同时调用一个类型的静态构造函数,只有一个线程可以获得锁,其它线程会被阻塞掉。第一个线程执行了静态构造函数之后,等待的线程会被唤醒,发现该静态构造函数已经被执行,就不会再次执行。
Q6: 如何重载操作符?
A6: CLR规范要求操作符重载方法必须是public static, C#语言规范要求操作符重载至少有一个参数的类型与当前定义这个方法的类型相同。代码如下:
1 class Point 2 { 3 public int X { get; set; } 4 public int Y { get; set; } 5 6 public static Point operator +(Point p1, Point p2) 7 { 8 return new Point() { X = p1.X + p2.X, Y = p1.Y + p2.Y }; 9 } 10 } 11 12 static void Main(string[] args) 13 { 14 Point p1 = new Point(); 15 p1.X = 5; 16 p1.Y = 5; 17 Point p2 = new Point(); 18 p2.X = 10; 19 p2.Y = 20; 20 21 Point p3 = p1 + p2; 22 Console.WriteLine(p3.X); 23 Console.WriteLine(p3.Y); 24 Console.Read(); 25 }
运行结果:
Q7: 什么是转换操作符方法?如何重载?
A7: 转换操作符是将对象从一个类型转换到另一个类型的方法。CLR规范要求转换操作符重载方法必须是public static, C#语言规范要求参数类型和返回类型二者必有其一与定义转换方法的类型相同。代码如下:
1 class Rational 2 { 3 // 由int32构建Rational对象 4 public Rational(Int32 num) 5 { 6 //... 7 } 8 9 // 由Single构建Rational对象 10 public Rational(Single num) 11 { 12 //... 13 } 14 15 // 将一个Rational转换为Int32 16 public Int32 ToInt32() 17 { 18 //... 19 } 20 21 // 由一个Rational转换为Single 22 public Single ToSingle() 23 { 24 //... 25 } 26 27 // 有一个Int32隐式构建并返回一个Rational对象 28 // implicit关键字:告诉编译器为了生成代码来调用此方法,不需要在源代码中进行显示转换 29 public static implicit operator Rational(Int32 num) 30 { 31 return new Rational(num); 32 } 33 34 // 由一个Single隐式构建并返回一个Rational对象 35 public static implicit operator Rational(Single num) 36 { 37 return new Rational(num); 38 } 39 40 // 由一个Rational显式返回一个Int32 41 // explicit关键字:告诉编译器生成代码来调用此方法,必须要在源代码中进行显式转换 42 public static explicit operator Int32(Rational r) 43 { 44 return r.ToInt32(); 45 } 46 47 // 由一个Rational显式返回一个Single 48 public static explicit operator Single(Rational r) 49 { 50 return r.ToSingle(); 51 } 52 }
Q8: 什么是扩展方法,扩展方法如何创建,创建过程中应注意什么?
A8: 通过扩展方法,开发人员可以向已定义的数据类型添加自定义功能,而不用创建新的派生类型。 通过使用这些扩展方法,可以编写一个能够像调用现有类型的实例方法那样进行调用的方法。扩展方法必须在非泛型静态类中声明,对类名没有要求,第一个参数必须用this关键字标记要扩展的类型名。定义扩展方法的静态了必须文件作用域,即不能为嵌套在另一个类中类。多个静态类可以定义相同的扩展方法,此时不能用实例来调用扩展方法,需要用静态方法调用方式显式调用某一个静态方法。代码如下:
1 class Program 2 { 3 static void Main(string[] args) 4 { 5 string s = "http://www.googl.com"; 6 7 // 以实例方式调用扩展方法 8 Uri uri = s.ConvertToUri(); 9 10 // 因为StringExtension类和StringExtension2类同时定义了AllAreCapital方法, 11 // 所以不能用实例来调用,需要以调用静态方法的语法显式调用 12 bool allAreCaplital = StringExtension.AllAreCapital(s); 13 } 14 } 15 16 public static class StringExtension 17 { 18 // 扩张方法将string转换为uri 19 public static Uri ConvertToUri(this String s) 20 { 21 // ... 22 } 23 24 // 扩展方法确定字符串是否所有字母为大写 25 public static bool AllAreCapital(this String s) 26 { 27 // ... 28 } 29 } 30 31 public static class StringExtension2 32 { 33 // 扩展方法确定字符串是否所有字母为大写 34 public static bool AllAreCapital(this String s) 35 { 36 // ... 37 } 38 }
扩展方法调用实际是对静态方法的调用,所以CLR不会生成代码对调用方法的表达式的值进行null检测,如以下代码是可以成功执行的:
1 class Program 2 { 3 static void Main(string[] args) 4 { 5 string str = null; 6 str.Print(); 7 Console.Read(); 8 } 9 } 10 11 public static class StringExtension 12 { 13 public static void Print(this string str) 14 { 15 Console.WriteLine("Executed!"); 16 } 17 }
若一个扩展方法的名称和一个实例方法相同,则该扩展方法永远不会被执行。
为了能够快速检测到扩展方法,在C#中,一旦用了this关键字标记某个静态方法的第一个参数,编译器就会在内部向该方法应用一个ExtensionAttribute特性,这个特性会在最终生成的文件的元数据中持久性地储存下来,任何静态方法只要包含了至少一个扩展方法,它的元数据中也会应用这个特性,任何程序集只要包含了至少一个定义了静态方法的静态类,它的元数据中也会应用这个特性。
Q9: 什么是分部方法,分部方法如何创建,它的作用是什么?
A9: 分部类或结构可以包含分部方法。 类的一个部分包含方法的签名。 可以在同一部分或另一个部分中定义可选实现。 如果未提供该实现,则会在编译时移除方法以及对方法的所有调用。代码如下:
1 // Definition in file1.cs 2 partial void onNameChanged(); 3 4 // Implementation in file2.cs 5 partial void onNameChanged() 6 { 7 // method body 8 }
定义分部方法应注意:1)它只能在分部类或结构中声明;2)返回类型始终是void,任何参数不能使用out修饰符;3)可以使用ref参数,可以是泛型方法,可以是实例或静态方法,也可以使用标记为unsafe;4)声明和实现必须具有完全相同的签名;5)分部方法总被是为private,但是C#编译器禁止显式在方法名前加private关键字。