public class SomeType { }//等价于 public class SomeType { public SomeType() : base() { } }
示例:字段初始化
using System; namespace ConsoleApp2 { class Program { static void Main(string[] args) { SomeType st = new SomeType("ctor..."); Console.WriteLine(st.ToString()); Console.Read(); } } internal sealed class SomeType { // 如果显示初始化这些字段的值, // 编译器会将字段的初始化内嵌到每个构造器函数中, // 这就会造成代码膨胀 // 不要显式初始化下面的字段 private Int32 m_x; private String m_s; private Double m_d; private Byte m_b; // 该构造器将所有字段设为默认值 // 其他所有构造器显式调用该构造器 public SomeType() { m_x = 5; m_s = "Hi there"; m_d = 3.14159; m_b = 0xff; } // 该构造器将所有的字段都设为默认值,然后修改m_s public SomeType(String s) : this() { m_s = s; } // 该构造器首先将所有字段都设为默认值,然后修改m_x和m_s public SomeType(Int32 x, String s) : this() { m_x = x; m_s = s; } public override string ToString() { return string.Format("m_x={0},m_s={1},m_d={2},m_b={3}", m_x,m_s,m_d,m_b); } } }
internal struct Point {//值类型的字段总是被编译器初始化为0或null //不能为实例字段直接赋值 public Int32 m_x, m_y;//可以为静态字段直接赋值 public static Int32 m_z = 5;//错误: CS0568 结构不能包含显式的无参数构造函数 //因为编译器不会自动调用无参构造器 //public Point() { } public Point(Int32 x, Int32 y) {//必须对所有字段完成赋值,否则会报错 m_x = x; m_y = y; } public Point(Int32 x) {//在值类型(struct)中this是可读写的,在引用类型(class)中this是只读的 //看起来很奇怪,但编译没问题,会将所有字段初始化为0或null this = new Point();//现在可以只对部分字段赋值了 m_x = x;//用x覆盖m_x的0 } } internal sealed class Rectangle { public Point m_topLeft, m_bottomRight; public Rectangle() { //在C#中,向一个值类型应用关键字new, //可以调用构造器来初始化值类型的字段 m_topLeft = new Point(1, 2); m_bottomRight = new Point(100, 200); } }
internal sealed class SomeRefType {//下面四种叫法都指同一种构造器: //类型构造器(type constructor) //静态构造器(static constructor) //类构造器(class constructor) //类型初始化器(type initializer) static SomeRefType() {//SomeRefType被首次访问时,执行这里的代码 } } internal struct SomeValType {//C#允许值类型定义无参的类型构造器 static SomeValType() {//SomeValType被首次访问时,执行这里的代码 } }
类型构造器总是私有;C#自动把它们标记为private。之所以必须私有,是为了防止任何由开发人员写的代码调用它,对它的调用总是由CLR负责。
internal struct SomeValType { static SomeValType() { Console.WriteLine("这句话永远不会显示"); } public Int32 m_x; } public sealed class Program { public static void Main() { SomeValType[] a = new SomeValType[10]; a[0].m_x = 123; Console.WriteLine(a[0].m_x);//显示123 } }
internal struct SomeValType { private static Int32 s_x = 5; }//等效于下面的写法 internal struct SomeValType { private static Int32 s_x; static SomeValType() { s_x = 5; } }
单个线程中的两个静态类型构造器包含相互引用的代码可能出问题
操作符重载方法
C#允许重载一元和二元操作符,以及由编译器生成的对应的CLS(Common Language Specification,公共语言规范)方法名。
C#的一元操作符及其相容于CLS的方法名 | ||
C#操作符 | 特殊方法名 | 推荐的相容于CLS的方法名 |
+ | op_UnaryPlus | Plus |
- | op_UnaryNegation | Negate |
! | op_LogicalNot | Not |
~ | op_OnesComplement | OnesComplement |
++ | op_Increment | Increment |
-- | op_Decrement | Decrement |
(无) | op_True | IsTrue{get;} |
(无) | op_False | IsFalse{get;} |
C#的二元操作符及其相容于CLS的方法名 | ||
C#操作符 | 特殊方法名 | 推荐的相容于CLS的方法名 |
+ | op_Addition | Add |
- | op_Subtraction | Subtract |
* | op_Multiply | Multiply |
/ | op_Division | Divide |
% | op_Modulus | Mod |
& | op_BitwiseAnd | BitwiseAnd |
| | op_BitwiseOr | BitwiseOr |
^ | op_ExclusiveOr | Xor |
<< |
op_LeftShift |
LeftShift |
>> | op_RightShift | RightShift |
== | op_Equality | Equals |
!= | op_Inequality | Equals |
< | op_LessThan | Compare |
> | op_GreaterThan | Compare |
<= | op_LessThanOrEqual | Compare |
>= | op_GreaterThanOrEqual | Compare |
using System; using System.Collections.Generic; using System.Linq; using System.Text; using System.Threading.Tasks; namespace ConsoleApp4 { class Program { static void Main(string[] args) { //隐式转型 Rational r1 = 5;//Int32转Rational Rational r2 = 2.5F;//Single转Rational //显式转型 Int32 x = (Int32)r1;//Ration转Int32 Single s = (Single)r2;//Ration转Single } } public sealed class Rational { private Int32 i; private Single si; // 由一个Int32构造一个Rational public Rational(Int32 num) { i = num; } // 由一个Single构造一个Rational public Rational(Single num) { si = num; } // 将一个Rational转换成一个Int32 public Int32 ToInt32() { return i; } // 将一个Rational转换成一个Single public Single ToSingle() { return si; } // 由一个Int32隐式构造并返回一个Rational // 被编译成: // public static Rational op_Implicit(Int32 num) public static implicit operator Rational(Int32 num) { return new Rational(num); } // 由一个Single隐式构造并返回一个Rational // 被编译成: // public static Rational op_Implicit(Single num) public static implicit operator Rational(Single num) { return new Rational(num); } // 由一个Rational显式返回一个Int32 // 被编译成: // public static Int32 op_Explicit(Rational r) public static explicit operator Int32(Rational r) { return r.ToInt32(); } // 由一个Rational显式返回一个Single // 被编译成: // public static Single op_Explicit(Rational r) public static explicit operator Single(Rational r) { return r.ToSingle(); } } }
为了真正理解操作符重载方法和转换操作符方法,强烈建议将 System.Decimal 类型作为典型来研究。
扩展方法
using System; using System.Collections.Generic; using System.Linq; using System.Text; using System.Threading.Tasks; namespace ConsoleApp5 { class Program { static void Main(string[] args) { StringBuilder sb = new StringBuilder("Hello. My name is Jeff."); Int32 index = sb.Replace('.', '!').IndexOf('!'); } } public static class StringBuilderExtensions { // 为StringBuilder定义扩展方法IndexOf public static Int32 IndexOf(this StringBuilder sb, Char value) { if (null == sb)//注意: sb可能为null return -1; for (Int32 index = 0; index < sb.Length; index++) if (sb[index] == value) return index; return -1; } } }
规则和原则:
● C#只支持扩展方法,不支持扩展发展、扩展事件、扩展操作符等。
● 扩展方法必须在非泛型的静态类中声明。
● 扩展方法类不能为嵌套类
● 如果扩展方法类在某个命名空间中,程序员必须导入(using 扩展类所在命名空间)才能使用
● 派生类也会得到扩展方法
● 如果微软在类中增加了和扩展方法相同的方法,我们定义的扩展方法将失效
为接口类型定义扩展方法
using System; using System.Collections.Generic; namespace ConsoleApp5 { class Program { static void Main(string[] args) { //每个Char在控制台上单独显示一行 "Grant".ShowItems(); //每个String在控制台上单独显示一行 new[] { "Jeff", "Kristin" }.ShowItems(); //每个Int32在控制台上单独显示一行 new List<Int32>() { 1, 2, 3 }.ShowItems(); } } public static class IEnumerableExtensions { //任何表达式,只要它最终的类型实现了IEnumerable<T>接口,就能调用此扩展方法 public static void ShowItems<T>(this IEnumerable<T> collection) { foreach (var item in collection) Console.WriteLine(item); } } }
为委托类型定义扩展方法
using System; using System.Collections.Generic; using System.Linq; using System.Text; using System.Threading.Tasks; namespace ConsoleApp6 { class Program { static void Main(string[] args) { //抛出NullReferenceException Action<Object> action = o => Console.WriteLine(o.GetType()); //吞噬NullReferenceException action.InvokeAndCatch<NullReferenceException>(null); //C#编译器允许创建委托来引用一个对象上的扩展方法 Action a = "Jeff".ShowItems; a(); } } public static class DelegateExtensions { public static void InvokeAndCatch<TException>(this Action<Object> d, Object o) where TException : Exception { try { d(o); } catch (TException) { } } } public static class IEnumerableExtensions { //任何表达式,只要它最终的类型实现了IEnumerable<T>接口,就能调用此扩展方法 public static void ShowItems<T>(this IEnumerable<T> collection) { foreach (var item in collection) Console.WriteLine(item); } } }
C#编译器会对定义了扩展方法的静态类应用 ExtensionAttribute 特性。
分部方法
using System; using System.Collections.Generic; using System.Linq; using System.Text; using System.Threading.Tasks; namespace ConsoleApp6 { class Program { static void Main(string[] args) { } } //工具生成的代码,存储在某个源代码文件中 //这个技术可用于密封类、静态类以及值类型 internal sealed partial class Base { private String m_name; //这是分部方法的声明 partial void OnNameChanging(String value); public String Name { get { return m_name; } set { //如果OnNameChanging分部方法没有实现主体,这句调用不会产生性能开销 OnNameChanging(value.ToUpper()); m_name = value; } } } //开发人员生成的代码,存储在另一个源代码文件中 internal sealed partial class Base { //这是分部方法的实现,会在m_name更改前调用 partial void OnNameChanging(string value) { if (String.IsNullOrEmpty(value)) throw new ArgumentNullException("value"); } } }
规则和原则
● 它们只能在分部类或结构中声明。
● 分部方法的返回类型始终是 void,任何参数都不能用 out 修饰符来标记。之所以有这两个限制,是因为方法在运行时可能不存在,所以不能将变量初始化为方法也许会返回的东西。
类似地,不允许 out 参数是因为方法必须初始化它,而方法可能不存在。分部方法可以有 ref 参数,可以是泛型方法,可以是实例或静态方法,而且可以标记为 unsafe。
● 当然,分部方法的声明和实现必须具有完全一致的签名。如果两者都应用了定制特性,编译器会合并两个方法的特性。应用于参数的任何特性也会合并。
● 如果没有对应的实现部分,便不能在代码中创建一个委托来引用这个分部方法。这同样是由于方法在运行时不存在。
● 分部方法总是被视为 private 方法,但C#编译器禁止在分部方法声明之前添加 private 关键字。