在 Linq 中创建的 IDisposable - 异常安全
IDisposable created inside Linq - Exception Safety
我有类似于以下设置的内容:
class DisposableContainer : IDisposable
{
IEnumerable<DisposableObject> items;
//Potential problem method
public void Populate(IEnumerable<OtherThings> things)
{
items = things.Select(thing => new DisposableObject(thing));
}
//IDisposable Implementation
private bool disposed = false;
~DisposableContainer()
{
Dispose(false);
}
public override void Dispose()
{
Dispose(true);
GC.SuppressFinalize(this);
}
private void Dispose(bool disposing)
{
if (!disposed)
{
if (disposing)
{
if (items != null)
{
items.Select(item => item.Dispose());
}
}
disposed = true;
}
}
}
假设 DisposableObject
正确实现了 IDisposable
,并且有一个构造函数接受类型为 OtherThing
的参数。
假设在 Populate
方法中,如果 select 调用的第 3 次迭代抛出异常,那么成功创建的前两个 DisposableObject
会发生什么情况?我假设它们已经泄露并且未被处置。
跟进问题:有没有办法安全地处理上述情况,同时仍然使用 Linq 生成 IEnumerable<>
?我无法预见一个,但我想抛出这个想法,看看是否有其他人有想法。
你的假设是正确的。已创建的 DisposableObject
将无法正确处理。
您可以通过使用包含已创建项目的临时列表来修复它,并在需要时处理它们:
public void Populate(IEnumerable<OtherThings> things)
{
var temp = new List<DisposableObject>();
try
{
temp.AddRange(things.Select(otherThing => new DisposableObject(otherThing)));
items = temp;
}
catch
{
foreach (var disposableObject in temp)
{
disposableObject.Dispose();
}
throw;
}
}
这不能通过使用现有的 Linq 扩展方法来完成,但是您可以(不建议这样做)添加您的扩展方法拥有:
public static IReadOnlyCollection<TDisposable> SelectDisposables<TItem, TDisposable>(
this IEnumerable<TItem> enumerable,
Func<TItem,TDisposable> selector)
where TItem : IDisposable
where TDisposable : IDisposable
{
var temp = new List<TDisposable>();
try
{
temp.AddRange(enumerable.Select(selector));
return temp;
}
catch
{
foreach (var disposable in temp)
{
disposable.Dispose();
}
throw;
}
}
可以这样使用:
items = things.SelectDisposables(thing => new DisposableObject(thing));
我有类似于以下设置的内容:
class DisposableContainer : IDisposable
{
IEnumerable<DisposableObject> items;
//Potential problem method
public void Populate(IEnumerable<OtherThings> things)
{
items = things.Select(thing => new DisposableObject(thing));
}
//IDisposable Implementation
private bool disposed = false;
~DisposableContainer()
{
Dispose(false);
}
public override void Dispose()
{
Dispose(true);
GC.SuppressFinalize(this);
}
private void Dispose(bool disposing)
{
if (!disposed)
{
if (disposing)
{
if (items != null)
{
items.Select(item => item.Dispose());
}
}
disposed = true;
}
}
}
假设 DisposableObject
正确实现了 IDisposable
,并且有一个构造函数接受类型为 OtherThing
的参数。
假设在 Populate
方法中,如果 select 调用的第 3 次迭代抛出异常,那么成功创建的前两个 DisposableObject
会发生什么情况?我假设它们已经泄露并且未被处置。
跟进问题:有没有办法安全地处理上述情况,同时仍然使用 Linq 生成 IEnumerable<>
?我无法预见一个,但我想抛出这个想法,看看是否有其他人有想法。
你的假设是正确的。已创建的 DisposableObject
将无法正确处理。
您可以通过使用包含已创建项目的临时列表来修复它,并在需要时处理它们:
public void Populate(IEnumerable<OtherThings> things)
{
var temp = new List<DisposableObject>();
try
{
temp.AddRange(things.Select(otherThing => new DisposableObject(otherThing)));
items = temp;
}
catch
{
foreach (var disposableObject in temp)
{
disposableObject.Dispose();
}
throw;
}
}
这不能通过使用现有的 Linq 扩展方法来完成,但是您可以(不建议这样做)添加您的扩展方法拥有:
public static IReadOnlyCollection<TDisposable> SelectDisposables<TItem, TDisposable>(
this IEnumerable<TItem> enumerable,
Func<TItem,TDisposable> selector)
where TItem : IDisposable
where TDisposable : IDisposable
{
var temp = new List<TDisposable>();
try
{
temp.AddRange(enumerable.Select(selector));
return temp;
}
catch
{
foreach (var disposable in temp)
{
disposable.Dispose();
}
throw;
}
}
可以这样使用:
items = things.SelectDisposables(thing => new DisposableObject(thing));