|
![]() |
名片设计 CorelDRAW Illustrator AuotoCAD Painter 其他软件 Photoshop Fireworks Flash |
|
本文讨论 Java 和 C# 之间的异同点,目的在于当迁移到 .NET 时,让 Java 开发人员把握所涉及的一些知识。Java 和 C# 之间的主要相似点是: • Java 和 C# 都源于 C++,并且共有 C++ 的一些特征。 • 两种语言都需要编译成中间代码,而不是直接编译成纯机器码。Java 编译成 Java 虚拟机 (Java Virtual Machine, JVM) 字节码,而 C# 则编译成公共中间语言 (Common Intermediate Language, CIL)。 • Java 字节码是通过称为 Java 虚拟机 (JVM) 的应用程序执行的。类似地,已编译的 C# 程序由公共语言运行库 (Common Language Runtime, CLR) 执行。 • 除了一些细微的差别以外,C# 中的非常处理与 Java 异常相似。C# 用 try...catch 构造来处理运行时错误(也称为非常),这和 Java 中是完全相同的。System.Exception 类是所有 C# 非常类的基类。 • 同 Java 相同,C# 是强类型检查编程语言。编译器能够检测在运行时可能会出现问题的类型错误。 • 同 Java 相同,C# 提供自动垃圾回收功能,从而使编程人员避免了跟踪分配的资源。 • Java 和 C# 都支持单一继续和多接口实现。 现在让我们看一看本文涵盖的重要差异: • C# 语言引入了一些新的语言构造,如 foreach、索引器、属性、委托、运算符重载和其他语言构造。在本文后面我们将更具体地讨论这些构造。 本页内容 源文件约定 顶层声明 完全限定名和命名空间别名 预处理指令 语言语法 转变和强制转变 值类型和引用类型 装箱 (Boxing) 和拆箱 (Unboxing) 运算符 流程控制 类基础 Main() 方式 其他方式 使用不确定数目的参数 属性 结构 C# 中的数组 继续和派生类 将类型强制转变到基类 选择构造函数 方式重写 抽象类 接口 运算符重载 非常 高级 C# 技术 垃圾回收 安全代码和不安全代码 小结 源文件约定 我们需要知道,两种语言在源程序的文件命名约定和结构上有一些不同: 文件命名 包含 C# 类的文件的命名约定与 Java 有点不同。首先,在 Java 中,所有源文件的扩展名都为 .java。每个源文件都包含一个顶层公共类声明,并且类名必须与文件名相匹配。换句话说,一个用公共范围声明的名为 Customer 的类必须定义在具著名称 Customer.java 的源文件中。 而 C# 源代码是由 .cs 扩展名表示的。与 Java 不同,源文件可以包含多个顶层公共类声明,而文件名不需要与任何类名相匹配。 返回页首 顶层声明 在 Java 和 C# 中,源代码以按一定顺序排列的顶层声明开始。Java 和 C# 程序中的声明只存在少许差别。 Java 中的顶层声明 在 Java 中,我们可以用 package 要害字将类组合在一起。打包的类必须在源文件的第一个可执行的行中使用 package 要害字。接着出现的是需要访问其他包中的类的任何导入语句,之后是类声明,比如: package ; import .; class Customer { ... } C# 中的顶层声明 C# 使用命名空间的概念,通过 namespace 要害字将逻辑上相关的类组合在一起。这些做法类似于 Java 包,而具有一样名称的类可以出现在两个不同的命名空间中。要访问定义在当前命名空间之外的命名空间中的类,我们可以使用紧跟该命名空间名的 using 要害字,如下所示: using .; namespace { class Customer { ... } } 注重,using 语句可以完全合法地放在命名空间声明中,在这种情况下,这样导入的命名空间就形成了包含命名空间的一部分。 Java 不答应在一样的源文件中有多个包,而 C# 答应在一个 .cs 文件中有多个命名空间: namespace AcmeAccounting { public class GetDetails { ... } } namespace AcmeFinance { public class ShowDetails { ... } } 返回页首 完全限定名和命名空间别名 同 Java 相同,通过提供类的完全限定名(如 System.Data.DataSet 或上面的示例中的 AcmeAccounting.GetDetails),我们可以在没有命名空间的 using 引用的情况下访问 .NET 或用户定义的命名空间中的类。 完全限定名可能会变得很长而不便于使用,在这种情况下,我们可以使用 using 要害字来指定一个简称或别名,以提高代码的可读性。 在下面的代码中,创建了一个别名来引用由一个虚构的公司所编写的代码: using DataTier = Acme.SQLCode.Client; using System; public class OutputSales { public static void Main() { int sales = DataTier.GetSales("January"); Console.WriteLine("January\\\'s Sales: {0}", sales); } } 注重 WriteLine() 的语法,格式字符串中带有 {x},其中 x 表示在此处要插入的值的参数列表的位置。假定 GetSales() 方式返回 500,则该应用程序的输出将为: January\\\'s Sales: 500 返回页首 预处理指令 与 C 和 C++ 相似,C# 包括预处理器指令,预处理器指令提供了有条件地跳过源文件的某些部分、报告错误和警告条件,以及描述源代码的不同部分的能力。使用“预处理指令”这个术语只是为了与 C 和 C++ 编程语言保持一致,因为 C# 并不包括单独的预处理步骤。有关 C# 预处理器指令的完整列表,请参见 C# 预处理器指令。 返回页首 语言语法 在这一部分中,我们讨论这两种语言之间的相似点和不同点。一些主要的不同点有: • 常量声明― Java 为此而使用 final 要害字,而 C# 使用要害字 const 或 readonly。 • 复合数据类型― 在 Java 中,我们可以使用类要害字来创建作为没有方式的类的复合数据类型,但是 C# 为此提供了 struct,同 C 中相同。 • 析构函数― C# 答应我们创建在销毁类的实例之前调用的析构函数方式。在 Java 中,可以提供 finalize() 方式来包含在将对象作为垃圾回收之前清除资源的代码。在 C# 中,由类析构函数来提供此功能。析构函数类似一个没有参数并前面带有波形符“~”的构造函数。 • 函数指针 ― C# 提供一个称为 delegate 的构造来创建类型安全的函数指针。Java 没有任何与之对等的机制。 数据类型 C# 提供了在 Java 中可用的所有数据类型,并且增加了对无符号数和新的 128 位高精度浮点类型的支持。 在 Java 中,对于每个基本数据类型,核心类库都提供了一个包装类来将其表示为 Java 对象。例如,Integer 类包装 int 数据类型,而 Double 类包装 double 数据类型。 而在 C# 中,所有的基本数据类型都是 System 命名空间中的对象。对于每个数据类型,都提供一个简称或别名。例如,int 是 System.Int32 的简称,而 double 是 System.Double 的简写形式。 下面的列表给出了 C# 数据类型及其别名。可以看到,前 8 个对应于 Java 中可用的基本类型。不过,请注重,Java 的 boolean 在 C# 中称为 bool。 C# 数据类型 简称 .NET类 类型 宽度 范围(位) byte System.Byte 无符号整数 8 -128 到 127 sbyte System.SByte 有符号整数 8 -128 到 127 int System.Int32 有符号整数 32 -2,147,483,648 到 2,147,483,647 uint System.UInt32 无符号整数 32 0 到 4294967295 short System.Int16 有符号整数 16 -32,768 到 32,767 ushort System.UInt16 无符号整数 16 0 到 65535 long System.Int64 有符号整数 64 -922337203685477508 到 922337203685477507 ulong System.UInt64 无符号整数 64 0 到 18446744073709551615 float System.Single 单精度浮点类型 32 -3.402823e38 到 3.402823e38 double System.Double 双精度浮点类型 64 -1.79769313486232e308 到 1.79769313486232e308 char System.Char 单个 Unicode 字符 16 用在文本中的 Unicode 符号 bool System.Boolean 逻辑 Boolean 类型 8 true 或 false object System.Object 所有其他类型的基本类型 string System.String 字符序列 decimal System.Decimal 可以表示具有 29 个有效位的小数的精确分数或整数类型 128 -2 x 10-96 到 2 x 1096 因为 C# 将所有的基本数据类型都表示为对象,所以按照基本数据类型来调用对象方式是可能的。例如: int i=10; Console.WriteLine(i.ToString()); 借助于自动装箱和拆箱,可以达到此目的。更多信息请参见装箱和拆箱。 枚举 与 C/C++ 相似,在 C# 中可以使用枚举来组合已命名常量,而在 Java 中不能使用枚举。下面的示例定义了一个简朴的 Color 枚举。 public enum Color {Green, Orange, Red, Blue} 还可以为枚举赋整数值,如下面的枚举声明所示: public enum Color {Green=10, Orange=20, Red=30, Blue=40} 下面的程序调用 Enum 类型的 GetNames 方式来显示枚举的可用常量。然后,它将值赋给枚举,并显示该值。 using System; public class TypeTest { public static void Main() { Console.WriteLine("Possible color choices: "); //Enum.GetNames returns a string array of named constants for the enum foreach(string s in Enum.GetNames(typeof(Color))) { Console.WriteLine(s); } Color FavoriteColor = Color.Blue; Console.WriteLine("Favorite Color is {0}",FavoriteColor); Console.WriteLine("Favorite Color value is {0}", (int)FavoriteColor); } } 在运行之后,该程序将显示如下结果: Possible color choices: Green Orange Red Blue Favorite Color is Blue Favorite Color value is 40 字符串 在 Java 和 C# 中,字符串类型表现出相似的行为,只有一些细微的差别。二者的字符串类型均是不可改变的,这意味着一旦字符串创建完毕,就不能改变字符串的值。在二者的实例中,看起来像修改字符串实际内容的方式实际上创建一个新的字符串供返回,而保留原始的字符串不变。在 C# 和 Java 中,比较字符串值的过程是不同的。在 Java 中,为了比较字符串的值,开发人员需要按照字符串类型调用 equals() 方式,正如在默认情况下 == 运算符比较引用类型相同。在 C# 中,开发人员可以使用 == 或 != 运算符来直接比较字符串的值。在 C# 中,尽管字符串是引用类型,但是在默认情况下,== 和 != 运算符将比较字符串的值而不是引用。在本文后面,我们将讨论值类型和引用。 正如在 Java 中相同,C# 开发人员不应该使用字符串类型来连接字符串,以避免每次连接字符串时都创建新的字符串类的开销。相反,开发人员可以使用 System.Text 命名空间中的 StringBuilder 类,它在功能上等同于 Java 中的 StringBuffer 类。 字符串 C# 提供了避免在字符串常量中使用转义序列(如代表制表符的“/t”或代表反斜杠字符的“/”)的功能。要这样做,可以在为字符串赋值之前使用 @ 符号来声明字符串。下面的示例显示了如何使用转义字符以及如何为字符串赋值: //Using escaped characters string path = "////FileShare//Directory//file.txt"; //Using String Literals string escapedPath = @"//FileShare/Directory/file.txt"; 返回页首 转变和强制转变 Java 和 C# 遵守相似的数据类型自动转变和强制转变规则。 同 Java 相同,C# 既支持隐式类型转变又支持显式类型转变。在扩大转变的情况下,转变是隐式的。例如,下面从 int 到 long 的转变是隐式的,犹如 Java 中的相同: int intVariable = 5; long l = intVariable; 下面是 .NET 数据类型之间的隐式转变列表: 隐式转变 源类型 目标类型 byte short, ushort, int, uint, long, ulong, float, double 或 decimal sbyte short, int, long, float, double, ? decimal int long, float, double, 或 decimal uint long, ulong, float, double, 或 decimal short int, long, float, double, 或 decimal ushort int, uint, long, ulong, float, double, 或 decimal long float, double, 或 decimal ulong float, double, 或 decimal float double char ushort, int, uint, long, ulong, float, double, 或 decimal 可以使用与 Java 相同的语法对希望显式转变的表达式进行强制转变: long longVariable = 5483; int intVariable = (int)longVariable; 显式转变 源类型 目标类型 byte sbyte 或 char sbyte byte, ushort, uint, ulong, 或 char int sbyte, byte, short, ushort, uint, ulong, 或 char uint sbyte, byte, short, ushort, int, 或 char short sbyte, byte, ushort, uint, ulong, 或 char ushort sbyte, byte, short, 或 char long sbyte, byte, short, ushort, int, uint, ulong, 或 char ulong sbyte, byte, short, ushort, int, uint, long, 或 char float sbyte, byte, short, ushort, int, uint, long, ulong, char, 或 decimal double sbyte, byte, short, ushort, int, uint, long, ulong, char, float, 或 decimal char sbyte, byte, 或 short decimal sbyte, byte, short, ushort, int, uint, long, ulong, char, float, 或 double 返回页首 值类型和引用类型 C# 支持两种变量类型: • 值类型 ― 这些是内置的基本数据类型,例如 char、int、float 和用 struct 声明的用户定义类型。 • 引用类型 ― 从基本类型构造而来的类和其他复杂数据类型。这种类型的变量并不包含类型的实例,而只是包含对实例的引用。 让我们略微深入地研究一下这个问题。假如我们创建两个值类型变量 i 和 j,比如: int i = 10; int j = 20; 图 1:值类型的内存位置 则 i 和 j 彼此完全独立,并且分配了不同的内存位置: 假如我们改变这些变量中的某一个的值,另一个自然不会受到影响。例如,假如我们有一个这样的表达式: int k = i; 则变量之间仍旧没有联系。也就是说,之后假如我们改变 i 的值,k 还会保留赋值时 i 具有的值。 然而,引用类型的做法却不同。例如,我们可以这样声明两个变量: myClass a = new myClass(); myClass b = a; 现在,因为类是 C# 中的引用类型,所以 a 称为对 myClass 的引用。上面两行中的第一行在内存中创建了 myClass 的一个实例,并且将 a 设置为引用该实例。因此,当我们将 b 设置为等于 a 时,它就包含了对内存中类的引用的重复。假如我们现在改变 b 中的属性,a 中的属性就将反映这些改变,因为这两者都指向内存中的一样对象,如下图所示: 图 2:引用类型的内存位置 返回页首 装箱 (Boxing) 和拆箱 (Unboxing) 这种将值类型转变为引用类型的过程称为装箱。而相反的将引用类型转变为值类型的过程就称为拆箱。如下面的代码所示: int valueVariable = 10; // boxing object obj = refVariable; // unboxing int valueVariable = (int) refVariable; Java 需要我们手动执行这样的转变。通过构造这样的对象,可以将基本数据类型转变成包装类的对象(装箱)。同样,通过调用这种对象中的适当方式,也可以从包装类的对象中提取基本数据类型的值(拆箱)。有关装箱的更多信息,请参见 装箱转变;有关拆箱的更多信息,请参见 拆箱转变。 返回页首 运算符 C# 提供了 Java 支持的所有可用运算符,如下表所示。在表的末尾,您将看到一些新的运算符,它们可以在 C# 中使用而不可以在 Java 中使用: 运算符 类别 符号 [Text] [Text] 一元 ++ -- + - ! ~ () 乘法 * / % 加法 + - 移位 << >> 关系 < > <= >= instanceof 相等 == != 逻辑与 & 逻辑异或 ^ 逻辑或 | 条件与 && 条件或 || 条件 ? : 赋值 = *= /= %= += -= <<= >>= &= ^= |= 操作数的类型 typeof 操作数的大小 sizeof 执行溢出检查 checked 取消溢出检查 unchecked 唯一不可以在 C# 中使用的 Java 运算符是 >>> 移位运算符。之所以在 Java 中存在此运算符,是因为该语言中缺乏无符号变量,例如在需要右移位以在最高有效比特位插入 1 时。 然而,C# 支持无符号变量,因而 C# 只需要标准 >> 运算符。取决于操作数是否带有符号,此运算符产生不同的结果。右移一个无符号数会在最高有效比特位插入 0,而右移一个有符号数则会复制前面的最高有效比特位。 checked 和 unchecked 运算符 假如对于分配给正在使用的数据类型的比特数来说结果太大,则算术运算会产生溢出。对于特定的整数算术运算,通过使用 checked 和 unchecked 要害字,可以检查或忽略这样的溢出。假如表达式是一个使用 checked 的常量表达式,则会在编译时产生错误。 下面这个简朴的示例说明了这两个运算符的用法 using System; public class Class1 { public static void Main(string[] args) { short a = 10000, b = 10000; short d = unchecked((short)(10000*10000)); Console.WriteLine(d= + d); short c = (short)(a*b); Console.WriteLine(c= + c); short e = checked((short)(a*b)); Console.WriteLine(e= + e); } } 在这段代码中,unchecked 运算符避免了发生编译时错误,否则,下面的语句会产生错误: short d = unchecked((short)(10000*10000)); 下一个表达式在默认情况下是不检查的,因此值会静静溢出: short c = (short)(a*b); 我们可以使用 checked 运算符来强制检查该表达式是否会在运行时溢出: short e = checked((short)(a*b)); 当运行时,赋第一个值给 d & c 会以值 -7936 静静溢出,但是当试图使用 checked() 以获得 e 的乘积值时,程序会引发 System.OverflowException 非常。 注重:另外,通过使用命令行编译器开关 (/checked) 或者直接在Visual Studio 中基于每个项目使用此开关,您还可以控制是否检查代码块中的算术溢出。 is 运算符 此运算符确定左边对象的类型是否与右边指定的类型相匹配: if (objReference is SomeClass) ... 在下面的示例中,CheckType() 方式打印一条消息,描述传递给它的参数的类型: using System; public class ShowTypes { public static void Main(string[] args) { CheckType (5); CheckType (10f); CheckType ("Hello"); } private static void CheckType (object obj) { if (obj is int) { Console.WriteLine("Integer parameter"); } else if (obj is float) { Console.WriteLine("Float parameter"); } else if (obj is string) { Console.WriteLine("String parameter"); } } } 运行此程序,输出如下: Integer parameter Float parameter String parameter sizeof 运算符 sizeof 运算符以指定值类型的字节数返回其大小,如下面的代码所示: using System; public class Size { public static void Main() { unsafe { Console.WriteLine("The size of short is {0}.", sizeof(short)); Console.WriteLine("The size of int is {0}.", sizeof(int)); Console.WriteLine("The size of double is {0}.",sizeof(double)); } } } 注重,包含 sizeof 运算符的代码放在一个不安全的块中。这是因为 sizeof 运算符被认为是一个不安全的运算符(由于它直接访问内存)。有关不安全代码的更多信息,请参见安全代码和不安全代码。 typeof 和 GetType typeof 运算符返回作为 System.Type 对象传递给它的类的类型。GetType() 方式是相关的,并且返回类或非常的运行时类型。typeof 和 GetType() 都可以与反射一起使用,以动态地查找关于对象的信息,如下面的示例所示: using System; using System.Reflection; public class Customer { string name; public string Name { set { name = value; } get { return name; } } } public class TypeTest { public static void Main() { Type typeObj = typeof(Customer); Console.WriteLine("The Class name is {0}", typeObj.FullName); // Or use the GetType() method: //Customer obj = new Customer(); //Type typeObj = obj.GetType(); Console.WriteLine("/nThe Class Members/n=================/n "); MemberInfo[] class_members = typeObj.GetMembers(); foreach (MemberInfo members in class_members) { Console.WriteLine(members.ToString()); } Console.WriteLine("/nThe Class Methods/n=================/n"); MethodInfo[] class_methods = typeObj.GetMethods(); foreach (MethodInfo methods in class_methods) { Console.WriteLine(methods.ToString()); } } } 运行此程序,输出如下: The Class name is Customer The Class Members ================= Int32 GetHashCode() Boolean Equals(System.Object) System.String ToString() Void set_Name(System.String) System.String get_Name() System.Type GetType() Void .ctor() System.String Name The Class Methods ================= Int32 GetHashCode() Boolean Equals(System.Object) System.String ToString() Void set_Name(System.String) System.String get_Name() System.Type GetType() 这为我们显示了从 System.Object 继续的所有类的成员,并且还展示了一种方式,C# 在内部将 get 和 set 属性 accessors 表示为 get_xxx() 和 set_xxx() 方式。 在下一个示例中,我们使用 GetType() 在运行时查找表达式的类型: using System; public class TypeTest { public static void Main() { int radius = 8; Console.WriteLine("Calculated area is = {0}", radius * radius * System.Math.PI); Console.WriteLine("The result is of type {0}", (radius * radius * System.Math.PI).GetType()); } } 此程序的输出告诉我们,结果是 System.Double 类型,选择它是因为System.Math.PI 是这种类型。 Calculated area is = 201.061929829747 The result is of type System.Double 返回页首 流程控制 在这两种语言中,流程控制语句是异常相似的,但是这一部分也会讨论它们的一些细微差别。 分支语句 分支语句根据特定的条件改变运行时程序执行的流程。 if、else 和 else if 这些在两种语言中是相同的。 switch 语句 在两种语言中,switch 语句都提供条件多分支操作。但是有点不同的是,Java 答应您“越过”一个 case 并执行下一个 case,除非您在 case 的末尾使用了 break 语句。然而,C# 需要在每个 case 的末尾都使用 break 或 goto 语句,假如两者都不存在,则编译器会产生下列错误: Control cannot fall through from one case label to another. 不过请注重,在没有指定要执行的代码的地方,当 case 匹配时,控制会越过随后的 case。当在 switch 语句中使用 goto 时,我们只能跳至同一 switch 中的另一个 case 块。假如我们想要跳至 default case,我们可以使用“goto default;”,否则,我们需要使用“goto case cond;”,其中 cond 是我们希望跳至的 case 的匹配条件。Java 的 switch 语句的另一个不同之处在于,在 Java 中,我们只能对整数类型使用 switch 语句,而 C# 答应我们对字符串变量使用 switch 语句。 例如,下面的程序在 C# 中是合法的,但在 Java 中却是不合法的: switch (args[0]) { case "copy": ... break; case "move": ... goto case "delete"; break; case "del": case "remove": case "delete": ... break; default: ... break; } goto 的返回 在 Java 中,goto 是一个没有实现的保留要害字。然而,我们可以使用带有 break 或 continue 标签的语句来达到与 goto 相似的目的。 C# 答应 goto 语句跳至带有标签的语句。不过请注重,为了跳至一个特定的标签,goto 语句必须在该标签的范围内。换句话说,goto 不可用来跳进一个语句块(不过,它可以跳出一个语句块)、跳出一个类,或退出 try...catch 语句中的 finally 块。不过请注重,在大多数情况下,我们都不鼓励您使用 goto,因为它违背了面向对象编程的良好实践。 循环语句 循环语句重复指定的代码块,直到满意给定的条件为止。 for 循环 在两种语言中,for 循环的语法和操作均一样: for (initialization; condition; expression) statement; foreach 循环 C# 引入了一种新的循环类型,称为 foreach 循环(类似于 Visual Basic 的 For Each)。foreach 循环答应遍历支持 IEnumerable 接口的容器类中的每一项(例如:数组)。下面的代码说明了如何使用 foreach 语句来输出一个数组的内容: public static void Main() { int[] arr1= new int[] {1,2,3,4,5,6}; foreach ( int i in arr1) { Console.WriteLine("Value is {0}", i); } } 在 C# 中的数组部分,我们将更具体地介绍 C# 中的数组。 while 和 do...while 循环 在两种语言中,while 和 do...while 语句的语法和操作均一样: while (condition) { //statements } As usual, don\\\'t forget the trailing ; in do...while loops: do { //statements } while(condition); 返回页首 类基础 访问修饰符 C# 中的修饰符与 Java 大致一样,我们将在这一部分介绍其中的一些细微差别。每个类成员或类本身都可以用访问修饰符进行声明,以定义许可访问的范围。没有在其他类中声明的类只能指定 public 或 internal 修饰符,而嵌套的类(如其他的类成员)可以指定下面五个修饰符中的任何一个: • public ― 对所有类可见 • protected ―仅从派生类中可见 • private ― 仅在给定的类中可见 • internal ― 仅在一样的程序集中可见 • protected internal ― 仅对当前的程序集或从包含类中派生的类型可见 public、protected 和 private 修饰符 public 修饰符使得可以从类内外的任何地方访问成员。protected 修饰符表示访问仅限于包含类或从它派生的类。private 修饰符意味着只可能从包含类型中进行访问。 internal 修饰符 internal 项只可以在当前的程序集中进行访问。.NET 中的程序集大致等同于 Java 的 JAR 文件,它表示可以从中构造其他程序的生成块。 protected internal 修饰符 protected internal 项仅对当前程序集或从包含类派生的类型可见。在 C# 中,默认访问修饰符是 private,而 Java 的默认访问修饰符是包范围。 sealed 修饰符 在其类声明中带有 sealed 修饰符的类可以认为是与抽象类完全相反的类 ― 它不能被继续。我们可以将一个类标记为 sealed,以防止其他类重写它的功能。自然地,sealed 类不能是抽象的。同时还需要注重,该结构是隐式密封的;因此,它们不能被继续。sealed 修饰符相称于在 Java 中用 final 要害字标记类。 readonly 修饰符 要在 C# 中定义常量,我们可以使用 const 或 readonly 修饰符替换 Java 的 final 要害字。在 C# 中,这两个修饰符之间的区别在于,const 项是在编译时处理的,而 readonly 字段是在运行时设置的。这可以答应我们修改用于在运行时确定 readonly 字段值的表达式。 这意味着给 readonly 字段的赋值可以出现在类构造函数及声明中。例如,下面的类声明了一个名为 IntegerVariable 的 readonly 变量,它是在类构造函数中初始化的: using System; public class ReadOnlyClass { private readonly int IntegerConstant; public ReadOnlyClass () { IntegerConstant = 5; } // We get a compile time error if we try to set the value of the readonly // class variable outside of the constructor public int IntMember { set { IntegerConstant = value; } get { return IntegerConstant; } } public static void Main(string[] args) { ReadOnlyClass obj= new ReadOnlyClass(); // We cannot perform this operation on a readonly field obj.IntMember = 100; Console.WriteLine("Value of IntegerConstant field is {0}", obj.IntMember); } } 注重,假如 readonly 修饰符应用于静态字段,它就应该在该静态字段中进行初始化。 类的构造函数。 返回页首 Main() 方式 每个 C# 应用程序都必须http://msdn.microsoft.com/vstudio/java/gettingstarted/csharpforjava/#Arrays in C#只能包含一个 Main() 方式,Main() 方式指定程序从何处开始执行。注重,在 C# 中,Main() 用大写字母开头,而 Java 使用小写的 main()。 Main() 只能返回 int 或 void,并且有一个可选的字符串数组参数来表示命令行参数: static int Main (string[] args) { ... return 0; } 字符串数组参数可以包含任何传入的命令行参数,这与 Java 中完全相同。因此,args[0] 指定第一个命令行参数,而 args[1] 表示第二个参数,等等。与 C++ 不同的是,args 数组不包含 EXE 文件的名称。 返回页首 其他方式 当将参数传递给方式时,它们可能通过值传递,也可能通过引用传递。值参数只提取任何变量的值以在方式中使用,因而调用代码中的变量值不受方式中对这些参数所执行的操作的影响。 而引用型参数指向在调用代码中声明的变量,因而在通过引用传递时,方式将修改该变量的内容。 通过引用传递 在 Java 和 C# 中,引用对象的方式参数总是通过引用传递,而基本数据类型参数则通过值传递。 在 C# 中,所有参数在默认情况下都是通过值传递的。要通过引用进行传递,我们需要指定要害字 ref 或 out 中的一个。这两个要害字的不同之处在于参数的初始化。ref 参数必须在使用前进行初始化,而 out 参数无需在传递前进行显式初始化,并且任何先前的值都会被忽略。 请注重,当方式将引用类型作为参数使用时,引用本身是通过值传递的。然而,引用仍旧指向内存中的同一对象,因此对对象的属性所做的任何改变在方式退出之后将保持不变。但是,因为引用本身是通过值传递的,所以在方式内它应该改为指向一个不同的对象甚至一个新对象,而一旦方式执行完毕,引用就会恢复为指向原来的对象,即使原来的对象是未赋值的也同样如此。 ref 要害字 当我们希望调用的方式永久性地改变用作参数的变量的值时,我们可以在参数中指定此要害字。发生传递的不是在调用中使用的变量值,而是对变量本身的引用。然后,方式作用于引用,这样,在方式执行过程中对参数所做的更改就保存到用作方式的参数的原始变量中。 下面的代码用 Add() 方式对此进行了说明,其中,第二个 int 参数是使用 ref 要害字通过引用传递的: using System; public class RefClass { public static void Main(string[] args) { int total = 20; Console.WriteLine("Original value of \\\'total\\\': {0}", total); // Call the Add method Add (10, ref total); Console.WriteLine("Value after Add() call: {0}", total); } public static void Add (int i, ref int result) { result += i; } } 这个简朴示例的输出结果表明,对 result 参数所做的改变反映在 Add() 调用所用的变量 total 中: Original value of \\\'total\\\': 20 Value after Add() call: 30 这是因为 result 参数引用了调用代码中的 total 变量所占用的实际内存位置。请注重,类的属性不是一个变量,并且不能直接用作 ref 类型的参数。 注重,在调用方式时,ref 要害字必须在参数之前,在方式声明中同样如此。 out 要害字 out 要害字的作用与 ref 要害字异常相似,并且对使用 out 要害字声明的参数所做的修改在方式外是可见的。它与 ref 有两点不同:首先,out 参数的任何初始值在方式中都是忽略的;其次,out 参数必须在方式中赋值: using System; public class OutClass { public static void Main(string[] args) { int total = 20; Console.WriteLine("Original value of \\\'total\\\': {0}", total); Add (33, 77, out total); Console.WriteLine("Value after Add() call: {0}", total); } public static void Add (int i, int j, out int result) { // The following line would cause a compile error // Console.WriteLine("Initial value inside method: {0}", result); result = i + j; } } 在该示例中,Add() 方式的第三个参数是用 out 要害字声明的,并且对该方式的调用还需要将 out 要害字用于该参数。输出结果为: Original value of \\\'total\\\': 20Value after Add() call: 110 因此,总的来说,当您希望方式修改现有的变量时,可以使用 ref 要害字,而使用 out 要害字来返回在该方式中产生的值。当方式为调用代码产生多个结果值时,通常一起使用 out 要害字与该方式的返回值。 返回页首 使用不确定数目的参数 通过在声明方式时指定 params 要害字,C# 答应我们发送可变数目的参数。参数列表也可以包含普通参数,但是需要注重,用 params 要害字声明的参数必须放在最后。它采用可变长度数组的形式,并且每个方式只有一个 params 参数。 当编译器尝试解析一个方式调用时,它查找其参数列表与调用的方式相匹配的方式。假如找不到可以与参数列表匹配的方式重载,但是存在与适当类型的 params 参数匹配的版本,则会调用该方式,并将额外的参数放在一个数组中。 下面的示例对此进行了演示: using System; public class ParamsClass { public static void Main(string[] args) { Average ("List One", 5,10,15); Average ("List Two", 5,10,15,20,25,30); } public static void Average (string title, params int[] values) { int Sum = 0; Console.Write("Average of {0}: ", title); for (int i = 0; i < values.Length; i++) { Sum += values[i]; Console.Write(values[i] + ", "); } Console.WriteLine(": {0}", (float)Sum/values.Length); } } 在上面的示例中,用整型数组中的 params 参数声明了方式 Average,让我们使用任何数目的参数来调用它。输出结果如下: Average of List One: 5, 10, 15, : 10 Average of List Two: 5, 10, 15, 20, 25, 30, : 17.5 注重,假如我们希望答应不同类型的不确定参数,我们可以指定 Object 类型的 params 参数。 返回页首 属性 在 C# 中,属性是类、struct,或接口的命名成员,它提供了一种简洁的途径,可以通过所谓的 get 和 set 访问器方式访问私有字段。 下面的代码片断为类 Animal 声明了一个名为 Species 的属性,它抽象了对名为 name 的私有变量的抽象访问: public class Animal { private string name; public string Species { get { return name; } set { name = value; } } } 通常,属性与它访问的内部成员有一样的名称,但是属性以大写字母开头(例如上面的示例中的 Name),或者内部成员带有“_”前缀。同时还需要注重 set 访问器中所用的名为 value 的隐式参数 ― 这种参数具有基础成员变量类型。 实际上,访问器在内部表示成 get_X() 和 set_X() 方式,从而与 .NET 语言保持兼容,因为 .NET 语言并不支持访问器(如本文前面的 typeOf 和 GetType() 部分中的屏幕截图所示)。一旦定义好属性,就可以异常轻易地获取或设置它的值: Animal animal = new Animal() // Set the property animal.Species = "Lion"; // Get the property value string str = animal.Species; 假如属性只有 get 访问器,它就是一个只读属性。假如它只有 set 访问器,它就是一个只写属性。假如两者都有,则它是一个可读写属性。 返回页首 结构 C# 支持 struct 要害字,它是源于 C 的另一个项,但是不可用于 Java。可以将 struct 看作是一个轻量级类。它可以采用与类大致一样的方法包含构造函数、常量、字段、方式、属性、索引器、运算符和嵌套类型。structs 不同于类,因为它们不能是抽象的,并且不支持实现继续。结构与类还有一点重要的不同,结构是值类型的,而类是引用类型的。在构造函数的工作方法上,结构也有所不同。特殊是,编译器总是提供默认的不带参数的构造函数,并且不答应您替换它。 在下面的示例中,我们使用 new 要害字并且通过初始化实例的成员初始化了一个 struct: using System; public struct CustomerStruct { public int ID; public string name; public CustomerStruct(int customerID, string customerName) { ID = customerID; name = customerName; } } class TestClass { public static void Main(string[] args) { // Declare a CustomerStruct using the default constructor CustomerStruct customer = new CustomerStruct(); Console.WriteLine("Struct values before initialization"); Console.WriteLine("ID = {0}, Name = {1}", customer.ID, customer.name); customer.ID = 100; customer.name = "Robert"; Console.WriteLine("Struct values after initialization"); Console.WriteLine("ID = {0}, Name = {1}", customer.ID, customer.name); } } 当我们编译并运行上面的代码时,它的输出显示,该结构的变量在默认情况下是已初始化的。int 变量初始化为 0,而字符串变量初始化为空字符串: 初始化前的 struct 变量 ID = 0, Name = 初始化后的 truct 值 ID = 100, Name = Robert 注重,当我们使用另一种表示法(CustomerStruct customer)声明 customer 时,它的成员变量将不被初始化,因此,假如试图在为它们赋值前使用它们,将会产生编译时错误。 返回页首 C# 中的数组 数组是具有一样数据类型的项的有序集合,通过数组名以及所期望的项相对于数组开始位置的偏移量可以访问数组。与 Java 相比,在 C# 中声明和使用数组的方法有一些重要的不同,我将在这一部分中对此进行介绍。 一维数组 一维数组以线性方法存储了固定数目的项,它仅仅需要一个索引值就可以确定任何一项。 在 C# 中,数组声明中的方括号必须紧跟数据类型,而不可以像在 Java 中相同出现在变量名的后面。因此,可以使用下面的语法来声明整数类型的数组: int[] MyArray; 而下面的声明在 C# 中是无效的: int MyArray[]; 一旦声明了数组,就可以使用新的要害字来设置它的大小,这与 Java 中是完全相同的: int[] MyArray; // declares the array reference MyArray = new int[5]; // creates a 5 element integer array 然后,我们就可以使用与 Java 完全一样的语法来访问一维数组中的元素,注重 C# 数组的索引也是从零开始的: MyArray [4] // accesses the last element in the array 初始化 可以使用与 Java 一样的语法在创建时对数组元素进行初始化: MyArray = new int[5] {1, 2, 3, 4, 5}; 与 Java 不同,初始化器的数目必须与数组大小完全匹配。 我们可以利用这一特性在一行中声明和初始化 C# 数组: int[] TaxRates = {0, 20, 23, 40, 50}; 此语法创建了一个大小与初始化器的数目相等的数组。 程序循环中的初始化 C# 中初始化数组的另一种方式就是使用 foreach 循环。下面的循环将数组中的每个元素都设置为零: int[] MyLittleArray = new int[5]; foreach (int i in MyLittleArray) { MyLittleArray[i] = 0; } 交错数组 C# 和 Java 都支持创建交错或者说非矩形的数组,其中的每一行都包含不同数目的列。例如,下面的交错数组的第一行有四项,而第二行有三项: int[][] JaggedArray = new int[2][]; JaggedArray[0] = new int[4]; JaggedArray[1] = new int[3]; 多维数组 C# 答应我们创建规则的多维数组,它可以看作是一样类型的值的矩阵。虽然 Java 和 C# 都支持交错的数组,但是 C# 还支持多维数组或数组的数组。我们将立刻介绍交错数组。 使用下面的语法,我们可以声明一个多维矩形数组: int[,] My2DIntArray; float[,,,] My4DFloatArray; 其中,My2DintArray 是可以借此访问每个元素的名称。 注重, int[][] My2DintArray; 行在 C# 中有不同的含义,我们很快就会明白这一点。 一旦声明了数组,我们就可以这样给它分配内存: int[,] My2DIntArray; // declares array reference My2DIntArray = new int[5,4]; // allocates space for 5x4 integers 然后,可以使用下面的语法来访问数组的元素: My2DIntArray [4,3] = 906; 因为数组是从零开始的,所以这将第四行第五列中的元素(右下角)设置为 906。 初始化 通过下面的任何一种方式,都可以在一行中创建、设置和初始化多维数组: int[,] intArray = { {1,2,3}, {4,5,6} }; int[,] intArray = new int [2,3] { {1,2,3}, {4,5,6} }; int[,] intArray = new int [,] { {1,2,3}, {4,5,6} }; 程序循环中的初始化 数组中所有的元素都可以使用嵌套的循环进行初始化,如下所示: int[,] intArray = new int[5,4]; foreach (int i in intArray) { foreach (int j in intArray[]) { j = 0; } } System.Array 类 在 .NET 中,数组是作为 System.Array 类的实例实现的。此类提供了几个有用的方式,例如 Sort() 和 Reverse()。 下面的程序说明了使用这几个方式是多么的轻易。首先,我们使用 Array 类的 Reverse() 方式来使数组的元素反向,然后,我们使用 Sort() 方式对它们进行排序: using System; public class ArrayMethods { public static void Main() { // Create string array of size 5 string[] EmployeeNames = new string[5]; Console.WriteLine("Enter five employee names:"); // Read 5 employee names from user for(int i=0;i<5;i++) { EmployeeNames[i]= Console.ReadLine(); } // Print the array in original order Console.WriteLine("/n** Original Array **"); foreach(string EmployeeName in EmployeeNames) { Console.Write("{0} ", EmployeeName); } //print the array in reverse order. Console.WriteLine("/n/n** Values in Reverse Order **"); System.Array.Reverse(EmployeeNames); foreach(string EmployeeName in EmployeeNames) { Console.Write("{0} ", EmployeeName); } //print the array in sorted order. Console.WriteLine("/n/n** Values in Sorted Order **"); System.Array.Sort(EmployeeNames); foreach(string EmployeeName in EmployeeNames) { Console.Write("{0} ", EmployeeName); } } } 下面是此程序的一些典型输出: Enter five employee names: Luca Angie Brian Kent Beatriz ** Original Array ** Luca Angie Brian Kent Beatriz ** Values in Reverse Order ** Beatriz Kent Brian Angie Luca ** Values in Sorted Order ** Angie Beatriz Brian Kent Luca 返回页首 继续和派生类 通过创建一个从现有类派生的新类,我们可以扩展现有类的功能。派生类继续了基类的属性,并且我们可以在需要时添加或重写方式和属性。 在 C# 中,继续和接口实现都通过 : 运算符来定义,这等同于 Java 中的扩展和实现。注重,基类应该一直在类声明的最左边。 同 Java 相同,C# 不支持多重继续,这意味着类不能从多个类中继续。然而,我们可以为此目的而采用与 Java 一样的方法使用接口,正如我们在下一部分中将看到的。 下面的代码定义了一个名为 Point 的类,它有两个私有成员变量 x 和 y,表示点的位置。这些变量可以分别通过名为 X 和 Y 的属性来访问: public class Point { private int x, y; public Point() { x = 0; y = 0; } public int X { get { return x; } set { x = value; } } public int Y { get { return y; } set { y = value; } } } 我们将从 Point 类派生一个新类(比如说名为 ColorPoint),如下所示: public class ColorPoint : Point 于是 ColorPoint 继续了基类的所有字段和方式,我们可以根据需要向派生类中添加新的字段和方式来提供额外的特性。在这个示例中,我们添加了一个私有成员和一些访问器来为点增加颜色: using System.Drawing; public class ColorPoint : Point { private Color screenColor; public ColorPoint() { screenColor = Color.Red; } public Color ScreenColor { get { return screenColor; } set { screenColor = value; } } } 注重,派生类的构造函数隐式调用了基类(或 Java 术语中的超类)的构造函数。在继续中,所有的基类构造函数都是按照这些类出现在类层次中的顺序在派生类的构造函数之前调用的。 返回页首 将类型强制转变到基类 与在 Java 中相同,我们不能使用对基类的引用来访问派生类的成员和方式,即使基类引用可能包含对派生类型对象的有效引用也同样如此。 我们可以通过隐式地引用派生的类型来引用派生类: ColorPoint clrpt = new ColorPoint(); Point pt = clrpt; 在这段代码中,基类引用 pt 包含 clrpt 引用的副本。 base 要害字 通过使用 base 要害字,我们可以访问子类中的基类成员,即使这些基类成员在超类中被重写也同样如此。例如,我们可以创建一个派生类,该类所包含的方式具有与基类中一样的签名。假如我们在此方式前加上 new 要害字,就表示这是一个属于派生类的全新方式。通过 base 要害字,我们仍旧可以提供方式来访问基类中的原始方式。 例如,我们的 Point 基类著名为 invert() 的方式,它交换 x 和 y 坐标。通过使用下面这样的代码,我们可以在派生类 ColorPoint 中提供此方式的替代方式: public new void invert() { int holding = X; X = Y; Y = holding; screenColor = Color.Gray; } 正如您所见,该方式交换 x 和 y,然后将点的颜色设置为灰色。通过在 ColorPoint 中创建另一个方式(例如下面的这个方式),我们可以提供对此方式的基实现的访问: public void baseInvert() { base.invert(); } 然后,我们就可以通过调用 baseInvert() 方式来调用 ColorPoint 对象中的基方式。 ColorPoint clrpt = new ColorPoint();clrpt.baseInvert(); 请记住,假如我们将对基类的引用赋值给 ColorPoint 的实例,然后访问它的方式,我们将获得一样的效果: Point pt = clrpt; pt.invert(); 返回页首 选择构造函数 基类对象总是在任何派生类之前构造的。因此基类的构造函数在派生类的构造函数之前执行。假如基类有多个构造函数,派生类就可以决定要调用的构造函数。例如,我们可以修改我们的 Point 类来添加第二个构造函数: public class Point { private int x, y; public Point() { x = 0; y = 0; } public Point(int x, int y) { this.x = x; this.y = y; } } 然后,通过使用 base 要害字,我们可以将 ColorPoint 类更改为使用某个特定的可用构造函数: public class ColorPoint : Point { private Color color; public ColorPoint(int x, int y) : base (x, y) { color = Color.Red; } } 在 Java 中,这项功能是通过 super 要害字来实现的。 返回页首 方式重写 通过为声明的方式提供新的实现,派生类可以重写基类的方式。Java 和 C# 之间的一个重要区别在于,Java 方式在默认情况下标记为虚方式,而在 C# 中,必须使用 virtual 修饰符才能将方式显式标记为虚方式。可以采用大致一样的方法重写属性访问器以及方式。 虚方式 派生类中要被重写的方式是用 virtual 修饰符声明的。而在派生类中,已被重写的方式用 override 修饰符声明。 override 修饰符表示派生类的方式或属性,这个派生类代替基类中具有一样的名称和签名的类。要被重写的基方式必须声明为 virtual、abstract 或 override:以这种方法重写非虚方式或静态方式是不可能的 ― 请参见关于此问题的下一部分。已被重写的方式或属性和重写方式或属性必须具有一样的访问级修饰符。 下面的示例显示了一个称为 StepUp 的虚方式,它是在派生类中用 override 修饰符重写的: using System; public class CountClass { public int count; // Constructor public CountClass(int startValue) { count = startValue; } public virtual int StepUp() { return ++count; } } class Count100Class : CountClass { // Constructor public Count100Class(int x) : base(x) { } public override int StepUp() { return ((base.count) + 100); } public static void Main() { CountClass counter = new CountClass(10); CountClass bigCounter = new Count100Class(10); Console.WriteLine("Value of count in base class = {0}", counter.StepUp()); Console.WriteLine("Value of count in derived class = {0}", bigCounter.StepUp()); } } 当我们运行这段代码时,会发现派生类的构造函数使用基类中给出的方式体,这使得我们在不复制该代码的情况下就可以初始化 count 成员。下面是我们得到的输出结果: Value of count in base class = 11 Value of count in derived class = 110 返回页首 抽象类 抽象类将一个(或多个)方式或属性声明为抽象的。这样的方式并不具有声明它们的类中提供的实现,尽管抽象类也可以包含非抽象方式,也就是说,已经为其方式提供了实现。抽象类不能直接实例化,而只能作为派生类。这样的派生类必须为所有的抽象方式和属性提供实现(使用 override 要害字),除非派生成员本身被声明为抽象的。 下面的示例声明了一个抽象的 Employee 类。我们还创建了一个名为 Manager 的派生类,它提供了定义在 Employee 类中的抽象方式 show() 的实现: using System; public abstract class Employee { // abstract show method public abstract void show(); } // Manager class extends Employee public class Manager: Employee { string name; public Manager(string name) { this.name = name; } //override the show method public override void show() { Console.WriteLine("Name : " + name); } } public class CreateManager { public static void Main(string[] args) { // Create instance of Manager and assign it to an Employee reference Employee temp = new Manager("John Chapman"); // Call show method. This will call the show method of the Manager class temp.show(); } } 这段代码调用了由 Manager 类提供的 show() 实现,并且在屏幕上打印出雇员的名字。 返回页首 接口 接口是一种“主干类”,包含方式签名但是没有方式的实现。在这个方面,接口与抽象类相同,只包含抽象方式。C# 接口异常类似于 Java 接口,工作原理基本相同。 接口的所有成员都定义为公共成员,并且接口不能包含常量、字段(私有数据成员)、构造函数、析构函数或任何类型的静态成员。假如为接口的成员指定任何修饰符,编译器将会产生错误。 为了实现接口,我们可以从接口派生类。这样的派生类必须为所有接口的方式提供实现,除非派生类声明为抽象的。 接口的声明与 Java 完全相同。在接口定义中,通过单独使用 get 和 set 要害字,属性仅指示它的类型,以及它是只读的、只写的还是可读写的。下面的接口声明了一个只读属性: public interface IMethodInterface { // method signatures void MethodA(); int MethodB(float parameter1, bool parameter2); // properties int ReadOnlyProperty { get; } } 用一个冒号来代替 Java 的实现要害字,类就可以继续此接口。实现类必须提供所有方式的定义以及任何必需的属性访问器: public class InterfaceImplementation : IMethodInterface { // fields private int count = 0; private int ID; // implement methods defined in interface public void MethodA() { ... } public int MethodB(float parameter1, bool parameter2) { ... return integerVariable; } public int ReadOnlyProperty { get { return count; } } // add extra methods if required } 实现多个接口 通过使用下面的语法,一个类可以实现多个接口: public class MyClass : interfacename1, interfacename2, interfacename3 假如一个类实现多个接口,则成员的名称会存在二义性,通过使用属性或方式名的完全限定符可以解决这个问题。换句话说,通过使用方式的完全限定名来指示它属于哪个接口(例如属于 IMethodInterface.MethodA),派生类可以解决这种冲突。 返回页首 运算符重载 与 C++ 相同,C# 答应我们重载运算符,以供在我们自己的类中使用。这可能使得用户定义的数据类型看起来很自然,并且可以在逻辑上作为基本数据类型使用。例如,我们可以创建一个新的名为 Complex 的数据类型来表示一个复杂的数字,并且提供一些方式,以使用标准的算术运算符对这样的数字进行算术运算,例如使用 + 运算符来使两个复杂的数字相加。 为了重载一个运算符,我们编写了一个函数,它将需要重载的运算符的符号放在名称 operator 的后面。例如,我们可以这样来重载 + 运算符: public static complex operator+(complex lhs, complex rhs) 所有的运算符重载都是类的静态方式。同时也需要注重,假如您重载等于运算符 (==),您还必须重载不等于运算符 (!=)。 可以重载的运算符的完整列表如下: • 一元运算符: +, -, !, ~, ++, --, true, false • 二元运算符: +, -, *, /, %, &, |, ^, <<, >>, ==, !=, >, <, >=, <= 下一个示例创建了一个 Complex 类,该类重载 + 和 - 运算符: using System; public class complex { private float real; private float img; public complex(float p, float q) { real = p; img = q; } public complex() { real = 0; img = 0; } public void Print() { Console.WriteLine("{0} + {1}i", real, img); } // Overloading \\\'+\\\' operator public static complex operator+(complex lhs, complex rhs) { complex sum = new complex(); sum.real = lhs.real + rhs.real; sum.img = lhs.img + rhs.img; return (sum); } // Overloading \\\'-\\\' operator public static complex operator-(complex lhs, complex rhs) { complex result = new complex(); result.real = lhs.real - rhs.real; result.img = lhs.img - rhs.img; return (result); } } 此类答应我们使用代码来创建和操作两个复杂的数字,如下所示: using System; public class ComplexClass { public static void Main(string[] args) { // Set up complex numbers complex A = new complex(10.5f,12.5f); complex B = new complex(8.0f,4.5f); complex C; // Print object A and B Console.Write("Complex Number A: "); A.Print(); Console.Write("Complex Number B: "); B.Print(); // Add A and B, print result C = A + B; Console.Write("/nA + B = "); C.Print(); // Subtract A and B, print result C = A - B; Console.Write("A - B = "); C.Print(); } } 正如程序所演示的,我们现在可以很直观地对属于复杂类的对象使用加减运算符。下面是我们得到的输出: Complex Number A: 10.5 + 12.5i Complex Number B: 8 + 4.5i A + B = 18.5 + 17i A - B = 2.5 + 8i 虽然 Java 在内部为字符串连接重载了 + 运算符,但是它并不支持运算符重载。 返回页首 非常 C# 中的非常处理与 Java 异常相似。 在程序执行的过程中,无论什么时候出现了严峻错误,.NET 运行库都会创建一个 Exception 对象来处理该错误。在 .NET 中,Exception 是所有非常类的基类。从 Exception 基类派生了两种类别的非常:System.SystemException 和 System.ApplicationException。System 命名空间中的所有类型都是从 System.SystemException 派生的,而用户定义的非常应该从 System.ApplicationException 派生,以便区分运行库错误和应用程序错误。一些常见的 System 非常包括: • IndexOutOfRangeException ― 使用了大于数组或集合大小的索引 • NullReferenceException ― 在将引用设置为有效的实例之前使用了引用的属性或方式 • ArithmeticException ― 在操作产生溢出或下溢时引发的非常 • FormatException ― 参数或操作数的格式不准确 与 Java 中相同,当我们有轻易引起非常的代码时,我们应该将此代码放在 try 块中。紧接其后的是一个或多个提供错误处理的 catch 块,并且我们还可以对任何我们想执行但又不知道是否引发非常的代码使用 finally 块。 注重:当使用多个 catch 块时,捕捉非常的代码必须以升序的顺序放置,这样就只有第一个与引发的非常相匹配的 catch 块会被执行。C# 编译器会强制这样做,而 Java 编译器不会这样做。 C# 也与 Java 相同,catch 块并不需要参数;在缺少参数的情况下,catch 块适用于任何 Exception 类。 例如,当从文件中进行读取时,可能会碰到 FileNotFoundException 或 IOException,首先,我们需要放置更详细的 FileNotFoundException 处理程序: try { // Code to open and read a file } catch (FileNotFoundException fe) { // Handle file not found exception first } catch (IOException ioe) { // Now handle any other IO exceptions } catch { // This block will catch all other exceptions } finally { // Executed whether or not an exception occurs, often to release resources } 通过从 Exception 派生,我们可以创建我们自己的非常类。例如,下面的代码创建了一个 InvalidDepartmentException 类,比方说,当某个部门的一个新雇员记录为无效时,我们可能引发该类。用户定义的非常的类构造函数使用 base 要害字来调用基类构造函数,并发送一个适当的消息: public class InvalidDepartmentException : System.Exception { public InvalidDepartmentException(string Department) : base( "Invalid Department: " + Department){ } } 那么,我们可以用下面的代码引发这种类型的非常: if (!(Department == "Sales" | Department == &quo 返回类别: 教程 上一教程: jbuilder 2005 update 4 下一教程: Java语言中字符的处理 您可以阅读与"Visual Studio:针对 Java 开发人员的 C# 编程语言"相关的教程: · 针对 Java 开发人员的 C# 编程语言 · javac-----Java编程语言编译器 · JAVA语言特点及开发工具JDK · 腾迅游戏开发人员采访实录 · JAVA语言的SOCKET编程 |
![]() ![]() |
快精灵印艺坊 版权所有 |
首页![]() ![]() ![]() ![]() ![]() ![]() ![]() |