Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Feature: Add ReactiveProperty #3729

Merged
merged 4 commits into from
Feb 15, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Jump to
Jump to file
Failed to load files.
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -349,6 +349,10 @@ namespace ReactiveUI
string? PropertyName { get; }
TSender Sender { get; }
}
public interface IReactiveProperty<T> : System.IDisposable, System.IObservable<T?>, System.Reactive.Disposables.ICancelable
{
T Value { get; set; }
}
public interface IRoutableViewModel : ReactiveUI.IReactiveObject, Splat.IEnableLogger, System.ComponentModel.INotifyPropertyChanged, System.ComponentModel.INotifyPropertyChanging
{
ReactiveUI.IScreen HostScreen { get; }
Expand Down Expand Up @@ -750,6 +754,20 @@ namespace ReactiveUI
public TSender Sender { get; }
}
[System.Runtime.Serialization.DataContract]
public class ReactiveProperty<T> : ReactiveUI.ReactiveObject, ReactiveUI.IReactiveProperty<T>, System.IDisposable, System.IObservable<T?>, System.Reactive.Disposables.ICancelable
{
public ReactiveProperty() { }
public ReactiveProperty(T? initialValue) { }
public ReactiveProperty(T? initialValue, System.Reactive.Concurrency.IScheduler? scheduler) { }
public bool IsDisposed { get; }
[System.Runtime.Serialization.DataMember]
[System.Text.Json.Serialization.JsonInclude]
public T Value { get; set; }
public void Dispose() { }
protected virtual void Dispose(bool disposing) { }
public System.IDisposable Subscribe(System.IObserver<T?> observer) { }
}
[System.Runtime.Serialization.DataContract]
public class ReactiveRecord : ReactiveUI.IHandleObservableErrors, ReactiveUI.IReactiveNotifyPropertyChanged<ReactiveUI.IReactiveObject>, ReactiveUI.IReactiveObject, Splat.IEnableLogger, System.ComponentModel.INotifyPropertyChanged, System.ComponentModel.INotifyPropertyChanging, System.IEquatable<ReactiveUI.ReactiveRecord>
{
public ReactiveRecord() { }
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -349,6 +349,10 @@ namespace ReactiveUI
string? PropertyName { get; }
TSender Sender { get; }
}
public interface IReactiveProperty<T> : System.IDisposable, System.IObservable<T?>, System.Reactive.Disposables.ICancelable
{
T Value { get; set; }
}
public interface IRoutableViewModel : ReactiveUI.IReactiveObject, Splat.IEnableLogger, System.ComponentModel.INotifyPropertyChanged, System.ComponentModel.INotifyPropertyChanging
{
ReactiveUI.IScreen HostScreen { get; }
Expand Down Expand Up @@ -750,6 +754,20 @@ namespace ReactiveUI
public TSender Sender { get; }
}
[System.Runtime.Serialization.DataContract]
public class ReactiveProperty<T> : ReactiveUI.ReactiveObject, ReactiveUI.IReactiveProperty<T>, System.IDisposable, System.IObservable<T?>, System.Reactive.Disposables.ICancelable
{
public ReactiveProperty() { }
public ReactiveProperty(T? initialValue) { }
public ReactiveProperty(T? initialValue, System.Reactive.Concurrency.IScheduler? scheduler) { }
public bool IsDisposed { get; }
[System.Runtime.Serialization.DataMember]
[System.Text.Json.Serialization.JsonInclude]
public T Value { get; set; }
public void Dispose() { }
protected virtual void Dispose(bool disposing) { }
public System.IDisposable Subscribe(System.IObserver<T?> observer) { }
}
[System.Runtime.Serialization.DataContract]
public class ReactiveRecord : ReactiveUI.IHandleObservableErrors, ReactiveUI.IReactiveNotifyPropertyChanged<ReactiveUI.IReactiveObject>, ReactiveUI.IReactiveObject, Splat.IEnableLogger, System.ComponentModel.INotifyPropertyChanged, System.ComponentModel.INotifyPropertyChanging, System.IEquatable<ReactiveUI.ReactiveRecord>
{
public ReactiveRecord() { }
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -349,6 +349,10 @@ namespace ReactiveUI
string? PropertyName { get; }
TSender Sender { get; }
}
public interface IReactiveProperty<T> : System.IDisposable, System.IObservable<T?>, System.Reactive.Disposables.ICancelable
{
T Value { get; set; }
}
public interface IRoutableViewModel : ReactiveUI.IReactiveObject, Splat.IEnableLogger, System.ComponentModel.INotifyPropertyChanged, System.ComponentModel.INotifyPropertyChanging
{
ReactiveUI.IScreen HostScreen { get; }
Expand Down Expand Up @@ -750,6 +754,20 @@ namespace ReactiveUI
public TSender Sender { get; }
}
[System.Runtime.Serialization.DataContract]
public class ReactiveProperty<T> : ReactiveUI.ReactiveObject, ReactiveUI.IReactiveProperty<T>, System.IDisposable, System.IObservable<T?>, System.Reactive.Disposables.ICancelable
{
public ReactiveProperty() { }
public ReactiveProperty(T? initialValue) { }
public ReactiveProperty(T? initialValue, System.Reactive.Concurrency.IScheduler? scheduler) { }
public bool IsDisposed { get; }
[System.Runtime.Serialization.DataMember]
[System.Text.Json.Serialization.JsonInclude]
public T Value { get; set; }
public void Dispose() { }
protected virtual void Dispose(bool disposing) { }
public System.IDisposable Subscribe(System.IObserver<T?> observer) { }
}
[System.Runtime.Serialization.DataContract]
public class ReactiveRecord : ReactiveUI.IHandleObservableErrors, ReactiveUI.IReactiveNotifyPropertyChanged<ReactiveUI.IReactiveObject>, ReactiveUI.IReactiveObject, Splat.IEnableLogger, System.ComponentModel.INotifyPropertyChanged, System.ComponentModel.INotifyPropertyChanging, System.IEquatable<ReactiveUI.ReactiveRecord>
{
public ReactiveRecord() { }
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -347,6 +347,10 @@ namespace ReactiveUI
string? PropertyName { get; }
TSender Sender { get; }
}
public interface IReactiveProperty<T> : System.IDisposable, System.IObservable<T?>, System.Reactive.Disposables.ICancelable
{
T Value { get; set; }
}
public interface IRoutableViewModel : ReactiveUI.IReactiveObject, Splat.IEnableLogger, System.ComponentModel.INotifyPropertyChanged, System.ComponentModel.INotifyPropertyChanging
{
ReactiveUI.IScreen HostScreen { get; }
Expand Down Expand Up @@ -748,6 +752,20 @@ namespace ReactiveUI
public TSender Sender { get; }
}
[System.Runtime.Serialization.DataContract]
public class ReactiveProperty<T> : ReactiveUI.ReactiveObject, ReactiveUI.IReactiveProperty<T>, System.IDisposable, System.IObservable<T?>, System.Reactive.Disposables.ICancelable
{
public ReactiveProperty() { }
public ReactiveProperty(T? initialValue) { }
public ReactiveProperty(T? initialValue, System.Reactive.Concurrency.IScheduler? scheduler) { }
public bool IsDisposed { get; }
[System.Runtime.Serialization.DataMember]
[System.Text.Json.Serialization.JsonInclude]
public T Value { get; set; }
public void Dispose() { }
protected virtual void Dispose(bool disposing) { }
public System.IDisposable Subscribe(System.IObserver<T?> observer) { }
}
[System.Runtime.Serialization.DataContract]
public class ReactiveRecord : ReactiveUI.IHandleObservableErrors, ReactiveUI.IReactiveNotifyPropertyChanged<ReactiveUI.IReactiveObject>, ReactiveUI.IReactiveObject, Splat.IEnableLogger, System.ComponentModel.INotifyPropertyChanged, System.ComponentModel.INotifyPropertyChanging, System.IEquatable<ReactiveUI.ReactiveRecord>
{
public ReactiveRecord() { }
Expand Down
28 changes: 28 additions & 0 deletions src/ReactiveUI.Tests/ReactiveProperty/ReactivePropertyTest.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,28 @@
// Copyright (c) 2023 .NET Foundation and Contributors. All rights reserved.
// Licensed to the .NET Foundation under one or more agreements.
// The .NET Foundation licenses this file to you under the MIT license.
// See the LICENSE file in the project root for full license information.

using Microsoft.Reactive.Testing;

namespace ReactiveUI.Tests.ReactiveProperty
{
public class ReactivePropertyTest : ReactiveTest
{
[Fact]
public void NormalCase()
{
var rp = new ReactiveProperty<string>();
Assert.Null(rp.Value);
rp.Subscribe(x => Assert.Null(x));
}

[Fact]
public void InitialValue()
{
var rp = new ReactiveProperty<string>("Hello world");
Assert.Equal(rp.Value, "Hello world");
rp.Subscribe(x => Assert.Equal(x, "Hello world"));
}
}
}
14 changes: 14 additions & 0 deletions src/ReactiveUI.Tests/ReactiveProperty/TestEnum.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
// Copyright (c) 2023 .NET Foundation and Contributors. All rights reserved.
// Licensed to the .NET Foundation under one or more agreements.
// The .NET Foundation licenses this file to you under the MIT license.
// See the LICENSE file in the project root for full license information.

namespace ReactiveUI.Tests.ReactiveProperty
{
internal enum TestEnum
{
None,
Enum1,
Enum2
}
}
23 changes: 23 additions & 0 deletions src/ReactiveUI/ReactiveProperty/IReactiveProperty.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,23 @@
// Copyright (c) 2023 .NET Foundation and Contributors. All rights reserved.
// Licensed to the .NET Foundation under one or more agreements.
// The .NET Foundation licenses this file to you under the MIT license.
// See the LICENSE file in the project root for full license information.

namespace ReactiveUI;

/// <summary>
/// Reactive Property.
/// </summary>
/// <typeparam name="T">The type of the property.</typeparam>
/// <seealso cref="IObservable&lt;T&gt;" />
/// <seealso cref="ICancelable" />
public interface IReactiveProperty<T> : IObservable<T?>, ICancelable
{
/// <summary>
/// Gets or sets the value.
/// </summary>
/// <value>
/// The value.
/// </value>
public T? Value { get; set; }
}
100 changes: 100 additions & 0 deletions src/ReactiveUI/ReactiveProperty/ReactiveProperty.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,100 @@
// Copyright (c) 2023 .NET Foundation and Contributors. All rights reserved.
// Licensed to the .NET Foundation under one or more agreements.
// The .NET Foundation licenses this file to you under the MIT license.
// See the LICENSE file in the project root for full license information.

namespace ReactiveUI;

/// <summary>
/// ReactiveProperty - a two way bindable declarative observable property with imperative get set.
/// </summary>
/// <typeparam name="T">The type of the property.</typeparam>
/// <seealso cref="ReactiveObject" />
/// <seealso cref="IReactiveProperty&lt;T&gt;" />
[DataContract]
public class ReactiveProperty<T> : ReactiveObject, IReactiveProperty<T>
{
private readonly IScheduler _scheduler;
private readonly CompositeDisposable _disposables = [];
private T? _value;

/// <summary>
/// Initializes a new instance of the <see cref="ReactiveProperty{T}"/> class.
/// </summary>
public ReactiveProperty() => _scheduler = RxApp.TaskpoolScheduler;

/// <summary>
/// Initializes a new instance of the <see cref="ReactiveProperty{T}"/> class.
/// </summary>
/// <param name="initialValue">The initial value.</param>
public ReactiveProperty(T? initialValue)
{
Value = initialValue;
_scheduler = RxApp.TaskpoolScheduler;
}

/// <summary>
/// Initializes a new instance of the <see cref="ReactiveProperty{T}"/> class.
/// </summary>
/// <param name="initialValue">The initial value.</param>
/// <param name="scheduler">The scheduler.</param>
public ReactiveProperty(T? initialValue, IScheduler? scheduler)
{
Value = initialValue;
_scheduler = scheduler ?? RxApp.TaskpoolScheduler;
}

/// <summary>
/// Gets a value indicating whether gets a value that indicates whether the object is disposed.
/// </summary>
public bool IsDisposed => _disposables.IsDisposed;

/// <summary>
/// Gets or sets the value.
/// </summary>
/// <value>
/// The value.
/// </value>
[DataMember]
[JsonInclude]
public T? Value
{
get => _value;
set => this.RaiseAndSetIfChanged(ref _value, value);
}

/// <summary>
/// Performs application-defined tasks associated with freeing, releasing, or resetting unmanaged resources.
/// </summary>
public void Dispose()
{
Dispose(true);
GC.SuppressFinalize(this);
}

/// <summary>
/// Notifies the provider that an observer is to receive notifications.
/// </summary>
/// <param name="observer">The object that is to receive notifications.</param>
/// <returns>
/// A reference to an interface that allows observers to stop receiving notifications before
/// the provider has finished sending them.
/// </returns>
public IDisposable Subscribe(IObserver<T?> observer) =>
this.WhenAnyValue(vm => vm.Value)
.ObserveOn(_scheduler)
.Subscribe(observer)
.DisposeWith(_disposables);

/// <summary>
/// Releases unmanaged and - optionally - managed resources.
/// </summary>
/// <param name="disposing"><c>true</c> to release both managed and unmanaged resources; <c>false</c> to release only unmanaged resources.</param>
protected virtual void Dispose(bool disposing)
{
if (_disposables?.IsDisposed == false && disposing)
{
_disposables?.Dispose();
}
}
}