classTest { staticvoidMain() { int[] a1; // single-dimensional array of int int[,] a2; // 2-dimensional array of int int[,,] a3; // 3-dimensional array of int int[][] j2; // "jagged" array: array of (array of int) int[][][] j3; // array of (array of (array of int))
类型系统统一化 C# 提供了一个“统一类型系统”。所有类型(包括值类型)都是从 object 类型派生的。这样,类型 Object 中定义的方法就可以在任何值类型的值(甚至是像 int 这样的“基元”类型的值)上调用。eg:
1 2 3 4 5 6 7 8 9 10
using System; classTest { staticvoidMain() { Console.WriteLine(3.ToString()); int i = 123; object o = i; // boxing int j = (int) o; // unboxing } }
一个 int 值可以转换为 object,并可再次转换回 int。此示例同时显示了“装箱”和“取消装箱”。当值类型的变量需要转换为引用类型时,执行“装箱”,即设置一个对象箱来保存值这个值(该值被复制到箱中)。“取消装箱”则正好相反。当对象箱被强制转换回其原来的值类型时,该值就从箱中取出并复制到适当的存储位置。
此类型系统统一化为值类型提供了对象性的优点,并且不会带来不必要的系统开销。对于不需要 int 值的行为与对象一样的程序,int 值只是 32 位值。对于需要 int 值的行为与对象一样的程序,可以根据需要使用此功能。这种将值类型作为对象处理的能力弥补了大多数语言中存在的值类型与引用类型之间的差距。例如,Stack 类可以提供 Push 和 Pop 方法,这些方法获得并返回一个 object 值。
委托:委托类型使用delegate进行声明,有一个返回值和任意数目任意类型的参数。委托是一种可用于封装命名或匿名方法的引用类型。 委托类似于 C++ 中的函数指针;但是,委托是类型安全和可靠的。 委托是事件的基础。 通过将委托与命名方法或匿名方法关联,可以实例化委托。必须使用具有兼容返回类型和输入参数的方法或 lambda 表达式实例化委托。 eg:
using System; classTest { staticvoidF(int p) { Console.WriteLine("p = {0}", p); p++; } staticvoidMain() { int a = 0; Console.WriteLine("Pre: a = {0}", a); F(a); Console.WriteLine("New: a = {0}", a); } }
输出结果为全为0 引用参数:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16
using System; classTest { staticvoidF(refint p) { Console.WriteLine("p = {0}", p); p++; } staticvoidMain() { int a = 0; Console.WriteLine("Pre: a = {0}", a); F(ref a); Console.WriteLine("New: a = {0}", a); } }
输出结果后面的a变成了0。
输出参数:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18
using System; classTest { staticvoidF(refint p, outint result) { Console.WriteLine("p = {0}", p); p++; result = p; } staticvoidMain() { int a = 0; int res; Console.WriteLine("Pre: a = {0}", a); F(ref a, out res); Console.WriteLine("New: a = {0}, res = {1}", a, res); } }
显然输出参数关键字out与引用参数ref的功能非常像,其实也基本上确实是一要的。但不同的是引用参数在使用前必须初始化,而输出参数不需要,也就是在调用函数之前并没有给输出参数分配空间。就像上例中如果将int res;改为int res = 0;,则将所有的out替换为ref之后结果也完成正确。
int a = int.MaxValue * 2; //编译报错 int temp = int.MaxValue; int a = temp * 2; //编译通过,显然结果不正确,因为编译时的溢出检查仅限于使用常量的表达式。 //使用checked溢出检查 int temp = int.MaxValue; try { int a = checked(temp * 2); //checked也可以用于代码块 } catch (OverflowException) { Console.WriteLine("OverFlow"); } //使用unchecked不检查溢出 int a = unchecked(int.MaxValue * 2); //这种不检查的可以用于不需要正确结果的计算,比如hasecode
(T)x:
is : is检查一个对象是否兼容于指定的类型,即提供类型检查功能。如果所提供的表达式非空,并且所提供的对象可以强制转换为所提供的类型而不会导致引发异常,则 is 表达式的计算结果将是 true。注意,is操作符永远不会抛出异常,且如果对象引用是null时,is操作符总是返回false eg:
1 2
Object a = new Object(); Boolean b = (a is Object); // b is true
//只要lock语句存在,语句块就是临界区并且balance永远不会是负数 // using System.Threading; classAccount { private Object thisLock = new Object(); int balance;
Random r = new Random();
publicAccount(int initial) { balance = initial; } intWithdraw(int amount) { // This condition never is true unless the lock statement // is commented out. if (balance < 0) { thrownew Exception("Negative Balance"); } // Comment out the next line to see the effect of leaving out // the lock keyword. lock (thisLock) { if (balance >= amount) { Console.WriteLine("Balance before Withdrawal : " + balance); Console.WriteLine("Amount to Withdraw : -" + amount); balance = balance - amount; Console.WriteLine("Balance after Withdrawal : " + balance); return amount; } else { return0; // transaction rejected } } }
publicvoidDoTransactions() { for (int i = 0; i < 100; i++) { Withdraw(r.Next(1, 100)); } } }
classTest { staticvoidMain() { Thread[] threads = new Thread[10]; Account acc = new Account(1000); for (int i = 0; i < 10; i++) { Thread t = new Thread(new ThreadStart(acc.DoTransactions)); threads[i] = t; } for (int i = 0; i < 10; i++) { threads[i].Start(); } } }
using语句: using 关键字有两个主要用途: 作为指令,用于为命名空间创建别名或导入其他命名空间中定义的类型。 using 指令有三种用途: 允许在命名空间中使用类型,这样无需在该命名空间中限定某个类型的使用: using System.Text; 允许访问类型的静态成员,而无需限定使用类型名称进行访问: using static System.Math; 为命名空间或类型创建别名。 这称为 using 别名指令。 using Project = PC.MyCompany.Project; 作为语句,用于定义一个范围,在此范围的末尾将释放对象。提供能确保正确使用 IDisposable 对象的方便语法。
1 2 3 4
using (Font font1 = new Font("Arial", 10.0f)) { byte charset = font1.GdiCharSet; }
//4个实例字段 publicstaticreadonly Color Red = new Color(0xFF, 0, 0); publicstaticreadonly Color Blue = new Color(0, 0xFF, 0); publicstaticreadonly Color Green = new Color(0, 0, 0xFF); publicstaticreadonly Color White = new Color(0xFF, 0xFF, 0xFF); //声明3个内部实例字段 internalushort redPart; internalushort bluePart; internalushort greenPart; publicColor(ushort red, ushort blue, ushort green) { redPart = red; bluePart = blue; greenPart = green; } }
字段可以直接在声明类的时候初始化。eg:
1
Color newCol = new Color() {redPart = red, bluePart = blue};
属性: 属性是一种成员,可用它来访问对象或类的某个特性。属性的示例包括字符串的长度、字体的大小、窗口的标题、客户的名称,等等。属性是字段的自然扩展。属性和字段都是命名的成员,都具有相关的类型,且用于访问字段和属性的语法也相同。属性用属性声明定义。属性声明的第一部分看上去与字段声明非常相似。第二部分包含一个 get 访问器和/或一个 set 访问器。可读取并写入的属性同时包含 get 和 set 访问器。当读取属性值时调用 get 访问器;当写入属性值时则调用 set 访问器。在 set 访问器中,属性的新值是通过一个名为 value 的隐式参数来赋值的。 属性声明相对直接一些,但是属性的实际值在它们被使用时才可见。在下面的示例中,Button 类定义一个 Caption 属性。 读取和写入 Caption 属性的方式可以与读取和写入字段相同:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17
publicclassButton { privatestring caption; publicstring Caption { get { return caption; } set { caption = value; Repaint(); } } } Button b = new Button(); b.Caption = "ABC"; // set; causes repaint string s = b.Caption; // get b.Caption += "DEF"; // get & set; causes repaint
using System; //类A隐式地从object派生的类A。 classA { publicvoidF() { Console.WriteLine("A.F"); } }
//类B显式地继承于A,即从A派生的类B,B也继承了A的F方法,并添加了方法G classB: A { publicvoidG() { Console.WriteLine("B.G"); } } classTest { staticvoidMain() { B b = new B(); b.F(); // Inherited from A b.G(); // Introduced in B A a = b; // Treat a B as an A a.F(); } }
//显示了一个包含虚方法 F 的类 A 和一个类 B(它重写了 F)。B 中的重写方法包含一个调用 base.F(),它调用 A 中被重写的方法。 一个类可以使用 abstract 修饰符来指示它自己是不完整的,只打算用作其他类的基类。这样的类称为抽象类。抽象类可以指定抽象成员,即非抽象派生类必须实现的成员。
//在抽象类 A 中引入了抽象方法 F。非抽象类 B 提供此方法的实现。 abstractclassA { publicabstractvoidF(); } classB: A { publicoverridevoidF() { Console.WriteLine("B.F"); } } classTest { staticvoidMain() { B b = new B(); b.F(); A a = b; a.F(); } }