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,是多此一举的。仅仅是为了通过代码分析,让编译器帮你擦屁股。