C#一大牛逼之处就是using语句简化了我们的程序。它的作用是在using结束后,立即释放被引用的资源。实质其实是调用被引用对象的Dispose()方法。这也是为喵被using的类型一定要实现IDisposable接口。虽然在.NET和JAVA里,程序员都不用关心手动释放内存的问题,但我认为在使用资源的时候加上using,是一种良好的习惯。有时候如果你不释放资源的话,可能会发生独占的问题。
好了,扯多了,话说回来,看看我今天遇到的一个有意思的问题。
今天心血来潮,想检验一下自己的代码,于是给项目启用了代码分析,用的是“Microsoft最少量建议规则”。结果以下代码产生了编译警告:
public DataSet ExecuteDataSet(DbCommand cmd, Trans t)
{
cmd.Connection = t.DbConnection;
cmd.Transaction = t.DbTrans;
DbProviderFactory dbfactory = DbProviderFactories.GetFactory(DbHelper._dbProviderName);
DbDataAdapter dbDataAdapter = dbfactory.CreateDataAdapter();
dbDataAdapter.SelectCommand = cmd;
DataSet ds = new DataSet();
dbDataAdapter.Fill(ds);
this._connection.Close();
return ds;
}
CA2000 : Microsoft.Reliability : 在方法 'DbHelper.ExecuteDataSet(DbCommand, Trans)' 中,'ds' 对象未按所有异常路径释放。请在对 'ds' 对象的所有引用超出范围之前,对该对象调用 System.IDisposable.Dispose。
于是我给ds套了个using,警告就木了:
public DataSet ExecuteDataSet(DbCommand cmd, Trans t)
{
cmd.Connection = t.DbConnection;
cmd.Transaction = t.DbTrans;
DbProviderFactory dbfactory = DbProviderFactories.GetFactory(DbHelper._dbProviderName);
DbDataAdapter dbDataAdapter = dbfactory.CreateDataAdapter();
dbDataAdapter.SelectCommand = cmd;
using (DataSet ds = new DataSet())
{
dbDataAdapter.Fill(ds);
this._connection.Close();
return ds;
}
}
但我突然觉得不对劲啊。但我担心ds是否真的能return出去。因为using完成后,ds就被释放了,所以调用这个方法可能获取不到原本想要的那个ds。
于是我上微博向微软MVP求教,老徐给我的答复是:
老徐FrankXuLei:如果再返回前调用了Dispose,应该对象就无法访问了,资源可能被释放了,这里要返回该对象的有效引用,不需要使用Using
看来是有点问题。于是我写了个小测试,预期结果是Query方法返回null:
static void Main(string[] args)
{
DataSet ds = Query("SELECT * FROM Feedback");
Console.WriteLine(ds.Tables[0].Rows[0]["Username"].ToString());
Console.ReadKey();
}
public static DataSet Query(string SQL)
{
using (OleDbConnection conn = new OleDbConnection(@"Provider=Microsoft.Jet.OLEDB.4.0;Data Source=E:\test.mdb"))
{
if (conn.State != ConnectionState.Open)
{
conn.Open();
}
try
{
using (DataSet Ds = new DataSet())
{
OleDbDataAdapter comm = new OleDbDataAdapter(SQL, conn);
comm.Fill(Ds, "ds");
return Ds;
}
}
catch
{
return null;
}
}
}
结果让我感到意外,程序居然得到了数据,说明Ds对象还活着!

我纳闷了,既然我都写了using,那Ds的Dispose()方法应该会被立即调用,Ds如果被销毁了,就应该return null出去。既然能读出数据,说明Ds活着,没有被销毁,那我们的using都干什么去了呢?Dispose()方法什么时候被调用呢?
于是我掏出了.NET程序猿必备神器Reflector一探究竟:

编译器发现我要return一个被引用的对象,而且这个对象会被改变,于是编译器很聪明的创建了一个临时变量CS$1$0000,把Ds交给这个变量,最后return的也不是Ds了(因为Ds已经被using消灭了)。
我瞬间觉得自己弱爆了,智商输给了编译器!
不过由此看来,在这种场景下写using,是多此一举的。仅仅是为了通过代码分析,让编译器帮你擦屁股。