rd-net/Lifetimes/Collections/Viewable/WriteOnceProperty.cs (83 lines of code) (raw):

using System; using JetBrains.Core; using JetBrains.Lifetimes; namespace JetBrains.Collections.Viewable { public sealed class WriteOnceProperty<T> : IViewableProperty<T> { private readonly WriteOnceSignal mySignal = new WriteOnceSignal(); private readonly object myLock = new object(); private Maybe<T> myMaybe; public ISource<T> Change => mySignal; public Maybe<T> Maybe { get { lock (myLock) // to avoid non-atomic read return myMaybe; } } public T Value { get { return Maybe.OrElseThrow(() => new InvalidOperationException("Not initialized")); } set { if (!SetIfEmpty(value)) throw new InvalidOperationException($"WriteOnceProperty is already set with `{value}`, but you're trying to rewrite it with `{value}`"); } } public bool SetIfEmpty(T newValue) { lock(myLock) { if (myMaybe.HasValue) return false; myMaybe = new Maybe<T>(newValue); } mySignal.Fire(newValue); return true; } public void Advise(Lifetime lifetime, Action<T> handler) { if (lifetime.IsNotAlive) return; Maybe<T> local; lock (myLock) { local = myMaybe; if (!local.HasValue) // to avoid calling the handler twice { mySignal.Advise(lifetime, handler); return; } } handler(local.Value); } // for test internal void fireInternal(T value) => mySignal.Fire(value); private sealed class WriteOnceSignal : SignalBase<T> { private readonly LifetimeDefinition myDef = new LifetimeDefinition(); public Lifetime Lifetime => myDef.Lifetime; public override void Advise(Lifetime lifetime, Action<T> handler) { if (Lifetime.IsNotAlive || lifetime.IsNotAlive) return; var nestedLifetime = Lifetime.Intersect(lifetime); base.Advise(nestedLifetime, handler); } public override void Fire(T value) { try { base.Fire(value); } finally { myDef.Terminate(); } } } } }