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