Visual Studio:针对 Java 开发人员的 C# 编程语言(2)

http://tech.ddvip.com   2007年03月23日    社区交流

本文详细介绍Visual Studio:针对 Java 开发人员的 C# 编程语言(2)

  注意,派生类的构造函数隐式调用了基类(或 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("
A + 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 == "Marketing"))
{
 throw new InvalidDepartmentException(Department);
}

  注意,C# 不支持 checked 异常。在 Java 中,这些是使用 throws 关键字进行声明的,以便指定一个方法可能引发一个特殊类型的异常,此异常必须由调用代码进行处理。

高级 C# 技术索引器

  索引器提供了一种以与数组相同的方式访问类或结构的方法。例如,我们可能有表示我们公司内某个部门的类。这个类可以包含该部门中所有雇员的名字,而索引器可以允许我们访问这些名字,如下所示:

myDepartment[0] = "Fred";
myDepartment[1] = "Barney";

  等等。在类的定义中,通过定义具有如下签名的属性,可以启用索引器:

public type this [int index]

  然后,我们提供 get 和 set 方法作为标准属性,而当使用索引器时,这些访问器指定哪些内部成员被引用。

  在下面的简单示例中,我们创建了一个名为 Department 的类,此类使用索引器来访问该部门的雇员(在内部表示为一个字符串数组):

using System;
public class Department
{
 private string name;
 private const int MAX_EMPLOYEES = 10;
 private string [] employees = new string [MAX_EMPLOYEES];
 public Department(string deptName)
 {
  name = deptName;
 }
 public string this [int index]
 {
  get
  {
   if (index >= 0 && index < MAX_EMPLOYEES)
    {
    return employees[index];
   }
  else
   {
    throw new IndexOutOfRangeException();
    //return "Error";
   }
  }
  set
  {
   if (index >= 0 && index < MAX_EMPLOYEES)
   { 
    employees[index] = value;
   }
   else
   {
    throw new IndexOutOfRangeException();
    //return "Error";
   }
  }
 }
 // Other methods and properties as usual
}

  然后,我们可以创建这个类的一个实例,并且对其进行访问,如下所示:

using System;
public class SalesDept
{
 public static void Main(string[] args)
 {
  Department sales = new Department("Sales");
  sales[0] = "Nikki";
  sales[1] = "Becky";
  Console.WriteLine("The sales team is {0} and {1}", sales[0],
               sales[1]);
 }
}
属性
为了增加有关类型的声明性信息,C# 引入一种叫做属性的新机制。有关类型的其他信息放在类型定义前面的声明性标记中。下面的示例向您展示了如何利用 .NET 框架属性来修饰一个类或方法。
在下面的示例中,通过添加 WebMethod 属性,GetTime 方法被标记为一个 XML Web 服务。
 using System;
 using System.Web.Services;
 public class Utilities : WebService
 {
  [WebMethod]
  public string GetTime()
  {
    return DateTime.Now.ToShortTimeString();
  }
 }

  通过添加 CODE>WebMethod 属性,.NET 框架现在会自动处理调用此函数所必需的 XML/SOAP 交换。可以调用这个 Web 服务来检索下列值:

<?xml version="1.0" encoding="utf-8" ?> <string xmlns="http://tempuri.org/">7:26 PM</string>

  在下面的示例中,通过添加 Serializable() 属性,Employee 类被标记为 Serializable。虽然 salary 字段被标记为 public,但是它不会被序列化,因为它是用 NonSerialized() 属性标记的。

using System;
[Serializable()]    
public class Employee 
{
  public int ID;
  public string Name;    
  [NonSerialized()] public int Salary;
}

  有关创建自定义属性的信息,请参见 Creating Custom Attributes

委托 (Delegate)

  C++、Pascal 和其他一些支持函数指针概念的语言都允许我们在运行时选择我们希望调用的函数。

  Java 并没有提供任何具有函数指针功能的结构,但是 C# 却通过 System.Delegate 类提供了此功能。一个委托实例封装一个方法,它是一个可调用实体。

  对于实例方法,委托由包含类的实例以及该实例中的方法组成。对于静态方法,可调用实体由一个类和此类中的静态方法组成。因此,委托可用于调用任意对象的函数,并且委托是面向对象、类型安全且可靠的。

  在定义和使用委托时有三个步骤:

  声明

  实例化

  调用

  我们可以用下面的语法声明一个委托:

delegate void myDelegate();

  然后,就可以用这个委托来引用返回 void 而且不带任何参数的所有函数。

  类似地,要为带有 string 参数并返回 long 的任何函数创建一个委托,我们应该使用下面的语法:

delegate long myDelegate(string mystring);

  然后,我们就可以将该委托指派给具有此签名的任何方法,如下所示:

myDelegate operation = new myDelegate(methodName);重新指派委托

  委托对象是不可改变的,也就是说,与它们匹配的签名一旦设置就不能改变。然而,我们可以指向另一个方法,只要它们都具有相同的签名即可。例如:

delegate myDelegate(int a, int b)
myDelegate operation = new myDelegate(Add);
operation = new myDelegate(Multiply);

  这里,我们将 operation 重新指派给一个新的委托对象,这样 operation 就可以调用 Multiply 方法。只有 Add() 和 Multiply() 都具有相同的签名时,我们才可以这样做。

调用委托

  调用委托相当简单,只需要将委托变量的名称替换成方法的名称即可:

delegate long myDelegate(int i, int j);
myDelegate operation = new myDelegate(Add);
long lresult = operation(10, 20);

  这里用值 10 和 20 调用 Add 方法,返回一个长整型结果,并将其赋给变量 lresult。

  让我们创建一个程序来快速地演示委托的创建、实例化和调用:

using System;
public class DelegateClass
{
 delegate long myDelegate (int i, int j);
 public static void Main(string[] args)
 {
  myDelegate operation = new myDelegate(MathClass.Add);
  Console.WriteLine("Call to Add method through delegate");
  long l = operation(10, 20);
  Console.WriteLine("Sum of 10 and 20 is " + l);
  Console.WriteLine("Call to Multiply method thru delegate");
  operation = new myDelegate(MathClass.Multiply);
  l = operation(1639, 1525);
  Console.WriteLine("1639 multiplied by 1525 equals " + l);
 }
}
public class MathClass
{
 public static long Add (int i, int j)
 {
  return (i+j);
 }
 public static long Multiply (int i, int j)
 {
  return (i*j);
 }
}

  我们会得到这样的输出:

Call to Add method through delegate
Sum of 10 and 20 is 30
Call to Multiply method through delegate
1639 multiplied by 1525 equals 2499475

  如前所述,委托实例必须包含对象引用。在上面的示例中,通过将方法声明为静态的(意味着我们自己不需要指定对象引用),我们避免了这样做。然而,如果委托引用一个实例方法,就必须给出对象引用,如下所示:

MathClass obj = new MathClass();
myDelegate operation = new myDelegate(obj.Power);

  其中,Power 是 MathClass 方法的一个实例。因此,如果 MathClass 的方法没有声明为静态的,我们就可以通过委托来调用它们,如下所示:

using System;
public class DelegateClass
{
 delegate long myDelegate(int i, int j);
 public static void Main(string[] args)
 {
  MathClass mathObj = new MathClass();
  myDelegate operation = new myDelegate(mathObj.Add);
  Console.WriteLine("Call to Add method through delegate");
  long l = operation(10, 20);
  Console.WriteLine("Sum of 10 and 20 is " + l);
  Console.WriteLine("Call to Multiply method thru delegate");
  operation = new myDelegate(mathObj.Multiply);
  l = operation(1639, 1525);
  Console.WriteLine("1639 multiplied by 1525 equals " + l);
 }
}

  当这些方法声明为 static 时,如果您运行此程序,您就会得到同前面一样的输出。

委托和事件

  .NET 框架也将委托广泛应用于事件处理任务(像 Windows 或 Web 应用程序中的按钮单击事件)。虽然在 Java 中事件处理通常通过实现自定义侦听器类来完成,但是 C# 开发人员可以利用委托来进行事件处理。事件被声明为带有委托类型的字段,只是在事件声明前面加上 event 关键字。通常,事件被声明为公共的,但是任何可访问性修饰符都是允许的。下面的代码显示了委托和事件的声明。

public delegate void CustomEventHandler(object sender, EventArgs e);
public event CustomEventHandler CustomEvent;

  事件委托是多路广播的,这意味着它们可以具有对多个事件处理方法的引用。通过维护事件的注册事件处理程序列表,委托可以担当引发事件的类的事件发送程序。下面的示例向您展示了可以如何给多个函数预订事件。类 EventClass 包含委托、事件和调用事件的方法。注意,只能从声明事件的类中调用事件。然后,类 TestClass 就可以使用 +=/-= 运算符来预订/取消预订事件。当调用 InvokeEvent() 方法时,它会激发此事件,而已经预订此事件的任何函数都会同步激发,如下面的代码所示:

using System;
class TestClass
{
 static void Main(string[] args)
 {
  EventClass myEventClass = new EventClass();
  // Associate the handler with the events
  myEventClass.CustomEvent += new EventClass.CustomEventHandler(CustomEvent1);
  myEventClass.CustomEvent += new EventClass.CustomEventHandler(CustomEvent2);
  myEventClass.InvokeEvent();
  myEventClass.CustomEvent -= new EventClass.CustomEventHandler(CustomEvent2);
  myEventClass.InvokeEvent();
 }
 private static void CustomEvent1(object sender, EventArgs e)
 {
  Console.WriteLine("Fire Event 1");
 }
 private static void CustomEvent2(object sender, EventArgs e)
 {
  Console.WriteLine("Fire Event 2");
 }
}
public class EventClass
{
 public delegate void CustomEventHandler(object sender, EventArgs e);
 //Declare the event using the delegate datatype
 public event CustomEventHandler CustomEvent;
 public void InvokeEvent()
 {
  CustomEvent(this, EventArgs.Empty);
 }
}

  此程序的输出如下:

Fire Event 1
Fire Event 2
Fire Event 1
垃圾回收
在 C 和 C++ 中,许多对象一旦声明,就需要编程人员在对象可以安全使用之前给它们分配资源。在对象使用完资源之后,将这些资源释放回自由内存池也是编程人员的责任。如果资源没有释放,当越来越多的资源被不必要地消耗时,就可以说代码泄漏内存。而在另一方面,如果资源过早地释放,则可能会发生数据丢失、其他内存区域破坏和 null 指针异常。
为了防止这些危险的发生,Java 和 C# 都通过一个应用程序来独立地管理所有对象的生命周期。
在 Java 中,JVM 通过跟踪所分配资源的引用来处理未使用的内存的释放。只要 JVM 检测到一个资源不再被一个有效的引用加以引用,该资源就会被当作垃圾回收。
在 C# 中,通过具有与 JVM 类似功能的公共语言运行库 (CLR) 来处理垃圾回收。CLR 垃圾回收器周期性检查内存堆,以查看是否有未引用的对象,并释放这些对象所占用的资源。
安全代码和不安全代码
C# 的一个特别值得注意的特性是它支持非类型安全代码。通常,CLR 负责监视 IL(Intermediate Language,中间语言)代码的行为,并阻止任何有问题的操作。然而,有时我们希望直接访问低级功能(例如 Win32 API 调用),只要我们负责保证这样的代码操作正确,我们就会被允许这样做。这样的代码必须放在源代码的不安全块中。
unsafe 关键字
进行低级 API 调用、使用指针算法、执行一些其他不安全操作的 C# 代码必须放在用 unsafe 关键字标记的块中。下面任何一种情况都可以标记为 unsafe:

一个完整的方法

一段放在括号中的代码块

一个单独的语句

下面的示例演示了上面所有三种情况中 unsafe 的使用:
using System;
class UnsafeClass
{
 unsafe static void PointyMethod()
 {
  int i=10;
  int *p = &i;
  Console.WriteLine("*p = " + *p);
  string address = "Pointer p = " + int.Format((int) p, "X");
  Console.WriteLine(address);
 }
static void StillPointy()
{
 int i=10;
 unsafe
 {
  int *p = &i;
  Console.WriteLine("*p = " + *p);
  string address = "Pointer p = " + int.Format((int) p, "X");
  Console.WriteLine(address);
 }
}
static void Main()
{
 PointyMethod();
 StillPointy();
}
 }

  在这段代码中,整个 PointyMethod() 方法被标记为 unsafe,因为该方法声明并使用了指针。与这段代码一样,一旦某个代码块再次使用指针,StillPointy() 方法就将其标记为 unsafe。

  有关不安全代码的更多信息,请参见 Unsafe at the Limit

fixed 关键字

  在安全代码中,垃圾回收器在其生命周期内可以相当自由地移动一个对象,它的任务是组织和压缩可用资源。然而,如果我们的代码使用指针,这种行为就可能很容易导致意外的结果,因此,我们可以使用 fixed 关键字来指示垃圾回收器不要移动特定的对象。

  下面的代码展示了如何使用 fixed 关键字来确保在 PointyMethod() 方法中的代码块执行期间系统不会移动数组。注意,fixed 仅仅使用在不安全代码中:

public class FixedClass
{
 public static void PointyMethod(char[] array)
 {
  unsafe
  {
   fixed (char *p = array)
   {
    for (int i=0; i&lt;array.Length; i++)
    {
     Console.Write(*(p+i));
    }
   }
  }
 }
 static void Main ()
 {
  char[] array = { 'H', 'e', 'l', 'l', 'o' };
  PointyMethod(array);
 }
}
小结
虽然 Microsoft 和其他厂商都已经为 .NET 平台引入了许多语言,但是 C# 是非常类似 Java 的一种语言,并且它非常适合希望从 J2EE 迁移到 .NET 平台的开发人员。
本文比较和对比了这两种语言。在许多方面,C# 具有 C++ 的强大功能、Java 的简洁优美,以及 Visual Basic 开发的简易性,我希望本文已经说明了这一点。

责编:豆豆技术应用

正在加载评论...