.NET,你忘记了么?(二)——使用using清理非托管资源

本文探讨了Dispose和Finalize在资源管理中的作用,特别是对于非托管资源的处理方式,并介绍了如何通过IDisposable接口及using语句来优雅地管理资源。

我们都知道,垃圾回收可以分为Dispose和Finalize两类,关于这两者的区别已经太多了,一个是正常的垃圾回收GC所调用的方法,另外一个是终结器Finalizer,所调用的方法,在Effective C#一书中,有着明确的建议是说使用IDispose接口来代替Finalize。原因是因为Finalize终结会增加垃圾回收对象的代数,从而影响垃圾回收。

有了上述的原因,我们现在只来看使用IDispose接口的类。

在.NET中,绝大多数的类都是运行在托管的环境下,所以都由GC来负责回收,那么我们就不需要实现IDispose接口,而是由GC来自动负责。可是有一些类使用的是非托管资源,那么这个时候,我们就应该去实现IDispose接口,说个比较常用的SqlConnection之类。

写段常用的连接SQL语句的模型:

string connectionString = System.Configuration.ConfigurationManager.ConnectionStrings["Study1ConnectionString1"].ConnectionString;
SqlConnection thisConnection = new SqlConnection(connectionString);
thisConnection.Open();
SqlCommand thisCommand = new SqlCommand();
thisCommand.Connection = thisConnection;
thisCommand.CommandText = "select * from [User]";
thisCommand.ExecuteNonQuery();
thisConnection.Close();

其实,作为非托管资源,为了防止我们忘记调用Close,一般都实现了Finalize,因此,即使我们没有Close掉,也会由终结器将这块内存回收。但是,就增加了这块垃圾的代数。

假设说我们写了这样的代码:

string connectionString = System.Configuration.ConfigurationManager.ConnectionStrings["Study1ConnectionString1"].ConnectionString;
SqlConnection thisConnection = new SqlConnection(connectionString);
thisConnection.Open();
SqlCommand thisCommand = new SqlCommand();
thisCommand.Connection = thisConnection;
thisCommand.CommandText = "select * form [User]";    //SQL语句错误
thisCommand.ExecuteNonQuery();
thisConnection.Close();

这样的话,我们打开的SqlConnection就没有关闭,只能等待Finalize去关闭了。

这是非常不好的做法。于是,我们可以想到异常处理:

SqlConnection thisConnection = null;
try
{
    string connectionString = System.Configuration.ConfigurationManager.ConnectionStrings["Study1ConnectionString1"].ConnectionString;
    thisConnection = new SqlConnection(connectionString);
    thisConnection.Open();
    SqlCommand thisCommand = new SqlCommand();
    thisCommand.Connection = thisConnection;
    thisCommand.CommandText = "select * form [User]";
    thisCommand.ExecuteNonQuery();
}
finally
{
    if (thisConnection != null)
    {
        thisConnection.Close();
    }
}

这样做就不错了,但是代码看起来有些丑陋,可是使用using就让代码优雅了很多,这也是C#比JAVA棒很多的地方,呵呵!

string connectionString = System.Configuration.ConfigurationManager.ConnectionStrings["Study1ConnectionString1"].ConnectionString;
using (SqlConnection thisConnection = new SqlConnection())
{
    thisConnection.Open();
    SqlCommand thisCommand = new SqlCommand();
    thisCommand.Connection = thisConnection;
    thisCommand.CommandText = "select * form [User]";
    thisCommand.ExecuteNonQuery();
}

 

代码量是不是小了很多呢?优雅了许多呢!

其实,在IL的位置,代码仍然是一样的,他同样把代码给编译成了try-finally的处理形式!

接下来,再来看下我们常用的使用数据库的方式:

string connectionString = System.Configuration.ConfigurationManager.ConnectionStrings["Study1ConnectionString1"].ConnectionString;
SqlConnection thisConnection = new SqlConnection(connectionString);
thisConnection.Open();
SqlCommand thisCommand = new SqlCommand();
thisCommand.Connection = thisConnection;
thisCommand.CommandText = "select * from [User]";
SqlDataReader thisReader = thisCommand.ExecuteReader();
thisReader.Close();
thisConnection.Close();

 

还是上面的问题,我们考虑用using语句来将之代码重构:

string connectionString = System.Configuration.ConfigurationManager.ConnectionStrings["Study1ConnectionString1"].ConnectionString;
using (SqlConnection thisConnection = new SqlConnection(connectionString))
{
    thisConnection.Open();
    SqlCommand thisCommand = new SqlCommand();
    thisCommand.Connection = thisConnection;
    thisCommand.CommandText = "select * from [User]";
    using (SqlDataReader reader = thisCommand.ExecuteReader())
    {
        while (reader.Read())
        { 
            //操作
        }
    }
}

我先把这段代码翻译成我们熟悉的try-finally的处理形式:

SqlConnection thisConnection = null;
try
{
    string connectionString = System.Configuration.ConfigurationManager.ConnectionStrings["Study1ConnectionString1"].ConnectionString;
    thisConnection = new SqlConnection(connectionString);
    thisConnection.Open();
    SqlCommand thisCommand = new SqlCommand();
    thisCommand.Connection = thisConnection;
    thisCommand.CommandText = "select * from [User]";
    SqlDataReader reader = null;
    try
    {
        reader = thisCommand.ExecuteReader();
        while (reader.Read())
        {
            //操作
        }
    }
    finally
    {
        reader.Close();
    }
}
finally
{
    thisConnection.Close();
}

更丑陋的代码吧!所以有个原则是:尽量避免using语句的嵌套。

怎么样解决呢?很容易,自己写我们的try-finally吧!

SqlConnection thisConnection = null;
SqlDataReader reader = null;
try
{
    string connectionString = System.Configuration.ConfigurationManager.ConnectionStrings["Study1ConnectionString1"].ConnectionString;
    thisConnection = new SqlConnection(connectionString);
    thisConnection.Open();
    SqlCommand thisCommand = new SqlCommand();
    thisCommand.Connection = thisConnection;
    thisCommand.CommandText = "select * from [User]";
    reader = thisCommand.ExecuteReader();
    while (reader.Read())
    {
        //操作
    }
}
finally
{
    if (thisConnection != null)
    {
        thisConnection.Close();
    }
    if (reader != null)
    {
        reader.Close();
    }
    
}

这样就好了!

关于using 的这节我就写到这,最后对全文做个总结,其实就是一句话:尽量使用using来进行非托管资源的资源回收。

### 托管堆与非托管堆的概念及区别 #### 定义 托管堆是指由运行时环境(如.NET Framework的CLR或Java的JVM)管理的一块内存区域。在这块区域内,所有的对象都由垃圾回收器负责分配和释放。这意味着开发人员不需要手动管理这些对象的生命周期[^4]。 非托管堆则是指不由运行时环境直接管理的内存区域。在这种模式下,程序员需要显式地调用诸如`malloc`和`free`这样的函数来分配和释放内存。这种机制常见于传统的C/C++程序中[^5]。 --- #### 区别 1. **内存管理方式** - 在托管环境中,内存分配通过`new`关键字完成,而释放则完全依赖于垃圾回收器(Garbage Collector, GC),无需开发者干预。 - 非托管环境下,内存分配和释放分别通过`malloc`/`calloc`以及`free`实现,这要求开发者严格控制资源的使用以防止泄漏或悬空指针等问题。 2. **性能特性** - 使用托管堆可能会引入一定的开销,因为GC会周期性暂停应用线程来进行清理工作。然而,这种方式简化了复杂场景下的内存管理逻辑。 - 对于非托管堆而言,虽然提供了更高的灵活性和潜在效率增益,但如果缺乏良好的实践,则容易造成难以追踪的错误,比如双重释放或者访问已销毁的对象地址等风险[^3]。 3. **适用范围** - 当涉及到跨平台兼容性和多语言协作项目时,采用基于CLR构建的应用更倾向于利用其内置支持功能丰富的API集合;同时也能更好地保障系统的稳定性与安全性。 - 如果目标侧重于底层硬件交互或是追求极致速度优化的任务,则可能更适合选用原生解决方案——即继续沿用经典的C / C ++风格做法[^1]。 4. **互操作性** - 由于存在桥接层使得不同技术栈之间能够无缝衔接起来,所以即使是在纯.NET生态之外仍然允许部分程度上的混合编程形式存在。例如可以通过PInvoke等方式调用外部DLL文件内的方法从而间接触达某些特定领域专用库所提供的服务[^2]。 - 而对于纯粹意义上的未受控情形来说就没有这么便利了,往往需要额外编写封装代码才能达到相同效果。 ```cpp // 示例:在托管C++中创建一个简单的托管类并实例化 __gc class ManagedClass { public: void SayHello() { System::Console::WriteLine("Hello from managed code!"); } }; void TestManagedHeap() { ManagedClass* mc = new ManagedClass(); // 创建托管对象 mc->SayHello(); } // 此处mc会被自动回收,无需手动删除 ``` ```c++ // 示例:传统C++中的动态内存分配与释放 #include <cstdlib> using namespace std; int main(){ int *arr=(int*) malloc(5*sizeof(int)); if(!arr){ return -1; } free(arr); } ``` ---
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值