在c#中处理相互引用的多个对象

时间:2022-10-21 11:23:51

Okay, I've searched "similar" topics but still haven't come across any answer to what I'm going to ask here.

好吧,我搜索过“类似”主题,但仍未找到任何答案,我将在这里提出要求。

I have a function that creates multiple sql objects under the System.Data.SqlClient namespace. I've read that the using statement disposes of an object after the using block but the variables declared are readonly. My function reuses some of these variables, so I can't really declare them within a using statement.

我有一个函数,在System.Data.SqlClient命名空间下创建多个sql对象。我已经读过using语句在using块之后处理一个对象但是声明的变量是readonly。我的函数重用了其中一些变量,因此我无法在using语句中声明它们。

Here is the body of my function for clarity. Should I call Dispose on the other objects (command, transaction, reader, etc) or will using recursively dispose of them through the connection object? How should I dispose these objects?

为清晰起见,这是我的功能正文。我应该在其他对象(命令,事务,阅读器等)上调用Dispose还是通过连接对象使用递归方式处理它们?我该如何处理这些物品?

I'm still new to C# (I come from C/C++ background) so please forgive me if the question sounds very ignorant.

我还是C#的新手(我来自C / C ++背景)所以如果这个问题听起来很无知,请原谅我。

public string SignIn(string userId, string password)
{
     SqlCommand sqlCommand = null;
     SqlTransaction sqlTransaction = null;
     string sessionId = "";

     using(SqlConnection sqlConnection = new SqlConnection Properties.Settings.Default.SessionManagerDBConnectionString))
     {
          try
          {
               sqlConnection.Open();

               sqlCommand = sqlConnection.CreateCommand();
               sqlCommand.CommandText = "GetUserByUserIdPassword";
               sqlCommand.CommandTimeout = 30;
               sqlCommand.CommandType = CommandType.StoredProcedure;
               SqlParameter parameterUserId = sqlCommand.Parameters.Add("@UserId", SqlDbType.NVarChar, 32);
               parameterUserId.Value = userId;
               SqlParameter parameterPassword = sqlCommand.Parameters.Add("@Password", SqlDbType.NChar, 64);
               parameterPassword.Value = this.GetSHA256Hash(password);
               sqlTransaction = sqlConnection.BeginTransaction("SampleTransaction");

               // more database activity, execute command, store results in datareader
               sqlTransaction.Commit();
               sqlConnection.Close();
          }
          catch (SqlException ex)
          {
               if(sqlTransaction != null)
                    sqlTransaction.Rollback();
               MessageBox.Show(ex.Number + ":" + ex.Message, ex.Server + ":" + ex.Source, MessageBoxButtons.OK, MessageBoxIcon.Error);
          }
     }
     return sessionId;
}

I tried to search for similar questions again and found some closer answers.

我试图再次搜索类似的问题,并找到了一些更接近的答案。

Is SqlCommand.Dispose() required if associated SqlConnection will be disposed?

如果关联的SqlConnection将被处理,是否需要SqlCommand.Dispose()?

Does SqlCommand.Dispose close the connection?

SqlCommand.Dispose是否关闭连接?

I suppose I should add a finally clause to my try-catch and call several Dispose methods there for all the sql objects I've created. I hope that suffices or is there a recommended style of doing this?

我想我应该在我的try-catch中添加一个finally子句,并为我创建的所有sql对象调用几个Dispose方法。我希望这样做是否足够或是否有推荐的风格?

finally
{
     if(sqlCommand != null)
          sqlCommand.Dispose();
     if(sqlTransaction != null)
          sqlTransaction.Dispose();
     ...
}

I tried putting a using statement within the try-catch block for one of the sqlCommand objects, but if that part of the code aborts when an exception is thrown, the execution jumps down to the catch portion. The using does not dispose that sqlCommand object.

我尝试在try-catch块中为其中一个sqlCommand对象放置一个using语句,但如果抛出异常时代码的那一部分中止,则执行会跳转到catch部分。使用不会处置sqlCommand对象。

try
{
     ...
     using(sqlCommand = sqlConnection.CreateCommand())
     {
          sqlCommand.CommandText = "GetUserByUserIdPassword2";
          sqlCommand.CommandTimeout = 30;
          sqlCommand.CommandType = CommandType.StoredProcedure;
          SqlParameter parameterUserId = sqlCommand.Parameters.Add("@UserId", SqlDbType.NVarChar, 32);
          parameterUserId.Value = userId;
          SqlParameter parameterPassword = sqlCommand.Parameters.Add("@Password", SqlDbType.NChar, 64);
          parameterPassword.Value = this.GetSHA256Hash(password);

          SqlDataReader reader = sqlCommand.ExecuteReader(CommandBehavior.SingleRow);
          // throws exception, no stored procedure "GetUserByUserIdPassword2"
     }
     ...
}
catch() {}

// sqlCommand still accessible at this point because using above was "aborted".

4 个解决方案

#1


2  

If the object implements IDisposable, then use using. If you need to create a new SqlCommand for some reason then finish one using block and start a new one. Or nest them if you still need access to the first SqlCommand.
You can reuse SqlCommand objects, as long as you haven't got a datareader still open from the command. So, you could create a SqlCommand, set all its properties and execute it, then reset all its properties and execute it again and so on. This does save slightly on the costs of memory allocation, but I think it also reduces the clarity of the code, so it is something I would only do if profiling proved it necessary.

如果对象实现了IDisposable,则使用using。如果由于某种原因需要创建一个新的SqlCommand,那么使用block完成一个并启动一个新的。如果您仍然需要访问第一个SqlCommand,请嵌套它们。您可以重用SqlCommand对象,只要您还没有从命令中打开一个datareader。因此,您可以创建一个SqlCommand,设置其所有属性并执行它,然后重置其所有属性并再次执行它,依此类推。这确实略微节省了内存分配的成本,但我认为它也降低了代码的清晰度,所以只有在分析证明它是必要的时候才会这样做。

#2


1  

will using recursively dispose of them through the connection object

将通过连接对象递归处理它们

Of course, using knows nothing about SqlConnections or other ADO.NET objects. All it knows is to call Dispose. Whatever the object being disposed does is what happens.

当然,使用对SqlConnections或其他ADO.NET对象一无所知。它只知道叫Dispose。无论处置的对象是什么,都会发生什么。

It happens to be the case that disposing a SqlConnection also disposes of the resources of all readers and commands associated with the connection. You only need to dispose the connection.

碰巧是,处理SqlConnection还会处理与连接相关的所有读取器和命令的资源。您只需要处理连接。

I don't know whether this is documented on MSDN but I know it from decompiling the assemblies. For compatibility reasons they can never change this behavior so it is safe to rely on it.

我不知道这是否在MSDN上有记录,但我知道它是通过反编译程序集来实现的。出于兼容性原因,他们永远不会改变这种行为,因此依赖它是安全的。

In general, you must call dispose on any object implementing IDisposable. Exception to this rule is when you know for sure that it is safe to not call Dispose. Like in this case.

通常,您必须在实现IDisposable的任何对象上调用dispose。此规则的例外情况是,当您确定不调用Dispose时是安全的。就像在这种情况下。

#3


1  

I think I found the answer!

我想我找到了答案!

Even though the sqlCommand object was still accessible in the bottom parts of the code when a nested using statement was skipped by a thrown exception, sqlCommand is still disposed later on. I tested this by actually assigning a function to the disposed event of said sqlCommand object.

即使抛出的异常跳过了嵌套的using语句,仍然可以在代码的底部访问sqlCommand对象,sqlCommand仍会在稍后处理。我通过实际为所述sqlCommand对象的处理事件分配一个函数来测试它。

The code here is slightly different than above because of the transaction object requirement. But the logic is essentially the same.

由于事务对象的要求,此处的代码与上面的代码略有不同。但逻辑基本相同。

try
{
       using(sqlCommand = new SqlCommand("GetUserByUserIdPassword2", sqlConnection, sqlTransaction))
       {
              sqlCommand.CommandTimeout = 15;
              sqlCommand.CommandType = CommandType.StoredProcedure;
              SqlParameter parameterUserId = sqlCommand.Parameters.Add("@UserId", SqlDbType.NVarChar, 32);
              parameterUserId.Value = userId;
              SqlParameter parameterPassword = sqlCommand.Parameters.Add("@Password", SqlDbType.NChar, 64);
              parameterPassword.Value = this.GetSHA256Hash(password);

              sqlCommand.Disposed += new System.EventHandler(this.sqlCommand_Disposed);

              SqlDataReader sqlDataReader = sqlCommand.ExecuteReader(CommandBehavior.SingleRow);
              // exception thrown, no sored proc "GetUserByUserIdPassword2"
              sqlDataReader.Close();
       }
}
catch(...) {}

...

private void sqlCommand_Disposed(object sender, EventArgs e)
{
     MessageBox.Show("sqlCommand has been disposed");
}

So, basically, even if a nested using statement is "aborted" by a thrown exception within the try block, and execution is skipped down to the catch block, the Dispose method is still called for that object after the function exits (or when the variable goes out of scope).

因此,基本上,即使嵌套的using语句被try块中的抛出异常“中止”,并且执行被跳过到catch块,在函数退出之后(或者当变量超出范围)。

I presume this behavior is the same for any number of nested using statements.

我认为这种行为对于任何数量的嵌套使用语句都是相同的。

#4


-1  

in CSharp, using is a special keyword which have different acts in code..

在CSharp中,using是一个特殊的关键字,在代码中有不同的行为。

if you put using to top of the codepage then using acts as c++ ' s Include or VB (and some other script langs') Import

如果你把use放到代码页的顶部然后使用act作为c ++的Include或VB(和其他一些脚本langs')导入

if you write a "using" which follows by a paranthesis then it acts like an alias of Dispose() method of CSharp.. or Destructor tilda of c++ or free() method of plain c..

如果你写一个“使用”后跟一个paranthesis那么它就像CSharp的Dispose()方法或c ++的destructor tilda或普通c的free()方法的别名。

About SQL Connection.. Best way is first Close() the connection (so with this, connection is still alive -actually just ready to use- but null.. return to wait new order in connection pool.. ) and then dispose if you need..

关于SQL连接..最好的方法是先关闭()连接(所以这个,连接仍然活着 - 实际上只是准备使用 - 但是null ..返回等待连接池中的新订单..)然后处理如果你需要..

if you have still problems and suspected that calling the Dispose() method not enough to free the resources

如果你仍然有问题,并怀疑调用Dispose()方法不足以释放资源

then you can use Garbage Collector's Collect() static method to force the garbage collection But not so much recommended by Microsoft because of face with accidental garbage collection problems (i.e. you collect the form but need some variable from that form )

然后你可以使用垃圾收集器的Collect()静态方法强制垃圾收集但是由于面对意外的垃圾收集问题而没有那么多推荐(即你收集表单但需要从该表单中获得一些变量)

in example of usage :

在使用示例中:

public void Dispose (bool ForceToCollect )
{
   if (ForceToCollect) 
               GC.Collect (0, GCCollectionMode.Forced );
}

In Gc.Collect() method you can set which group of data memory freed with first option where is int..if you plan to free resources of the last group - means last created group of items, as an example last created form instance and its all child controls - then int should be 0; the one which is 1 before the last created then should be 1 and so on..

在Gc.Collect()方法中,您可以设置使用第一个选项释放哪个数据内存组,其中int为int ..如果您计划释放最后一个组的资源 - 表示最后创建的项目组,作为示例最后创建的表单实例和它的所有子控件 - 然后int应为0;在最后一个创建之前为1的那个应该是1,依此类推。

Hope this helps..

希望这可以帮助..

#1


2  

If the object implements IDisposable, then use using. If you need to create a new SqlCommand for some reason then finish one using block and start a new one. Or nest them if you still need access to the first SqlCommand.
You can reuse SqlCommand objects, as long as you haven't got a datareader still open from the command. So, you could create a SqlCommand, set all its properties and execute it, then reset all its properties and execute it again and so on. This does save slightly on the costs of memory allocation, but I think it also reduces the clarity of the code, so it is something I would only do if profiling proved it necessary.

如果对象实现了IDisposable,则使用using。如果由于某种原因需要创建一个新的SqlCommand,那么使用block完成一个并启动一个新的。如果您仍然需要访问第一个SqlCommand,请嵌套它们。您可以重用SqlCommand对象,只要您还没有从命令中打开一个datareader。因此,您可以创建一个SqlCommand,设置其所有属性并执行它,然后重置其所有属性并再次执行它,依此类推。这确实略微节省了内存分配的成本,但我认为它也降低了代码的清晰度,所以只有在分析证明它是必要的时候才会这样做。

#2


1  

will using recursively dispose of them through the connection object

将通过连接对象递归处理它们

Of course, using knows nothing about SqlConnections or other ADO.NET objects. All it knows is to call Dispose. Whatever the object being disposed does is what happens.

当然,使用对SqlConnections或其他ADO.NET对象一无所知。它只知道叫Dispose。无论处置的对象是什么,都会发生什么。

It happens to be the case that disposing a SqlConnection also disposes of the resources of all readers and commands associated with the connection. You only need to dispose the connection.

碰巧是,处理SqlConnection还会处理与连接相关的所有读取器和命令的资源。您只需要处理连接。

I don't know whether this is documented on MSDN but I know it from decompiling the assemblies. For compatibility reasons they can never change this behavior so it is safe to rely on it.

我不知道这是否在MSDN上有记录,但我知道它是通过反编译程序集来实现的。出于兼容性原因,他们永远不会改变这种行为,因此依赖它是安全的。

In general, you must call dispose on any object implementing IDisposable. Exception to this rule is when you know for sure that it is safe to not call Dispose. Like in this case.

通常,您必须在实现IDisposable的任何对象上调用dispose。此规则的例外情况是,当您确定不调用Dispose时是安全的。就像在这种情况下。

#3


1  

I think I found the answer!

我想我找到了答案!

Even though the sqlCommand object was still accessible in the bottom parts of the code when a nested using statement was skipped by a thrown exception, sqlCommand is still disposed later on. I tested this by actually assigning a function to the disposed event of said sqlCommand object.

即使抛出的异常跳过了嵌套的using语句,仍然可以在代码的底部访问sqlCommand对象,sqlCommand仍会在稍后处理。我通过实际为所述sqlCommand对象的处理事件分配一个函数来测试它。

The code here is slightly different than above because of the transaction object requirement. But the logic is essentially the same.

由于事务对象的要求,此处的代码与上面的代码略有不同。但逻辑基本相同。

try
{
       using(sqlCommand = new SqlCommand("GetUserByUserIdPassword2", sqlConnection, sqlTransaction))
       {
              sqlCommand.CommandTimeout = 15;
              sqlCommand.CommandType = CommandType.StoredProcedure;
              SqlParameter parameterUserId = sqlCommand.Parameters.Add("@UserId", SqlDbType.NVarChar, 32);
              parameterUserId.Value = userId;
              SqlParameter parameterPassword = sqlCommand.Parameters.Add("@Password", SqlDbType.NChar, 64);
              parameterPassword.Value = this.GetSHA256Hash(password);

              sqlCommand.Disposed += new System.EventHandler(this.sqlCommand_Disposed);

              SqlDataReader sqlDataReader = sqlCommand.ExecuteReader(CommandBehavior.SingleRow);
              // exception thrown, no sored proc "GetUserByUserIdPassword2"
              sqlDataReader.Close();
       }
}
catch(...) {}

...

private void sqlCommand_Disposed(object sender, EventArgs e)
{
     MessageBox.Show("sqlCommand has been disposed");
}

So, basically, even if a nested using statement is "aborted" by a thrown exception within the try block, and execution is skipped down to the catch block, the Dispose method is still called for that object after the function exits (or when the variable goes out of scope).

因此,基本上,即使嵌套的using语句被try块中的抛出异常“中止”,并且执行被跳过到catch块,在函数退出之后(或者当变量超出范围)。

I presume this behavior is the same for any number of nested using statements.

我认为这种行为对于任何数量的嵌套使用语句都是相同的。

#4


-1  

in CSharp, using is a special keyword which have different acts in code..

在CSharp中,using是一个特殊的关键字,在代码中有不同的行为。

if you put using to top of the codepage then using acts as c++ ' s Include or VB (and some other script langs') Import

如果你把use放到代码页的顶部然后使用act作为c ++的Include或VB(和其他一些脚本langs')导入

if you write a "using" which follows by a paranthesis then it acts like an alias of Dispose() method of CSharp.. or Destructor tilda of c++ or free() method of plain c..

如果你写一个“使用”后跟一个paranthesis那么它就像CSharp的Dispose()方法或c ++的destructor tilda或普通c的free()方法的别名。

About SQL Connection.. Best way is first Close() the connection (so with this, connection is still alive -actually just ready to use- but null.. return to wait new order in connection pool.. ) and then dispose if you need..

关于SQL连接..最好的方法是先关闭()连接(所以这个,连接仍然活着 - 实际上只是准备使用 - 但是null ..返回等待连接池中的新订单..)然后处理如果你需要..

if you have still problems and suspected that calling the Dispose() method not enough to free the resources

如果你仍然有问题,并怀疑调用Dispose()方法不足以释放资源

then you can use Garbage Collector's Collect() static method to force the garbage collection But not so much recommended by Microsoft because of face with accidental garbage collection problems (i.e. you collect the form but need some variable from that form )

然后你可以使用垃圾收集器的Collect()静态方法强制垃圾收集但是由于面对意外的垃圾收集问题而没有那么多推荐(即你收集表单但需要从该表单中获得一些变量)

in example of usage :

在使用示例中:

public void Dispose (bool ForceToCollect )
{
   if (ForceToCollect) 
               GC.Collect (0, GCCollectionMode.Forced );
}

In Gc.Collect() method you can set which group of data memory freed with first option where is int..if you plan to free resources of the last group - means last created group of items, as an example last created form instance and its all child controls - then int should be 0; the one which is 1 before the last created then should be 1 and so on..

在Gc.Collect()方法中,您可以设置使用第一个选项释放哪个数据内存组,其中int为int ..如果您计划释放最后一个组的资源 - 表示最后创建的项目组,作为示例最后创建的表单实例和它的所有子控件 - 然后int应为0;在最后一个创建之前为1的那个应该是1,依此类推。

Hope this helps..

希望这可以帮助..

相关文章