我正在设计一个流畅的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;
}
代码似乎编译得很好,但我正在寻找可能因此而出现的奇怪行为或错误。是不是很糟糕的做法?
答案 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 强>
P.S。 using语句和整个IDisposable功能是为了解决这种情况而建立的,无论在什么情况下都会强迫好的devs处理。
<强>场景#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,要么是矫枉过正,也不是最容易使用的。