目录结构:
接下来笔者将会对C#中的方法做详细阐述。
1.构造函数
我们首先来讲解一下C#中的构造函数,C#中的数据类型大致分为两类,分别是引用数据类型和值类型。构造函数又分为实例构造函数,和静态构造函数。
1.1 引用类型的构造函数
这种构造函数是最常见的,看下面的栗子:
class Program { //无参构造器 public Program() { } //带参数数组的构造器 public Program(Object[] para) { } //静态构造器 static Program() { } }
如果一个类中无构造器,那么会默认生成一个无参的构造器。
1.2 值类型的构造函数
值类型(Struct)构造器与引用类型构造器的工作方式完全不同,值类型总是运行创建值类型的构造器,并且允许值类型的实例化。
在值类型中使用构造器有如下几点需要注意:
1.值类型中不能显示定义无参构造器
2.C#中定义的构造器,必需显示调用才会执行。(C#的值类型默认不会调用值类型的无参构造器)
3.类型构造器中的字段只能访问静态字段
我们来看如下这个栗子:
struct Point { public Int32 m_x, m_y; public Point() { m_x = m_y = 5; } } class Rectangle { public Point m_topLeft, m_bottomRight; public Rectangle() { } }
上面程序中m_x,m_y的值既不是0,也不是5。编译器会直接报错,C#中是不允许在值类型中定义无参构造器的。
2. 析构函数
2.1 析构函数的使用
析构函数和构造函数的功能恰好相反,构造函数的作用是创建对象所需要的资源,而析构函数的作用是在对象销毁时回收资源。
注意:
1.不能在结构中定义析构函数,只能在类中使用析构函数。
2.一个类只能有一个析构函数。
3.无法继承或重载析构函数。
4.析构函数既没有修饰符,也没有参数。
5.不应使用空析构函数。如果类包含析构函数,Finalize 队列中则会创建一个项。调用析构函数时,将调用垃圾回收器来处理该队列。如果析构函数为空,只会导致不必要的性能损失。
6.程序员无法控制何时调用析构函数,因为这是由垃圾回收器决定的。垃圾回收器检查是否存在应用程序不再使用的对象。如果垃圾回收器认为某个对象符合析构,则调用析构函数(如果有)并回收用来存储此对象的内存。程序退出时也会调用析构函数。
7.可以通过调用 Collect 强制进行垃圾回收,但大多数情况下应避免这样做,因为这样会导致性能问题。
例如:
例如,下面是类 Car 的析构函数的声明:
class Car { ~Car() // destructor { // cleanup statements... } }
该析构函数隐式地对对象的基类调用 Finalize。这样,前面的析构函数代码被隐式地转换为以下代码:
protected override void Finalize() { try { // Cleanup statements... } finally { base.Finalize(); } }
这意味着对继承链中的所有实例递归地调用 Finalize 方法(从派生程度最大的到派生程度最小的)。
如果您的应用程序在使用昂贵的外部资源,我们还建议您提供一种在垃圾回收器释放对象前显式地释放资源的方式。可通过实现来自 IDisposable 接口的 Dispose 方法来完成这一点,该方法为对象执行必要的清理。这样可大大提高应用程序的性能。即使有这种对资源的显式控制,析构函数也是一种保护措施,可用来在对 Dispose 方法的调用失败时清理资源。
class First { ~First() { Console.WriteLine("First's destructor is called."); } } class Second : First { ~Second() { Console.WriteLine("Second's destructor is called."); } } class Third : Second { ~Third() { Console.WriteLine("Third's destructor is called."); } } class TestDestructors { static void Main() { Third t = new Third(); t = null; GC.Collect();//强制回收 Console.ReadLine(); } }
输出为:
Third's destructor is called.
Second's destructor is called.
First's destructor is called.
2.2 析构函数和Dispose()方法的区别
1. Dispose需要实现IDisposable接口。
2. Dispose由开发人员代码调用,而析构函数由GC自动调用。
3. Dispose方法应释放所有托管和非托管资源。而析构函数只应释放非托管资源。因为析构函数由GC来判断调用,当GC判断某个对象不再需要的时候,则调用其析构方法,这时候该对象中可能还包含有其他有用的托管资源。
4. 通过系统GC频繁的调用析构方法来释放资源会降低系统性能,所以推荐显示调用Dispose方法。
5. Dispose方法结尾处加上代码“GC.SuppressFinalize(this);”,即告诉GC不需要再调用该对象的析构方法,否则,GC仍会在判断该对象不再有用后调用其析构方法,虽然程序不会出错,但影响系统性能。
6、析构函数 和 Dispose 释放的资源应该相同,这样即使类使用者在没有调用 Dispose 的情况下,资源也会在 Finalize 中得到释放。
7、Finalize 不应为 public。
8、有 Dispose 方法存在时,应该调用它,因为 Finalize 释放资源通常是很慢的。
9、使用using可以自动调用对象的Dispose方法。
3.操作符重载
许多编程语言(比如C#)允许定义操作符应该如何操作类型的实例。例如,许多类型都重载了相等(==)和不等(!=)操作符。CLR对操作符重载一无所知,他甚至不知道什么是操作符。是编程语言定义了每个操作符的含义,已经当这些特殊符号出现时,应该生成什么样的代码。
CLR要求定义操作符的方法必须声明为public和static,例如:
class Complex { public static Complex operator+(Complex c1,Complex c2) { ... } }
用ildasm工具打开上面编译好的类,可以看到如下图所示:
从这里我们知道,操作符+ 会转化为 op_addition 方法。
下面列出C#中的一元和二元操作符,及其对应的特殊名:
C#的一元操作符:
C#操作符 | 特殊方法名 |
+ | op_UnaryPlus |
- | op_UnaryNegation |
! | op_LogicalNot |
~ | op_OnesComplement |
++ | op_Increment |
-- | op_Decrement |
op_True | |
op_False |
C#的二元操作符:
C#操作符 | 特殊方法名 |
+ | op_Addition |
- | op_Subtraction |
* | op_Multiply |
/ | op_Division |
% | op_Modulus |
& | op_BitwiseAnd |
| | op_BitwiseOr |
^ | op_ExclusiveOr |
<< | op_LeftShift |
>> | op_RightShift |
== | op_Equality |
!= | op_Inequality |
< | op_LessThan |
> | op_GreaterThan |
<= | op_LessThanOrEqual |
>= | op_GreaterThanOrEqual |
4.转化操作符方法
有时需要将对象从一种类型转化为另一种类型,当源类型和目标类型都是编译器识别的基元类型时,编译器自己就知道如何生成转化对象所需要的代码。
CLR要求转化操作符必须定义为public和static。在C#中,implicit关键字告诉编译器为了生成代码来调用方法,不需要在源代码中进行显示转化(隐式转化);explicit关键字告诉编译器需要在源代码中进行强制转化(显示转化)。
在implicit和explicit关键字之后,要指定operator关键字告诉编译器该方法是一个转化操作符。在operator指定要转化成什么类型,在圆括号内,则指定从什么类型转化。
栗子:
class Program { static void Main() { Rational r1 = 5;//隐式转化 Int32 i = (Int32)r1;//显示转化 Console.WriteLine(i.ToString()); Console.ReadLine(); } } public sealed class Rational { private Int32 num; //由一个Int32构造一个Rational public Rational(Int32 num) { this.num = num; } //指定从Int32到Radional为隐式转化 public static implicit operator Rational(Int32 i) { return new Rational(i); } //指定从Radional到Int32为显示转化 public static explicit operator Int32(Rational r) { return r.num; } }
使用ildasm工具打开上面的程序,会发现Ration显示转化方法的元数据为:
我们可以看出IL代码为:
public static Rational op_Implicit(Int32 num) public static Int32 op_explicit(Rational)
5.扩展方法
C#中允许扩展方法的使用,看如下栗子:
class Program { static void Main() { Program p = new Program(); p.Print(2); Console.ReadLine(); } } static class PrintClass { //定义一个扩展方法 public static void Print(this Program program, Int32 count) { for (Int32 i = 0; i < count; i++) { Console.WriteLine(i); } } }
使用C#的时候需要注意:
1.C#只支持扩展方法,不支持扩展属性、扩展事件、扩展操作符
2.扩展方法(第一个参数前面有this关键字)必需在非泛型的静态类中声明。
3.C#编译器在静态类中查找扩展方法时,要求静态类本身具有文件作用域。如果静态类嵌套在另一个类中,那么C#编译器就显示错误消息:“扩展方法必须在顶级静态类中定义,....”
4.多个静态类可以定义相同的扩展方法,如果编译器检测到存在两个或多个相同的扩展方法,那么就会报错。
5.扩展方法可能存在版本控制的问题。例如:在未来的版本中,为上面案例中Program类定义了Print实例方法,那么再重新编译我的代码,Print方法就会被绑定到Program的Print实例方法,这而不是静态的Print方法,显然这不是我期望的,程序可能会出现不可预期的行为。
6.分部方法(partial)
C#中提供了分布方法的功能,它允许方法的定义和方法的使用在不同的源文件中,
栗子:
//工具生成的代码,存储在某个源代码文件中 internal sealed partial class Base { private String m_name; //声明分部方法 partial void OnNameChanging(String value); public String Name { get { return m_name; } set { OnNameChanging(value);//通知类要进行更改了 m_name = value;//更改字段 } } }
//开发人员生成的代码,存在另一个源代码文件中 internal sealed partial class Base { //这是分部方法的实现,会在m_name更改前调用 partial void OnNameChanging(string value) { if (String.IsNullOrEmpty(value)) { throw new ArgumentException("value"); } } }
分部方法实现了在尽量少地破坏原来的代码的情况下,增加新的功能。
使用分部方法需要注意以下几点:
1.在使用分部方法的时,要进行方法的声明,要用partial关键字标记。
2.实现分部方法的声明时,要用partial关键字标记。
3.分部方法的返回类型始终是void,任何参数都不能用out来标记。
4.分部方法的声明和实现必需有完全一致的声明。
5.分部方法总是被视为private,但是C#编译器禁止在分部方法前添加private关键字。
7.外部方法(extern)
修饰符用于声明在外部实现的方法。extern 修饰符的常见用法是在使用 Interop 服务调入非托管代码时与 DllImport 属性一起使用;在这种情况下,该方法还必须声明为 static,
如下面的示例所示:
[DllImport("avifil32.dll")] private static extern void AVIFileInit();
栗子:
[DllImport("User32.dll",EntryPoint="MessageBox",CharSet=CharSet.Unicode)] public static extern int MyMessageBox(int h, string m, string c, int type); //hovertree.com static int Main() { string myString; Console.Write("Enter your message: "); myString = Console.ReadLine(); return MyMessageBox(0, myString, "My Message Box", 0); }
在上面的栗子中,DllImport引用的文件是User32.dll,这里还可以简化掉后缀,直接写为"User32"。