处理比处理实例寿命更长的对象引用
Dispose of objects references that live longer than the disposing instance
我目前正在 NHibernate 之上开发一个工作单元和存储库模式(注意:我没有决定采用一种或另一种模式,因此请不要讨论存储库模式的用处关于已经实现了一个的 ORM)。
我首先根据文档构建一个创建工作单元实例的单例(使用 IoC 配置)Sessionmanager(为了便于阅读而删除了私有方法):
public sealed class SessionManager : ISessionManager
{
#region Members
/// <summary>
/// lock to manage thread safe access to the dictionary.
/// </summary>
private readonly ReaderWriterLockSlim _lock;
/// <summary>
/// NHibernate sessionfactory to create sessions.
/// </summary>
private readonly ISessionFactory _factory;
/// <summary>
/// Units of Work that are already in use.
/// </summary>
private readonly Dictionary<string, IUnitOfWork> _units;
/// <summary>
/// Flag indicating if the manager got disposed.
/// </summary>
private bool _disposed;
#endregion
#region Constructors
/// <summary>
/// Creates a new Manager with the given Config.
/// </summary>
/// <param name="config"></param>
public SessionManager(NHibernate.Cfg.Configuration config)
{
_lock = new ReaderWriterLockSlim();
_units = new Dictionary<string, IUnitOfWork>();
_factory = config
.Configure()
.AddAssembly(typeof(SessionManager).Assembly.FullName)
.BuildSessionFactory();
_disposed = false;
}
#endregion
#region Methods
/// <summary>
/// Either creates or returns an existing unit of work.
/// </summary>
/// <param name="identifier">identifier of the uow</param>
/// <param name="access">the data access that is needed for this uow.</param>
/// <returns></returns>
public IUnitOfWork Create(string identifier, DataAccess access)
{
Throw.IfNull(identifier, nameof(identifier));
if (TryGetUnitOfWork(identifier, out var unit))
return unit;
return CreateOrReturn(identifier, access);
}
/// <summary>
/// Disposes the given instance.
/// </summary>
/// <param name="unitOfWork">The unit of work that should get disposed.</param>
public void DisposeUnitOfWork(IUnitOfWork unitOfWork)
{
Throw.IfNull(unitOfWork, nameof(unitOfWork));
try
{
_lock.EnterWriteLock();
if (_units.ContainsValue(unitOfWork))
{
var key = _units.FirstOrDefault(x => x.Value.Equals(unitOfWork)).Key;
if (!string.IsNullOrWhiteSpace(key))
_units.Remove(key);
}
unitOfWork.Dispose();
}
finally
{
_lock.ExitWriteLock();
}
unitOfWork.Dispose();
}
/// <summary>
/// Disposes managed and unmanaged ressources.
/// </summary>
/// <param name="disposing">Flag indicating if the call happened in the public <see cref="Dispose"/> method or the Finalizer.</param>
void Dispose(bool disposing)
{
if (!_disposed)
{
if (disposing)
{
foreach (var unit in _units)
unit.Value.Dispose();
_factory.Dispose();
_lock.Dispose();
}
// TODO: free unmanaged resources (unmanaged objects) and override a finalizer below.
// TODO: set large fields to null.
_disposed = true;
}
}
/// <summary>
/// Disposes managed and unmanaged ressources.
/// </summary>
public void Dispose()
{
Dispose(true);
GC.SuppressFinalize(this);
}
#endregion
}
}
工作单元包含 ISession
或 IStatelessSession
以创建事务或所需的存储库。
public sealed class UnitOfWork : IUnitOfWork
{
#region Members
private bool _disposed;
public string Identifier { get; }
public DataAccess Access { get; }
private readonly ISession _session;
private readonly IStatelessSession _statelessSession;
#endregion
#region Constructors
private UnitOfWork(DataAccess access, string identifier)
{
Access = access;
Identifier = identifier;
_disposed = false;
}
internal UnitOfWork(DataAccess access, string identifier, ISession session)
: this(access, identifier)
{
_session = session;
}
internal UnitOfWork(DataAccess access, string identifier, IStatelessSession session)
: this(access, identifier)
{
_statelessSession = session;
}
#endregion
#region Methods
public void CloseTransaction(IGenericTransaction transaction)
{
transaction.Dispose();
}
public IGenericTransaction OpenTransaction()
{
if (_session != null)
return new GenericTransaction(_session.BeginTransaction());
if (_statelessSession != null)
return new GenericTransaction(_statelessSession.BeginTransaction());
throw new InvalidOperationException("You tried to create a transaction without having a vaild session.");
}
public IGenericTransaction OpenTransaction(IsolationLevel level)
{
if (_session != null)
return new GenericTransaction(_session.BeginTransaction(level), level);
if (_statelessSession != null)
return new GenericTransaction(_statelessSession.BeginTransaction(level), level);
throw new InvalidOperationException("You tried to create a transaction without having a vaild session.");
}
public bool Equals(IUnitOfWork other)
{
if (other == null)
return false;
return Access == other.Access;
}
public IRepository<T> GetRepository<T>() where T : Entity<T>
{
switch (Access)
{
case DataAccess.Statefull:
return new StatefullRepository<T>(_session);
case DataAccess.Stateless:
return new StatelessRepository<T>(_statelessSession);
default:
throw new ArgumentException("Cannot determine which repository is needed.", nameof(Access));
}
}
#region IDisposable Support
void Dispose(bool disposing)
{
if (!_disposed)
{
if (disposing)
{
if (_session != null)
_session.Dispose();
if (_statelessSession != null)
_statelessSession.Dispose();
}
// TODO: free unmanaged resources (unmanaged objects) and override a finalizer below.
// TODO: set large fields to null.
_disposed = true;
}
}
// TODO: override a finalizer only if Dispose(bool disposing) above has code to free unmanaged resources.
// ~UnitOfWork() {
// // Do not change this code. Put cleanup code in Dispose(bool disposing) above.
// Dispose(false);
// }
// This code added to correctly implement the disposable pattern.
public void Dispose()
{
// Do not change this code. Put cleanup code in Dispose(bool disposing) above.
Dispose(true);
// TODO: uncomment the following line if the finalizer is overridden above.
// GC.SuppressFinalize(this);
}
#endregion
#endregion
}
正如您所见,存储库是使用 ISession
或 IStatelessSession
创建的。这两个接口都实现了 IDisposable
接口,这意味着它们应该被处理掉。因此,我的存储库也实现了 IDisposable
。然而,问题就在这里。理论上,我可以从一个工作单元创建任意数量的存储库,例如:
public void UpdatePets(List<Cat> felines, List<Dog> carnines, ISessionManager manager)
{
var uow = manager.Create("Pets", DataAccess.Statefull);
using (var t = uow.OpenTransaction())
{
using (var catRepo = uow.GetRepository<Cat>())
{
catRepo.Add(felines);
t.Commit();
}
}
//Some Businesslogic that forbids using one Transaction to mitigate the problem of an already disposed ISession or IStatelessSession.
using(var t = uow.OpenTransaction())
{
using (var dogRepo = uow.GetRepository<Dog>())
{
dogRepo.Add(carnines);
t.Commit();
}
}
}
如果那是消耗我的工作单元的服务,我会像这样使用 "standard" IDisposable
模式:
private void Dispose(bool disposing)
{
if (!_disposed)
{
if (disposing)
{
if (_session != null)
_session.Dispose();
}
_disposed = true;
}
}
public void Dispose()
{
Dispose(true);
GC.SuppressFinalize(this);
}
第二个存储库会抛出 ObjectDisposedexception,因为我在第二个存储库中引用的 ISession 来自我的工作单元,而当我的第一个存储库离开 using 块时,那个已经被处理掉了。因此,为了克服这个问题,我会在我的存储库中执行以下操作:
public void Dispose()
{
_session = null;
GC.SuppressFinalize(this);
}
然而,这感觉不对,因为我没有正确处理对我应该处理的对象的引用,而是 "close my Eyes and forget about it",这可能永远不是编程中的好解决方案。
所以我的问题基本上是:"Is there a good way to properly dispose objects that might live longer than the object holding the reference?"
您需要建立所有权,并使用它来确定哪个 class 负责清理每个 "thing"。它是所有者,并且只有所有者应该调用 Dispose
,无论有多少其他 class 可能已经与它交互。
所有权需要 独占 并且(包装 classes 除外)嵌套1 - 所有者的生命周期需要至少与拥有的 object 一样长。通过遵循这两条规则,我们可以确保 Dispose a) 调用一次 2 b) 当不再需要 object 时调用。
存储库对 "own" session 来说是错误的。多个存储库可以被赋予相同的 session(因此,如果他们是所有者,我们就失去了独占 属性),正如您自己的标题所暗示的,他们有一个 比它短 生命周期。
一些一般经验法则
- 作为参数传递给实例方法的 object 通常不会被实例拥有。
- 由实例创建的 object 生命周期长于单个方法调用的生命周期的 object 将由实例拥有
- 由实例方法创建的 object 且不长于单个方法调用的生命周期的 object 将由方法拥有
1对于包装器,内部 object 往往会首先创建,可能已配置,然后传递给包装器 object。所以包装器 object 的生命周期在内部 object.
之后开始
2不严格要求。 Disposables 应该容忍 Dispose 被多次调用,但这更多的是 belt-and-braces 防御性而不是所需的模式。
我目前正在 NHibernate 之上开发一个工作单元和存储库模式(注意:我没有决定采用一种或另一种模式,因此请不要讨论存储库模式的用处关于已经实现了一个的 ORM)。 我首先根据文档构建一个创建工作单元实例的单例(使用 IoC 配置)Sessionmanager(为了便于阅读而删除了私有方法):
public sealed class SessionManager : ISessionManager
{
#region Members
/// <summary>
/// lock to manage thread safe access to the dictionary.
/// </summary>
private readonly ReaderWriterLockSlim _lock;
/// <summary>
/// NHibernate sessionfactory to create sessions.
/// </summary>
private readonly ISessionFactory _factory;
/// <summary>
/// Units of Work that are already in use.
/// </summary>
private readonly Dictionary<string, IUnitOfWork> _units;
/// <summary>
/// Flag indicating if the manager got disposed.
/// </summary>
private bool _disposed;
#endregion
#region Constructors
/// <summary>
/// Creates a new Manager with the given Config.
/// </summary>
/// <param name="config"></param>
public SessionManager(NHibernate.Cfg.Configuration config)
{
_lock = new ReaderWriterLockSlim();
_units = new Dictionary<string, IUnitOfWork>();
_factory = config
.Configure()
.AddAssembly(typeof(SessionManager).Assembly.FullName)
.BuildSessionFactory();
_disposed = false;
}
#endregion
#region Methods
/// <summary>
/// Either creates or returns an existing unit of work.
/// </summary>
/// <param name="identifier">identifier of the uow</param>
/// <param name="access">the data access that is needed for this uow.</param>
/// <returns></returns>
public IUnitOfWork Create(string identifier, DataAccess access)
{
Throw.IfNull(identifier, nameof(identifier));
if (TryGetUnitOfWork(identifier, out var unit))
return unit;
return CreateOrReturn(identifier, access);
}
/// <summary>
/// Disposes the given instance.
/// </summary>
/// <param name="unitOfWork">The unit of work that should get disposed.</param>
public void DisposeUnitOfWork(IUnitOfWork unitOfWork)
{
Throw.IfNull(unitOfWork, nameof(unitOfWork));
try
{
_lock.EnterWriteLock();
if (_units.ContainsValue(unitOfWork))
{
var key = _units.FirstOrDefault(x => x.Value.Equals(unitOfWork)).Key;
if (!string.IsNullOrWhiteSpace(key))
_units.Remove(key);
}
unitOfWork.Dispose();
}
finally
{
_lock.ExitWriteLock();
}
unitOfWork.Dispose();
}
/// <summary>
/// Disposes managed and unmanaged ressources.
/// </summary>
/// <param name="disposing">Flag indicating if the call happened in the public <see cref="Dispose"/> method or the Finalizer.</param>
void Dispose(bool disposing)
{
if (!_disposed)
{
if (disposing)
{
foreach (var unit in _units)
unit.Value.Dispose();
_factory.Dispose();
_lock.Dispose();
}
// TODO: free unmanaged resources (unmanaged objects) and override a finalizer below.
// TODO: set large fields to null.
_disposed = true;
}
}
/// <summary>
/// Disposes managed and unmanaged ressources.
/// </summary>
public void Dispose()
{
Dispose(true);
GC.SuppressFinalize(this);
}
#endregion
}
}
工作单元包含 ISession
或 IStatelessSession
以创建事务或所需的存储库。
public sealed class UnitOfWork : IUnitOfWork
{
#region Members
private bool _disposed;
public string Identifier { get; }
public DataAccess Access { get; }
private readonly ISession _session;
private readonly IStatelessSession _statelessSession;
#endregion
#region Constructors
private UnitOfWork(DataAccess access, string identifier)
{
Access = access;
Identifier = identifier;
_disposed = false;
}
internal UnitOfWork(DataAccess access, string identifier, ISession session)
: this(access, identifier)
{
_session = session;
}
internal UnitOfWork(DataAccess access, string identifier, IStatelessSession session)
: this(access, identifier)
{
_statelessSession = session;
}
#endregion
#region Methods
public void CloseTransaction(IGenericTransaction transaction)
{
transaction.Dispose();
}
public IGenericTransaction OpenTransaction()
{
if (_session != null)
return new GenericTransaction(_session.BeginTransaction());
if (_statelessSession != null)
return new GenericTransaction(_statelessSession.BeginTransaction());
throw new InvalidOperationException("You tried to create a transaction without having a vaild session.");
}
public IGenericTransaction OpenTransaction(IsolationLevel level)
{
if (_session != null)
return new GenericTransaction(_session.BeginTransaction(level), level);
if (_statelessSession != null)
return new GenericTransaction(_statelessSession.BeginTransaction(level), level);
throw new InvalidOperationException("You tried to create a transaction without having a vaild session.");
}
public bool Equals(IUnitOfWork other)
{
if (other == null)
return false;
return Access == other.Access;
}
public IRepository<T> GetRepository<T>() where T : Entity<T>
{
switch (Access)
{
case DataAccess.Statefull:
return new StatefullRepository<T>(_session);
case DataAccess.Stateless:
return new StatelessRepository<T>(_statelessSession);
default:
throw new ArgumentException("Cannot determine which repository is needed.", nameof(Access));
}
}
#region IDisposable Support
void Dispose(bool disposing)
{
if (!_disposed)
{
if (disposing)
{
if (_session != null)
_session.Dispose();
if (_statelessSession != null)
_statelessSession.Dispose();
}
// TODO: free unmanaged resources (unmanaged objects) and override a finalizer below.
// TODO: set large fields to null.
_disposed = true;
}
}
// TODO: override a finalizer only if Dispose(bool disposing) above has code to free unmanaged resources.
// ~UnitOfWork() {
// // Do not change this code. Put cleanup code in Dispose(bool disposing) above.
// Dispose(false);
// }
// This code added to correctly implement the disposable pattern.
public void Dispose()
{
// Do not change this code. Put cleanup code in Dispose(bool disposing) above.
Dispose(true);
// TODO: uncomment the following line if the finalizer is overridden above.
// GC.SuppressFinalize(this);
}
#endregion
#endregion
}
正如您所见,存储库是使用 ISession
或 IStatelessSession
创建的。这两个接口都实现了 IDisposable
接口,这意味着它们应该被处理掉。因此,我的存储库也实现了 IDisposable
。然而,问题就在这里。理论上,我可以从一个工作单元创建任意数量的存储库,例如:
public void UpdatePets(List<Cat> felines, List<Dog> carnines, ISessionManager manager)
{
var uow = manager.Create("Pets", DataAccess.Statefull);
using (var t = uow.OpenTransaction())
{
using (var catRepo = uow.GetRepository<Cat>())
{
catRepo.Add(felines);
t.Commit();
}
}
//Some Businesslogic that forbids using one Transaction to mitigate the problem of an already disposed ISession or IStatelessSession.
using(var t = uow.OpenTransaction())
{
using (var dogRepo = uow.GetRepository<Dog>())
{
dogRepo.Add(carnines);
t.Commit();
}
}
}
如果那是消耗我的工作单元的服务,我会像这样使用 "standard" IDisposable
模式:
private void Dispose(bool disposing)
{
if (!_disposed)
{
if (disposing)
{
if (_session != null)
_session.Dispose();
}
_disposed = true;
}
}
public void Dispose()
{
Dispose(true);
GC.SuppressFinalize(this);
}
第二个存储库会抛出 ObjectDisposedexception,因为我在第二个存储库中引用的 ISession 来自我的工作单元,而当我的第一个存储库离开 using 块时,那个已经被处理掉了。因此,为了克服这个问题,我会在我的存储库中执行以下操作:
public void Dispose()
{
_session = null;
GC.SuppressFinalize(this);
}
然而,这感觉不对,因为我没有正确处理对我应该处理的对象的引用,而是 "close my Eyes and forget about it",这可能永远不是编程中的好解决方案。 所以我的问题基本上是:"Is there a good way to properly dispose objects that might live longer than the object holding the reference?"
您需要建立所有权,并使用它来确定哪个 class 负责清理每个 "thing"。它是所有者,并且只有所有者应该调用 Dispose
,无论有多少其他 class 可能已经与它交互。
所有权需要 独占 并且(包装 classes 除外)嵌套1 - 所有者的生命周期需要至少与拥有的 object 一样长。通过遵循这两条规则,我们可以确保 Dispose a) 调用一次 2 b) 当不再需要 object 时调用。
存储库对 "own" session 来说是错误的。多个存储库可以被赋予相同的 session(因此,如果他们是所有者,我们就失去了独占 属性),正如您自己的标题所暗示的,他们有一个 比它短 生命周期。
一些一般经验法则
- 作为参数传递给实例方法的 object 通常不会被实例拥有。
- 由实例创建的 object 生命周期长于单个方法调用的生命周期的 object 将由实例拥有
- 由实例方法创建的 object 且不长于单个方法调用的生命周期的 object 将由方法拥有
1对于包装器,内部 object 往往会首先创建,可能已配置,然后传递给包装器 object。所以包装器 object 的生命周期在内部 object.
之后开始2不严格要求。 Disposables 应该容忍 Dispose 被多次调用,但这更多的是 belt-and-braces 防御性而不是所需的模式。