处置来自同一对象

时间:2016-10-16 06:48:57

标签: c# dispose fluent

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

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

因此,我们假设work的类型为IUnitOfWork,但方法WithRepository(c => c.Users)会返回名为IActionFlow<IUserRepository>的{​​{1}}接口,IDisposable

当我致电Execute()并获得最终结果时,我将失去对该IActionFlow<IUserRepository>个实例的引用,因此我无法处置它。

将实例自身放在Execute()方法上有什么缺点?

类似的东西:

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

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

5 个答案:

答案 0 :(得分:5)

您可以使用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保持流利,同时在WithRepository完成的同一时间内处理Execute

答案 1 :(得分:3)

选项1:

您可以将代码包装在if块中,以便自动调用dispose,

using

因此,您的using(var repository = work.Timeout(TimeSpan.FromSeconds(5)).WithRepository(c => c.Users)) { IUser user = repository .Do(r => r.LoadByUsername("matt")) .Execute(); } 方法无需调用Execute

Dispose()

选项2:

您可以在属性中分配结果并返回存储库对象,您可以使用该对象显式调用public TResult Execute() { // ... //Dispose(); return result; } 方法。

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

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()方法中调用此方法可能会产生意想不到的奇怪问题。

答案 2 :(得分:0)

必须从客户端代码处理一次性对象,您将从中看到的一个副作用是,当您的消费者使用您的API时,他们将获得代码分析错误(如果他们使用它,很多人会这样做)喜欢:&#34; CA2213:应处理一次性字段&#34;有关该错误的详细信息,请参阅https://msdn.microsoft.com/en-us/library/ms182328.aspx

说了上面的话,你不应该处理绝对必要的东西来处理&#34;执行&#34;方法,采取以下场景:

<强>场景#1

  1. 你有必须处理的开放式连接, 连接以除执行之外的其他方法打开。
  2. 在&#34; Do&#34;期间发生错误方法执行时,dispose永远不会被调用,因为执行永远不会被调用,并且open连接保持打开状态。请注意,即使您的机器上的Do永不失败也不保证它不会因为从另一个线程发生在机器上的随机事件而无法生产。
  3. P.S。 using语句和整个IDisposable功能是为了解决这种情况而建立的,无论在什么情况下都会强迫好的devs处理。

    <强>场景#2

    1. 执行方法可以做一些有价值的东西,例如:打开一个连接,然后就可以了。
    2. 在这种情况下,您不需要dispose方法,您可以通过将连接包装在using语句中来简单地在execute方法中处理连接:

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

      如果你有场景#1,你必须让客户使用using语句并且必须承担它的不流畅性(析构者不会削减它),如果你有场景#2你不要&# 39;根本不需要处置。

答案 3 :(得分:0)

在我看来,你正试图处理存储库。

如果您希望保持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分解为两种,但你可以这样使用它而不需要改变任何东西。

另一种选择是为结果创建一个包装类,以维护对存储库的引用(例如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 
}

我同意其他人的说法,让对象从内部处理是个坏主意。 我在五分钟内写了这个,所以可能会有一些错别字,但我认为你会得到一般的想法。

答案 4 :(得分:0)

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