Castle Windsor 显式共享依赖项

Castle Windsor explicitly sharing dependencies

如何在不同时间执行的组件图之间共享临时对象?

我有一个来自旧遗留代码的状态引擎。每个状态都由一个 IState 表示,并负责在流程中创建下一个状态。

public interface IState
{
    Guid Session { get; }
    IState GetNextState();
}

起始状态由模型初始化:

public class Model
{
    private readonly IStateFactory _stateFactory;

    public Model(IStateFactory stateFactory)
    {
        _stateFactory = stateFactory;
    }

    public IState GetFirstState()
    {
        return _stateFactory.GetStateA();
    }
}

每个状态都包含一个会话上下文(这里简化为只包含一个 GUID)。

public class Context : IDisposable
{
    public static int CreatedCount = 0;
    public static int DisposedCount = 0;
    //Has other DI injected dependencies.

    public Context()
    {
        CreatedCount++;
    }

    public Guid SessionGuid { get; } = Guid.NewGuid();

    public void Dispose()
    {
        DisposedCount++;
    }
}

已添加 "CreatedCount" 和 "DisposedCount" 以帮助演示问题。请注意,它们是静态整数。

状态的实现可能是这样的:

public class MyState : IState
{
    private readonly Context _context;
    private readonly IStateFactory _stateFactory;

    public MyState(IStateFactory stateFactory, Context context)
    {
        _context = context;
        _stateFactory = stateFactory;
    }

    public Guid Session => _context.SessionGuid;

    public IState GetNextState()
    {
        var nextState = _stateFactory.GetStateB(_context);
        _stateFactory.DestroyState(this);
        return nextState;
    }
}

状态工厂是一个简单的 Castle Windsor 实现的 TypedFactory 接口。

public interface IStateFactory
{
    IState GetFirstState(); 
    IState GetStateB(Context context);

    void DestroyState(IState state);
}

想法是每个 "state" 都可以根据某个动作启动下一个状态,并且当前状态 "context" 应该在下一个状态中使用。

容器以预期方式构建:

var container = new WindsorContainer();
container.AddFacility<TypedFactoryFacility>();

container.Register(
    Component.For<Context>().LifestyleTransient(),
    Component.For<IState>().ImplementedBy<MyState>().Named("stateA").LifestyleTransient(),
    Component.For<IState>().ImplementedBy<MyState>().Named("stateB").LifestyleTransient(),
    Component.For<IStateFactory>().AsFactory()
);

本质上,我希望 "stateB" 获得 Context 的所有权。但是当我释放 "stateA"(通过调用 MyState.GetNextState)时,上下文被释放并释放!我如何告诉 Castle.Windsor 将所有权转移到下一个州?

var model = container.Resolve<Model>();
var initialState = model.GetFirstState();
var nextState = initialState.GetNextState(); //releases the initial State.

Assert.That(initialState.Session, Is.EqualTo(nextState.Session)); //The context was 'shared' by stateA by passing it into the factory method for stateB.

Assert.That(Context.CreatedCount, Is.EqualTo(1));
Assert.That(Context.DisposedCount, Is.EqualTo(0)); //FAIL! Castle Windsor should see that the shared "Context" was passed into the constructor of modelB, and added a reference to it.

container.Release(model);
container.Release(nextState); //usually done by the model.
Assert.That(Context.CreatedCount, Is.EqualTo(1));
Assert.That(Context.DisposedCount, Is.EqualTo(1)); 

需要注意的是,状态转换可以从另一个线程启动,但在创建线程上调用。这会扰乱默认的 Castle Windsor 作用域生活方式使用的 CallContext。这是针对桌面应用程序的,因此默认的 WCF 和 Web 请求范围生活方式不适用。

使用LifestyleScoped:

Component.For<Context>().LifestyleScoped()

using (container.BeginScope())
{
    var model = container.Resolve<Model>();
    var initialState = model.GetFirstState();
    var nextState = initialState.GetNextState();
    Assert.That(Context.CreatedCount, Is.EqualTo(0));
}

Assert.That(Context.CreatedCount, Is.EqualTo(1));

我想出了另一个解决这个问题的方法,它适用于 Castle Windsors 范围不起作用的地方。我创造了一种生活方式,只要有东西通过引用计数使用它,它就会一直存在。一旦引用计数变为 0,对象就会被释放。

public class ReferenceCountedSingleton : AbstractLifestyleManager, IContextLifestyleManager
{
    private readonly object _lock = new object();
    private Burden _cachedBurden;
    private int _referenceCount;

    public override void Dispose()
    {
        var localInstance = _cachedBurden;
        if (localInstance != null)
        {
            localInstance.Release();
            _cachedBurden = null;
        }
    }

    public override object Resolve(CreationContext context, IReleasePolicy releasePolicy)
    {
        lock(_lock)
        {
            _referenceCount++;

            if (_cachedBurden != null)
            {
                Debug.Assert(_referenceCount > 0);
                return _cachedBurden.Instance;
            }

            if (_cachedBurden != null)
            {
                return _cachedBurden.Instance;
            }
            var burden = CreateInstance(context, false);
            _cachedBurden = burden;
            Track(burden, releasePolicy);
            return burden.Instance;
        }
    }

    public override bool Release(object instance)
    {
        lock (_lock)
        {
            if (_referenceCount > 0) _referenceCount--;
            if (_referenceCount > 0) return false;
            _referenceCount = 0;
            _cachedBurden = null;
            return base.Release(instance);
        }
    }

    protected override void Track(Burden burden, IReleasePolicy releasePolicy)
    {
        burden.RequiresDecommission = true;
        base.Track(burden, releasePolicy);
    }

    public object GetContextInstance(CreationContext context)
    {
        return context.GetContextualProperty(ComponentActivator);
    }
}

它是这样使用的:

container.Register(Component.For<Context>).LifestyleCustom<ReferenceCountedSingleton>());