从美学的角度出发,不要被观点所困扰,而是将注意力集中在以下内容的技术含义上。
我非常喜欢将表达式合并方法用于简单方法和属性以及方法链接。 IMO,它们看起来不错,干净,没有花括号的多余声音(是的,我也使用F#)。
但是,对于包含using语句的方法,您不能使用表达式合并方法。如果不关注语法的具体细节,则技术含义是做以下类似的事情:
class ...
{
HttpClient client;
public async Task SaveSomething(X value) =>
Using
.Disposable(await client.PostAsync("..", value))
.Act(response => response.EnsureSuccessStatusCode());
public async Task<X> GetSomething() =>
await Using
.Disposable(await client.GetAsync(".."))
.Act(response => response.ReadAsAsync<X>());
}
静态包装器实现:
static class Using
{
public static DisposableAct<TDisposable> Disposable<TDisposable>(TDisposable disposable)
where TDisposable : IDisposable => new DisposableAct<TDisposable>(disposable);
}
class DisposableAct<TDisposable> where TDisposable : IDisposable
{
private readonly TDisposable disposable;
public DisposableAct(TDisposable disposable) => this.disposable = disposable;
public TResult Act<TResult>(Func<TDisposable, TResult> act)
{
using (disposable)
{
return act(disposable);
}
}
}
相对于:
class ...
{
HttpClient client;
public async Task SaveSomething(X value)
{
using (var response = await client.PostAsync("..", value))
{
response.EnsureSuccessStatusCode();
}
}
public Task<X> GetSomething()
{
using (var response = await client.PostAsync("..", value))
{
return await response.ReadAsAsync<X>();
}
}
}
答案 0 :(得分:2)
包装器和尝试复制的代码之间有很多区别。
您将在async
方法返回时立即处理可处置资源,而不是在异步操作完成之后。在异步操作完成之后,您尝试复制的代码不会释放资源。如果您在等待任何东西后仍使用一次性资源,则该资源将被处置。
如果在调用Act
之前发生异常,则可使用资源被泄漏。没有这样的机会在其他代码中泄漏一次性资源。
如果您多次致电Act
,则一次性资源将已经被处置。
如果您从不在包装器上调用Act
,则将永远不会处理一次性资源。这基本上是#2,但是如果代码的作者做错了。
您正在创建多个其他对象,从而增加了内存压力。既有一次性包装纸,也有其他async
方法,这意味着有其他状态机。
#1是可以在实现中修复的东西,而无需更改调用方的使用方式,其他则是您设计此方式的固有内容,而解决这些问题则需要更改调用方公平使用操作的方式进行重大修复。
就解决所有这些问题而言,第一件事就是完全删除包装对象。它为程序员提供了很多机会来做错误的事情(#3,#4,以及其他一些我没说过的错误),并且是#5的重要组成部分。取而代之的是让静态方法接受两个参数,一个可处理资源和一个对其采取的行动。这样,您便可以进行控制,以确保正确地始终(或至少接近始终)。
public static TResult UseDisposable<TDisposable, TResult>(TDisposable disposable, Func<TDisposable, TResult> function)
where TDisposable : IDisposable
{
using (disposable)
{
return function(disposable);
}
}
接下来,如果要支持异步方法,则需要为此专门指定一个重载 ,在该重载中您将意识到这是一个异步方法并进行相应的处理。幸运的是await
使编写起来很容易。 (请注意,从技术上讲,这里使用async
意味着我们正在创建一个状态机,如果我们手动进行操作,我们可以从技术上避免这种情况。如果您想避免与原始机种的差异,则要么需要做整个手工操作(如果您想确保所有正确的错误处理和取消行为都正确完成,这将非常困难)。如果您可以忍受添加的分配,那么代码并不会比代码复杂得多您正在尝试复制。
public static async Task<TResult> UseDisposable<TDisposable, TResult>(TDisposable disposable, Func<TDisposable, Task<TResult>> function)
where TDisposable : IDisposable
{
using (disposable)
{
return await function(disposable);
}
}
然后,对于非结果返回操作,您当然需要这两个重载的版本:
public static void UseDisposable<TDisposable>(TDisposable disposable, Action<TDisposable> action)
where TDisposable : IDisposable
{
using (disposable)
{
action(disposable);
}
}
public static async Task UseDisposable<TDisposable, TResult>(TDisposable disposable, Func<TDisposable, Task> action)
where TDisposable : IDisposable
{
using (disposable)
{
await action(disposable);
}
}
在这一点上值得注意的是,上述重载并没有完全解决可处置资源进入using
之前发生异常的可能性。上面的版本缩小了代码的可能性范围,尤其是消除了许多可能的误用,但并不能完全消除它。如果您对此有所担心,则可以更进一步,而不是接受一次性资源,可以接受生成这种方法。这完全消除了这种可能性。幸运的是,所有这些重载都可以并排放置,因此您可以拥有所有这些重载,然后仅在担心构造函数与使用开始之间的任何地方发生可能的异常时才使用一次性生成器版本。 / p>
public static TResult UseDisposable<TDisposable, TResult>(Func<TDisposable> disposableGenerator, Func<TDisposable, TResult> function)
where TDisposable : IDisposable
{
using (var disposable = disposableGenerator())
{
return function(disposable);
}
}
public static async Task<TResult> UseDisposable<TDisposable, TResult>(Func<TDisposable> disposableGenerator, Func<TDisposable, Task<TResult>> function)
where TDisposable : IDisposable
{
using (var disposable = disposableGenerator())
{
return await function(disposable);
}
}
public static void UseDisposable<TDisposable>(Func<TDisposable> disposableGenerator, Action<TDisposable> action)
where TDisposable : IDisposable
{
using (var disposable = disposableGenerator())
{
action(disposable);
}
}
public static async Task UseDisposable<TDisposable, TResult>(Func<TDisposable> disposableGenerator, Func<TDisposable, Task> action)
where TDisposable : IDisposable
{
using (var disposable = disposableGenerator())
{
await action(disposable);
}
}
如果您在后面的四个方法中都使用了它们,那么您可能还想制作原始的4个重载扩展方法,并且发现您使用它们的程度足以使其有意义。