IDisposable 项目的迭代器函数
Iterator function for IDisposable items
假设我想创建一个生成 IDisposable
项的迭代器函数。
IEnumerable<Disposable> GetItems()
{
yield return new Disposable();
yield return new Disposable();
}
这对于客户端代码来说似乎并不理想:
foreach (var item in source.GetItems())
{
using (item)
{
// ...
}
}
直觉上,using
来得太晚了。事情可能会四处移动。可能会不小心在 foreach
和 using
之间插入代码 。不理想。
我正在寻找更好的选择!
想到的一种方法是创建以下 API 而不是迭代器函数:
// Client
while (source.HasItem)
{
using (var item = source.GetNextItem())
{
// ...
}
}
// Source
private IEnumerator<Disposable> Enumerator { get; }
private bool? _hasItem;
bool HasItem
{
get
{
if (this._hasItem == null) this._hasItem = this.Enumerator.MoveNext();
return this._hasItem;
}
}
Disposable GetNextItem()
{
if (!this.HasItem) throw new IndexOutOfBoundsException();
this._hasItem = null;
return this.Enumerator.Current;
}
不过现在看来源码要变成IDisposable
了!不然它怎么知道什么时候释放 Enumerator
?这可能是一个令人不快的副作用。
我正在寻找一种在客户中感觉可靠的替代方案,但它可以防止来源也变成 IDisposable
。
编辑 - 澄清:我忘了提到我们需要的一些内容来自迭代器。具体来说,假设我们要返回 IDbCommand
个实例,即 IDisposable
。在返回每个命令之前,我们需要用一些查询数据填充它,而这些查询数据又来自一个简单的迭代器方法。
如果我没理解错的话,下面的模式适合你
foreach (var item in source.GetItems())
{
using (item)
{
// ...
}
}
但潜在的问题是将一些代码放在 using
块之外。那么,为什么不将该逻辑包装在自定义扩展方法中:
public static class EnumerableExtennisons
{
public static IEnumerable<T> WithUsing<T>(this IEnumerable<T> source)
where T : IDisposable
{
foreach (var item in source)
{
using (item)
yield return item;
}
}
}
通过这种方式,您可以确保在将项目返回给调用者之前*将项目包装在 using
块中,因此调用者无法插入代码 before/after。 C# 编译器生成的代码确保 item.Dispose
在 IEnumerator<T>
的 MoveNext
或 Dispose
方法中被调用(以防迭代提前结束)。
用法是在需要的地方附加 .WithUsing()
调用而不是 using
块:
foreach (var item in source.GetItems().WithUsing())
{
// ...
}
迭代器函数本身可以负责放置一个 using(即在请求下一个项目时处理当前项目)。
IEnumerable<Disposable> GetItems()
{
// Assumming CreateItems() is an iterator as well
foreach (var item in this.CreateItems())
using (item) yield return item;
}
因此,我们基本上将拥有一个仅流式处理的迭代器。 ToList()
这样的方法变得毫无意义,因为一次只能使用一个项目。
不幸的是,这确实为错误创造了空间。
我们只能暴露一个IEnumerator<T>
,它只支持MoveNext()
和Current
。
现在,底层的 private 迭代器函数可以流式传输,负责处理项目。不会向客户端引入无效操作 - 除非他们尝试存储借用的对象并稍后尝试使用它们,此时很明显对象已经被处置。
// Client
using (var itemEnumerator = source.GetItemEnumerator())
{
while (itemEnumerator.MoveNext())
var current = itemEnumerator.Current;
}
// Source
IEnumerator<Disposable> GetItemEnumerator() => this.StreamItems().GetEnumerator();
private IEnumerable<Disposable> StreamItems()
{
while (this.ShouldCreateItem())
{
using (var item = this.CreateItem())
{
yield return item;
}
}
}
假设我想创建一个生成 IDisposable
项的迭代器函数。
IEnumerable<Disposable> GetItems()
{
yield return new Disposable();
yield return new Disposable();
}
这对于客户端代码来说似乎并不理想:
foreach (var item in source.GetItems())
{
using (item)
{
// ...
}
}
直觉上,using
来得太晚了。事情可能会四处移动。可能会不小心在 foreach
和 using
之间插入代码 。不理想。
我正在寻找更好的选择!
想到的一种方法是创建以下 API 而不是迭代器函数:
// Client
while (source.HasItem)
{
using (var item = source.GetNextItem())
{
// ...
}
}
// Source
private IEnumerator<Disposable> Enumerator { get; }
private bool? _hasItem;
bool HasItem
{
get
{
if (this._hasItem == null) this._hasItem = this.Enumerator.MoveNext();
return this._hasItem;
}
}
Disposable GetNextItem()
{
if (!this.HasItem) throw new IndexOutOfBoundsException();
this._hasItem = null;
return this.Enumerator.Current;
}
不过现在看来源码要变成IDisposable
了!不然它怎么知道什么时候释放 Enumerator
?这可能是一个令人不快的副作用。
我正在寻找一种在客户中感觉可靠的替代方案,但它可以防止来源也变成 IDisposable
。
编辑 - 澄清:我忘了提到我们需要的一些内容来自迭代器。具体来说,假设我们要返回 IDbCommand
个实例,即 IDisposable
。在返回每个命令之前,我们需要用一些查询数据填充它,而这些查询数据又来自一个简单的迭代器方法。
如果我没理解错的话,下面的模式适合你
foreach (var item in source.GetItems())
{
using (item)
{
// ...
}
}
但潜在的问题是将一些代码放在 using
块之外。那么,为什么不将该逻辑包装在自定义扩展方法中:
public static class EnumerableExtennisons
{
public static IEnumerable<T> WithUsing<T>(this IEnumerable<T> source)
where T : IDisposable
{
foreach (var item in source)
{
using (item)
yield return item;
}
}
}
通过这种方式,您可以确保在将项目返回给调用者之前*将项目包装在 using
块中,因此调用者无法插入代码 before/after。 C# 编译器生成的代码确保 item.Dispose
在 IEnumerator<T>
的 MoveNext
或 Dispose
方法中被调用(以防迭代提前结束)。
用法是在需要的地方附加 .WithUsing()
调用而不是 using
块:
foreach (var item in source.GetItems().WithUsing())
{
// ...
}
迭代器函数本身可以负责放置一个 using(即在请求下一个项目时处理当前项目)。
IEnumerable<Disposable> GetItems()
{
// Assumming CreateItems() is an iterator as well
foreach (var item in this.CreateItems())
using (item) yield return item;
}
因此,我们基本上将拥有一个仅流式处理的迭代器。 ToList()
这样的方法变得毫无意义,因为一次只能使用一个项目。
不幸的是,这确实为错误创造了空间。
我们只能暴露一个IEnumerator<T>
,它只支持MoveNext()
和Current
。
现在,底层的 private 迭代器函数可以流式传输,负责处理项目。不会向客户端引入无效操作 - 除非他们尝试存储借用的对象并稍后尝试使用它们,此时很明显对象已经被处置。
// Client
using (var itemEnumerator = source.GetItemEnumerator())
{
while (itemEnumerator.MoveNext())
var current = itemEnumerator.Current;
}
// Source
IEnumerator<Disposable> GetItemEnumerator() => this.StreamItems().GetEnumerator();
private IEnumerable<Disposable> StreamItems()
{
while (this.ShouldCreateItem())
{
using (var item = this.CreateItem())
{
yield return item;
}
}
}