这是一个最低限度可重现的示例:
namespace IterableDisposeTest
{
interface IInstance
{
int ID { get; }
}
class Instance : IInstance
{
private static int p_counter = 0;
public int ID { get; }
public Instance(MagicVariable _magicVariable)
{
ID = p_counter++;
Console.WriteLine(_magicVariable.MagicString);
}
}
interface IProducer
{
public IEnumerable<IInstance> GetNextInstance();
}
class Producer : IProducer
{
private MagicVariable p_magicVariable;
public Producer(MagicVariable _magicVariable)
{
p_magicVariable = _magicVariable;
}
public IEnumerable<IInstance> GetNextInstance()
{
for (int i = 0; i < 42; i++)
yield return new Instance(p_magicVariable);
}
}
class ProducerFactory: IDisposable
{
private bool disposedValue;
private MagicVariable p_magicVariable = new MagicVariable();
public IEnumerable<Producer> GetNextProducer()
{
while (true)
yield return new Producer(p_magicVariable);
}
protected virtual void Dispose(bool disposing)
{
if (!disposedValue)
{
Console.WriteLine("ProducerFactory Disposed");
if (disposing)
{
}
p_magicVariable.Dispose();
disposedValue = true;
}
}
~ProducerFactory()
{
Dispose(disposing: false);
}
public void Dispose()
{
Dispose(disposing: true);
GC.SuppressFinalize(this);
}
}
class MagicVariable: IDisposable
{
private string p_magicString = "I'm MAGIC!";
private bool p_disposedValue;
public string MagicString
{
get
{
ObjectDisposedException.ThrowIf(p_disposedValue, typeof(string));
return p_magicString;
}
}
protected virtual void Dispose(bool disposing)
{
if (!p_disposedValue)
{
Console.WriteLine("MagicVariable Disposed");
if (disposing)
{
}
p_disposedValue = true;
}
}
~MagicVariable()
{
Dispose(disposing: false);
}
public void Dispose()
{
Dispose(disposing: true);
GC.SuppressFinalize(this);
}
}
static class SuperProducer
{
public static IEnumerable<IProducer> GetNextProducer()
{
using ProducerFactory factory = new ProducerFactory();
foreach(var producer in factory.GetNextProducer())
yield return producer;
}
}
internal class Program
{
static void Main(string[] args)
{
IProducer? producer = SuperProducer.GetNextProducer().FirstOrDefault((IProducer?)null);
if (producer != null)
{
// Thrown ObjectDisposedException there:
foreach (IInstance instance in producer.GetNextInstance())
Console.WriteLine($"Instance id is: {instance.ID}");
}
}
}
}
它会导致异常ObjectDisposedException,因为工厂在第 124 行退出语句时被释放。using困境如下 - 只有当语句中没有创建工厂时,错误才会消失using,但这与 RAII 概念相矛盾,工厂不会被创建。释放。如果你在 - 中创建一个工厂,那么它会在从第 126 行的循环using返回之前被释放。请告诉我这个示例中的架构错误是什么?IProducer你能怎样解决它?
你实际上期望什么?你在工厂(ProducerFactory)和实例(Instance)之间共享一个资源(MagicVariable),而资源的生命周期由工厂控制,工厂在实例之前销毁。没有必要这样做。
每个资源都有一个范围和生命周期,好的代码应该与这些相匹配。换句话说,您无法将资源转移到其链接比资源本身寿命更长的位置。是的,在有垃圾收集的语言中,这条规则不像没有垃圾收集的语言那么严格,但损坏的程序仍然是损坏的程序,尽管是安全损坏的程序。
让我们看一个简单的例子:
看起来很明显你不应该这样写?那么为什么您认为通过将 tmp 变量包装在 ProducerFactory 中并将 res 变量包装在 Instance 和 Producer 中可以避免此异常?
那么,工厂和资源该怎么办呢?而且只有三个选择:
如果工厂拥有由所有生成的对象共享的资源,则意味着当这些相同的对象处于活动状态时无法释放该工厂。
你的超级制片人打破了这条规则。工厂必须在更高的级别上创建:
如果这个选项完全不能令人满意,则意味着工厂不应该拥有该资源。为每个生成的对象创建单独的资源。
如果这个选项又不合适,并且资源不得不被共享,那么就该计算链接了。计算有多少个对象拥有某个资源,只有当该数量为零时才释放它。