从同一对象处置对象

Disposing object from same object

我正在设计一个流利的API,用法有点像这样:

IUser user = work
                 .Timeout(TimeSpan.FromSeconds(5))
                 .WithRepository(c => c.Users)
                 .Do(r => r.LoadByUsername("matt"))
                 .Execute();

所以,假设 workIUnitOfWork 类型,但是方法 WithRepository(c => c.Users) returns 一个名为 IActionFlow<IUserRepository> 的接口是 IDisposable.

当我调用 Execute() 并获得最终结果时,我丢失了对该 IActionFlow<IUserRepository> 实例的引用,因此我无法处理它。

让实例将自身置于 Execute() 方法上有什么缺点?

类似于:

public TResult Execute()
{
    // ...
    Dispose();
    return result;
}

代码似乎编译得很好,但我正在寻找可能因此而出现的奇怪行为或错误。这是不好的做法吗?

选项 1:

你能否将你的代码包装在 using 块中,以便自动调用 dispose,

using(var repository = work.Timeout(TimeSpan.FromSeconds(5)).WithRepository(c => c.Users))
{
   IUser user = repository
                 .Do(r => r.LoadByUsername("matt"))
                 .Execute();

}

因此,您的 Execute 方法不需要调用 Dispose()

public TResult Execute()
{
    // ...
    //Dispose();
    return result;
}

选项 2:

您可以将结果分配到 属性 和 return 存储库对象中,您可以使用它显式调用 Dispose 方法。

像这样(可以进一步重构),

using(var repository = work
                 .Timeout(TimeSpan.FromSeconds(5))
                 .WithRepository(c => c.Users)
                 .Do(r => r.LoadByUsername("matt"))
                 .Execute())
{

   IUser user = repository.Result;
   //repository.Dispose();
}

//******

public TResult Result { get; set; }

public IActionFlow<IUserRepository> Execute()
{
    // ...
    //Dispose();
    this.Result = result;
    return this;
}

注意:调用Dispose()方法后,根据可用的服务器资源,垃圾回收可能会在任何阶段发生。所以在 Execute 方法中调用它可能会产生意想不到的奇怪问题。

一次性对象必须从客户端代码中处理,您将从您所做的事情中看到的副作用之一是,当您的消费者使用您的 API 他们会得到代码分析错误(如果他们使用它,很多人都这样做)比如:"CA2213: Disposable fields should be disposed" 请参阅 https://msdn.microsoft.com/en-us/library/ms182328.aspx 了解有关该错误的详细信息。

综上所述,您不应该在 "Execute" 方法中处理绝对必要的东西,请考虑以下情况:

场景#1

  1. 你有一些东西,比如你必须处理的打开的连接, 连接以 Execute 以外的其他方法打开。
  2. 在 "Do" 方法执行期间发生错误,从未调用 dispose,因为从未调用过 execute 并且打开的连接保持打开状态。请注意,即使 Do 在您的机器上从不失败,也不能保证它不会因另一个线程在机器上发生的随机事件而在生产中失败。

P.S。 using 语句和整个 IDisposable 功能都是为解决这种情况而构建的,无论情况如何,都迫使优秀的开发人员进行处置。

场景#2

  1. execute 方法做了一些值得一次性使用的事情,例如打开一个连接然后它会做一些事情。

在这种情况下,您不需要 dispose 方法,只需将连接包装在 using 语句中即可在 execute 方法中处理连接,例如:

public void Execute(object whatever){
    using (var conn = new Connection()){
        //method body
    }
}

如果你有场景#1,你必须让客户端使用 using 语句,并且必须承担它的不流畅性(析构函数不会削减它),如果你有场景#2,你就没有'根本不需要处理。

你可以有这样的Using方法:

public static TResult Using<TDisposable, TResult>(Func<TDisposable> factory,
    Func<TDisposable, TResult> fn) where TDisposable : IDisposable {
    using (var disposable = factory()) {
        return fn(disposable);
    }
}

那么您的代码将如下所示:

var user = Using(() => work.
    Timeout(TimeSpan.FromSeconds(5)).
    WithRepository(c => c.Users),
    repository => repository.Do(r => r.LoadByUsername("matt")).
        Execute());

这将使您的 API 保持流畅,同时您将在 Execute 完成的同一时刻处理 WithRepository

在我看来,您正在尝试处理存储库。

如果您想保持 API 完好无损,我建议您这样做:

using(var repo = work.Timeout(TimeSpan.FromSeconds(5))
      .WithRepository(c => c.Users))
{
    IUser user = repo.Do(r => r.LoadByUsername("matt")).Execute();
    //Do whatever with user here if lazy loading.
}

我知道这会将您的流利 API 一分为二,但您可以按这种方式使用它而无需进行任何更改。

另一种选择是为结果创建一个包装器 class,该包装器维护对存储库的引用(例如 ActionFlowExecution)并实现 IDisposable:

public class ActionFlowExecution<TResult, TRepository> : IDisposable
{
    private TRepository _repository;
    internal ActionFlowExecution(TResult result, TRepository repository)
    {
        Result = result;
        _repository = repository;
    }

    public TResult Result { get; private set; }

    public void Dispose()
    {
        if(_repository != null)
        {
            _repository.Dispose();
            _repository = null;
        }
    }
}

然后您可以这样调用您的 API:

using(var execution = work
             .Timeout(TimeSpan.FromSeconds(5))
             .WithRepository(c => c.Users)
             .Do(r => r.LoadByUsername("matt"))
             .Execute())
{
    IUser user = execution.Result;
    //Do whatever with user here 
}

我同意其他人的看法,即从内部处理对象是个坏主意。 我用了五分钟写完这篇文章,所以可能会有一些错别字,但我想你会明白大概的意思。

只要以后不使用该对象,您就可以安全地执行您提到的操作。这里唯一但非常重要的是命名。我会调用方法 ExecuteAndDispose。其他选项要么不流畅 API,要么矫枉过正,也不容易使用。