c#

位置:IT落伍者 >> c# >> 浏览文章

通过C#中的解构器编写可靠高效的应用程序


发布日期:2023年12月19日
 
通过C#中的解构器编写可靠高效的应用程序

引言

在企业应用开发世界性能灵活性和安全性是最重要的我作为一个VC++程序员开始我的职业生涯并且在一个晴朗的早晨我被转到了Web开发部像每个C++程序员一样我也很失落我想每个像TomDick甚至Harry能用HTML编程然而不久我就发现真正的挑战是生产高性能的灵活的可靠的应用程序综上所述Web环境松耦合的不分国界的本质将使你永远神往

为了制作高性能的灵活的应用程序用最优化的方式使用你的资源是至关重要的一个技巧是尽可能晚地使用你的资源并且在使用后尽快释放它我在这里的意图是描述在C#中的对象清除机制

解构器

我们知道解构器被用来清除类的事例当我们在C#中使用解构器是我们必须记住以下几点

一个类只能有一个解构器

解构器不能被继承或重载

解构器不能被调用他们是自动被(编译器)调用的

解构器不能带修饰或参数

下面是类MyClass解构器的一个声明

~ MyClass()

{

// Cleaning up code goes here

}

程序员不能控制解构器何时将被执行因为这是由垃圾收集器决定的垃圾收集器检查不在被应用程序使用的对象它认为这些条件是符合清楚的并且收回它们的内存解构器也在程序退出时被调用当解构器执行时其背后所发生的那一幕是解构器隐式调用对象基类的ObjectFinalize方法因此上述解构器代码被隐含转化成

protected override void Finalize()

{

try

{

// Cleaning up

}

finally

{

baseFinalize();

}

}

现在让我们看一个解构器怎样被调用的例子我们有三个类AB和C B派生自AC派生自B每个类有它们自己的构造器和解构在类App的main函数中我们创建C的对象

using System;

class A

{

public A()

{

ConsoleWriteLine(Creating A);

}

~A()

{

ConsoleWriteLine(Destroying A);

}

}

class B:A

{

public B()

{

ConsoleWriteLine(Creating B);

}

~B()

{

ConsoleWriteLine(Destroying B);

}

}

class C:B

{

public C()

{

ConsoleWriteLine(Creating C);

}

~C()

{

ConsoleWriteLine(Destroying C);

}

}

class App

{

public static void Main()

{

C c=new C();

ConsoleWriteLine(Object Created );

ConsoleWriteLine(Press enter to Destroy it);

ConsoleReadLine();

c=null;

//GCCollect();

ConsoleRead();

}

}

正如我们预料的基类的构造器将会被执行并且程序会等待用户按enter当这个发生我们把类C的对象置为null但解构器没有被执行!!??正像我们所说的程序员无法控制解构器何时被执行因为这是由垃圾搜集器决定的但程序退出时解构器被调用了你能通过重定向程序的o/p到文本文件来检查这个我将它输出在这里注意到基类的解构器被调用了因为在背后baseFinalize()被调用了

Creating A

Creating B

Creating C

Object Created

Press enter to Destroy it

Destroying C

Destroying B

Destroying A

所以如果一旦你使用完对象你就想调用解构器你该怎么做?有两个方法

调用垃圾搜集器来清理

实现IDisposable的Dispose方法

调用垃圾搜集器

你能通过调用GCCollect方法强制垃圾搜集器来清理内存但在大多数情况下这应该避免因为它会导致性能问题在上面的程序中在GCCollect()处移除注释编译并运行它现在你能看到解构器在控制台中被执行了

实现IDisposable接口

IDisposable 接口包括仅有的一个公共方法其声明为void Dispose()我们能实现这个方法来关闭或释放非托管资源如实现了这个接口的类事例所控制的文件和句柄等这个方法被用做所有任务联合对象的资源释放当实现了这个方法对象必须寻求确保所有拥有的资源被继承结构中关联的资源也释放(不能把握翻不出来)

class MyClass:IDisposable

{

public void Dispose()

{

//implementation

}

}

当我们实现了IDisposable接口时我们需要规则来确保Dispose被适当地调用

联合使用解构器和IDisposable接口

Public class MyClass:IDisposable

{

private bool IsDisposed=false;

public void Dispose()

{

Dispose(true);

GCSupressFinalize(this);

}

protected void Dispose(bool Diposing)

{

if(!IsDisposed)

{

if(Disposing)

{

//Clean Up managed resources

}

//Clean up unmanaged resources

}

IsDisposed=true;

}

~MyClass()

{

Dispose(false);

}

}

在这里重载了Dispose(bool)来做清理工作并且所有的清理代码都仅写在这个方法中这个方法被解构器和IDisposableDispose()两着调用我们应该注意Dispose(bool)没有在任何地方被调用除了在IDisposableDispose()和解构器中

当一个客户调用IDisposableDispose()时客户特意地想要清理托管的和非托管资源并且因此完成清理工作有一件你必须注意的事情是我们在清理资源之后立即调用了GCSupressFinalize(this)这个方法通知垃圾搜集器不需要调用解构器因为我们已经做了清理

注意上面的例子解构器使用参数false调用Dispose这里我们确信垃圾搜集器搜集了托管资源我们仅仅做非托管资源的清理

结论

尽管如此我们花费一些时间实现IDisposable接口如果客户不能合适地调用它们会怎样?为此C#有一个酷的解决方案using代码块它看起来像这样

using (MyClass objCls =new MyClass())

{

}

上一篇:基类和子类的调用顺序 (C#,java)

下一篇:在 C# 中处理结构内的数组源代码分析