/// <summary> /// Initializes a new instance of the <see cref="ComputedValue{T}"/> class. /// </summary> /// <param name="sharedState">The shared state this computedValue is connected to.</param> /// <param name="options">An <see cref="ComputedValueOptions{T}"/> instance that define the options for this computed value.</param> public ComputedValue(ISharedState sharedState, ComputedValueOptions <T> options) { if (sharedState is null) { throw new ArgumentNullException(nameof(sharedState)); } if (options is null) { throw new ArgumentNullException(nameof(options)); } this.Name = options.Name; this.derivation = options.Getter; this.SharedState = sharedState; if (options.Setter != null) { this.setter = sharedState.CreateAction($"{options.Name}-setter", options.Context, options.Setter); } this.scope = options.Context; this.equalityComparer = options.EqualityComparer; this.keepAlive = options.KeepAlive; this.requiresReaction = options.RequiresReaction; }
public Person(ISharedState sharedState) { observableObject = new ObservableObject(nameof(Person), sharedState.GetEnhancer(typeof(DeepEnhancer)), sharedState); observableObject.AddObservableProperty <string>(nameof(FirstName)); observableObject.AddObservableProperty <string>(nameof(LastName)); observableObject.AddComputedMember(nameof(FullName3), new ComputedValueOptions <string>(this.Getter, nameof(FullName3)) { Context = this, KeepAlive = false, RequiresReaction = false, EqualityComparer = new ReferenceEqualityComparer <string>(), Setter = this.Setter, }); testAction = sharedState.CreateAction("ChangeBothNames", this, new Action <string, string>(this.ChangeBothNames)); }
/// <summary> /// Initializes a new instance of the <see cref="ComputedValue{T}"/> class. /// </summary> /// <param name="sharedState">The shared state this computedValue is connected to.</param> /// <param name="options">An <see cref="ComputedValueOptions{T}"/> instance that define the options for this computed value.</param> internal ComputedValue(ISharedState sharedState, ComputedValueOptions <T> options) { // resolve state state from context if necessary. this.SharedState = Net.SharedState.ResolveState(sharedState); if (options is null) { throw new ArgumentNullException(nameof(options)); } this.Name = options.Name; this.derivation = options.Getter; if (options.Setter != null) { this.setter = sharedState.CreateAction($"{options.Name}-setter", options.Context, options.Setter); } this.scope = options.Context; this.equalityComparer = options.EqualityComparer; this.keepAlive = options.KeepAlive; this.requiresReaction = options.RequiresReaction; }
/// <summary> /// Creates a reaction that operates on data of type T. /// </summary> /// <typeparam name="T">The type the reaction operates on.</typeparam> /// <param name="sharedState">The shared state to use.</param> /// <param name="expression">The expression that delivers a value.</param> /// <param name="effect">The effect that is executed when the value changes.</param> /// <param name="reactionOptions">The options to use for the reaction.</param> /// <returns>An <see cref="IDisposable"/> instance that can be used to stop the reaction.</returns> public static IDisposable Reaction <T>(this ISharedState sharedState, Func <Reaction, T> expression, Action <T, Reaction> effect, ReactionOptions <T> reactionOptions = null) { if (sharedState is null) { throw new ArgumentNullException(nameof(sharedState)); } if (expression is null) { throw new ArgumentNullException(nameof(expression)); } if (effect is null) { throw new ArgumentNullException(nameof(effect)); } if (reactionOptions is null) { reactionOptions = new ReactionOptions <T>(); } var name = reactionOptions.Name ?? $"Reaction@{sharedState.GetUniqueId()}"; var action = sharedState.CreateAction( name + "effect", reactionOptions.Context, reactionOptions.ErrorHandler != null ? WrapErrorHandler(reactionOptions.ErrorHandler, effect) : effect); var runSync = reactionOptions.Scheduler == null && reactionOptions.Delay == 0; var firstTime = true; var isScheduled = false; T value = default; Reaction reaction = null; var equals = reactionOptions.EqualityComparer != null ? new Func <T, T, bool>(reactionOptions.EqualityComparer.Equals) : new Func <T, T, bool>((x, y) => Equals(x, y)); var scheduler = CreateSchedulerFromOptions(reactionOptions, ReactionRunner); #pragma warning disable CA2000 // Dispose objects before losing scope #pragma warning disable IDE0067 // Dispose objects before losing scope reaction = new Reaction( sharedState, name, () => { if (firstTime || runSync) { ReactionRunner().GetAwaiter().GetResult(); } else if (!isScheduled) { isScheduled = true; scheduler().GetAwaiter().GetResult(); } }, reactionOptions.ErrorHandler); #pragma warning restore IDE0067 // Dispose objects before losing scope #pragma warning restore CA2000 // Dispose objects before losing scope Task ReactionRunner() { isScheduled = false; // Q: move into reaction runner? if (reaction.IsDisposed) { return(Task.CompletedTask); } var changed = false; reaction.Track(() => { var nextValue = expression(reaction); changed = firstTime || !equals(value, nextValue); value = nextValue; }); if (firstTime && reactionOptions.FireImmediately) { action(value, reaction); } if (!firstTime && changed) { action(value, reaction); } if (firstTime) { firstTime = false; } return(Task.CompletedTask); } reaction.Schedule(); return(new DisposableDelegate(() => reaction.Dispose())); }
/// <summary> /// Creates a reaction that operates on data of type T. /// </summary> /// <typeparam name="T">The type the reaction operates on.</typeparam> /// <param name="sharedState">The shared state to use.</param> /// <param name="expression">The expression that delivers a value.</param> /// <param name="effect">The effect that is executed when the value changes.</param> /// <param name="reactionOptions">The options to use for the reaction.</param> /// <returns>An <see cref="IDisposable"/> instance that can be used to stop the reaction.</returns> public static IDisposable Reaction <T>(this ISharedState sharedState, Func <Reaction, T> expression, Action <T, Reaction> effect, ReactionOptions <T> reactionOptions = null) { if (sharedState is null) { throw new ArgumentNullException(nameof(sharedState)); } if (expression is null) { throw new ArgumentNullException(nameof(expression)); } if (effect is null) { throw new ArgumentNullException(nameof(effect)); } if (reactionOptions is null) { reactionOptions = new ReactionOptions <T>(); } var name = reactionOptions.Name ?? $"Reaction@{sharedState.GetUniqueId()}"; var action = sharedState.CreateAction( name + "effect", reactionOptions.Context, reactionOptions.ErrorHandler != null ? WrapErrorHandler(reactionOptions.ErrorHandler, effect) : effect); var runSync = reactionOptions.Scheduler == null && reactionOptions.Delay == 0; var firstTime = true; var isScheduled = false; T value = default; Reaction reaction = null; var equals = reactionOptions.EqualityComparer != null ? new Func <T, T, bool>(reactionOptions.EqualityComparer.Equals) : new Func <T, T, bool>((x, y) => Equals(x, y)); #pragma warning disable CS1998 // Async method lacks 'await' operators and will run synchronously var scheduler = CreateSchedulerFromOptions(reactionOptions, async() => #pragma warning restore CS1998 // Async method lacks 'await' operators and will run synchronously { ReactionRunner(); }); #pragma warning disable CA2000 // Dispose objects before losing scope #pragma warning disable IDE0067 // Dispose objects before losing scope reaction = new Reaction( sharedState, name, () => { if (firstTime || runSync) { ReactionRunner(); } else if (!isScheduled) { var taskScheduler = sharedState.GetTaskScheduler(); isScheduled = true; Task.Factory.StartNew( scheduler, CancellationToken.None, TaskCreationOptions.DenyChildAttach, taskScheduler).Unwrap().ContinueWith( t => { if (t.Exception != null) { reaction.ReportExceptionInReaction(t.Exception); } }, taskScheduler); } }, reactionOptions.ErrorHandler); #pragma warning restore IDE0067 // Dispose objects before losing scope #pragma warning restore CA2000 // Dispose objects before losing scope void ReactionRunner() { isScheduled = false; // Q: move into reaction runner? if (reaction.IsDisposed) { return; } var changed = false; reaction.Track(() => { var nextValue = expression(reaction); changed = firstTime || !equals(value, nextValue); value = nextValue; }); if (firstTime && reactionOptions.FireImmediately) { action(value, reaction); } if (!firstTime && changed) { action(value, reaction); } if (firstTime) { firstTime = false; } } reaction.Schedule(); return(new DisposableDelegate(() => reaction.Dispose())); }