From 54ddbf5e175c1cc0a096a04ba616e3612f9b04e7 Mon Sep 17 00:00:00 2001 From: Glenn <5834289+glennawatson@users.noreply.github.com> Date: Wed, 12 May 2021 11:46:43 +1000 Subject: [PATCH 1/4] Create ReactiveRecord.cs --- .../ReactiveObject/ReactiveRecord.cs | 111 ++++++++++++++++++ 1 file changed, 111 insertions(+) create mode 100644 src/ReactiveUI/ReactiveObject/ReactiveRecord.cs diff --git a/src/ReactiveUI/ReactiveObject/ReactiveRecord.cs b/src/ReactiveUI/ReactiveObject/ReactiveRecord.cs new file mode 100644 index 0000000000..8e3bd51502 --- /dev/null +++ b/src/ReactiveUI/ReactiveObject/ReactiveRecord.cs @@ -0,0 +1,111 @@ +// Copyright (c) 2021 .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 System; +using System.ComponentModel; +using System.Reactive; +using System.Runtime.Serialization; +using System.Threading; + +namespace ReactiveUI +{ + /// + /// ReactiveObject is the base object for ViewModel classes, and it + /// implements INotifyPropertyChanged. In addition, ReactiveObject provides + /// Changing and Changed Observables to monitor object changes. + /// + [DataContract] + public record ReactiveRecord : IReactiveNotifyPropertyChanged, IHandleObservableErrors, IReactiveObject + { + private readonly Lazy>> _changing; + private readonly Lazy>> _changed; + private readonly Lazy _propertyChangingEventsSubscribed; + private readonly Lazy _propertyChangedEventsSubscribed; + private readonly Lazy> _thrownExceptions; + + /// + /// Initializes a new instance of the class. + /// + public ReactiveRecord() + { + _changing = new Lazy>>(() => ((IReactiveObject)this).GetChangingObservable(), LazyThreadSafetyMode.PublicationOnly); + _changed = new Lazy>>(() => ((IReactiveObject)this).GetChangedObservable(), LazyThreadSafetyMode.PublicationOnly); + _propertyChangingEventsSubscribed = new Lazy( + () => + { + this.SubscribePropertyChangingEvents(); + return Unit.Default; + }, + LazyThreadSafetyMode.PublicationOnly); + _propertyChangedEventsSubscribed = new Lazy( + () => + { + this.SubscribePropertyChangedEvents(); + return Unit.Default; + }, + LazyThreadSafetyMode.PublicationOnly); + _thrownExceptions = new Lazy>(this.GetThrownExceptionsObservable, LazyThreadSafetyMode.PublicationOnly); + } + + /// + public event PropertyChangingEventHandler? PropertyChanging + { + add + { + _ = _propertyChangingEventsSubscribed.Value; + PropertyChangingHandler += value; + } + remove => PropertyChangingHandler -= value; + } + + /// + public event PropertyChangedEventHandler? PropertyChanged + { + add + { + _ = _propertyChangedEventsSubscribed.Value; + PropertyChangedHandler += value; + } + remove => PropertyChangedHandler -= value; + } + + private event PropertyChangingEventHandler? PropertyChangingHandler; + + private event PropertyChangedEventHandler? PropertyChangedHandler; + + /// + [IgnoreDataMember] + public IObservable> Changing => _changing.Value; + + /// + [IgnoreDataMember] + public IObservable> Changed => _changed.Value; + + /// + [IgnoreDataMember] + public IObservable ThrownExceptions => _thrownExceptions.Value; + + /// + void IReactiveObject.RaisePropertyChanging(PropertyChangingEventArgs args) => PropertyChangingHandler?.Invoke(this, args); + + /// + void IReactiveObject.RaisePropertyChanged(PropertyChangedEventArgs args) => PropertyChangedHandler?.Invoke(this, args); + + /// + public IDisposable SuppressChangeNotifications() => IReactiveObjectExtensions.SuppressChangeNotifications(this); + + /// + /// Determines if change notifications are enabled or not. + /// + /// A value indicating whether change notifications are enabled. + public bool AreChangeNotificationsEnabled() => IReactiveObjectExtensions.AreChangeNotificationsEnabled(this); + + /// + /// Delays notifications until the return IDisposable is disposed. + /// + /// A disposable which when disposed will send delayed notifications. + public IDisposable DelayChangeNotifications() => IReactiveObjectExtensions.DelayChangeNotifications(this); + } +} From c0bf3f3399705ca56d1389918c7be72c144469b6 Mon Sep 17 00:00:00 2001 From: Glenn Watson Date: Wed, 12 May 2021 11:48:15 +1000 Subject: [PATCH 2/4] Add IsExternalInit --- src/ReactiveUI/IsExternalInit.cs | 17 +++++++++++++++++ 1 file changed, 17 insertions(+) create mode 100644 src/ReactiveUI/IsExternalInit.cs diff --git a/src/ReactiveUI/IsExternalInit.cs b/src/ReactiveUI/IsExternalInit.cs new file mode 100644 index 0000000000..6124ad128c --- /dev/null +++ b/src/ReactiveUI/IsExternalInit.cs @@ -0,0 +1,17 @@ +// Copyright (c) 2019-2021 ReactiveUI Association Incorporated. All rights reserved. +// ReactiveUI Association Incorporated licenses this file to you under the MIT license. +// See the LICENSE file in the project root for full license information. + +using System.ComponentModel; + +namespace System.Runtime.CompilerServices +{ + /// + /// Reserved to be used by the compiler for tracking metadata. + /// This class should not be used by developers in source code. + /// + [EditorBrowsable(EditorBrowsableState.Never)] + internal static class IsExternalInit + { + } +} \ No newline at end of file From 26314c8ea98d1aa78509ab38cdc9bd2a8161622c Mon Sep 17 00:00:00 2001 From: Glenn Watson Date: Wed, 12 May 2021 11:56:21 +1000 Subject: [PATCH 3/4] Add version file bump --- .editorconfig | 32 +++++++++++++++++--------------- version.json | 2 +- 2 files changed, 18 insertions(+), 16 deletions(-) diff --git a/.editorconfig b/.editorconfig index ee3eb6c50b..de41f1de01 100644 --- a/.editorconfig +++ b/.editorconfig @@ -10,12 +10,11 @@ root = true insert_final_newline = true indent_style = space indent_size = 4 - -[project.json] -indent_size = 2 +trim_trailing_whitespace = true # C# files [*.cs] + # New line preferences csharp_new_line_before_open_brace = all csharp_new_line_before_else = true @@ -42,11 +41,6 @@ dotnet_style_qualification_for_property = false:suggestion dotnet_style_qualification_for_method = false:suggestion dotnet_style_qualification_for_event = false:suggestion -# only use var when it's obvious what the variable type is -csharp_style_var_for_built_in_types = false:none -csharp_style_var_when_type_is_apparent = true:suggestion -csharp_style_var_elsewhere = true:suggestion - # Types: use keywords instead of BCL types, and permit var only when the type is clear csharp_style_var_for_built_in_types = false:suggestion csharp_style_var_when_type_is_apparent = false:none @@ -62,14 +56,14 @@ dotnet_naming_symbols.constant_fields.applicable_kinds = field dotnet_naming_symbols.constant_fields.required_modifiers = const dotnet_naming_style.pascal_case_style.capitalization = pascal_case -# static fields should have s_ prefix +# static fields should have _ prefix dotnet_naming_rule.static_fields_should_have_prefix.severity = suggestion dotnet_naming_rule.static_fields_should_have_prefix.symbols = static_fields dotnet_naming_rule.static_fields_should_have_prefix.style = static_prefix_style dotnet_naming_symbols.static_fields.applicable_kinds = field dotnet_naming_symbols.static_fields.required_modifiers = static dotnet_naming_symbols.static_fields.applicable_accessibilities = private, internal, private_protected -dotnet_naming_style.static_prefix_style.required_prefix = s_ +dotnet_naming_style.static_prefix_style.required_prefix = _ dotnet_naming_style.static_prefix_style.capitalization = camel_case # internal and private fields should be _camelCase @@ -84,7 +78,7 @@ dotnet_naming_style.camel_case_underscore_style.capitalization = camel_case # Code style defaults csharp_using_directive_placement = outside_namespace:suggestion dotnet_sort_system_directives_first = true -csharp_prefer_braces = true:silent +csharp_prefer_braces = true:suggestion csharp_preserve_single_line_blocks = true:none csharp_preserve_single_line_statements = false:none csharp_prefer_static_local_function = true:suggestion @@ -105,8 +99,8 @@ dotnet_style_prefer_is_null_check_over_reference_equality_method = true:suggesti dotnet_style_prefer_inferred_tuple_names = true:suggestion dotnet_style_prefer_inferred_anonymous_type_member_names = true:suggestion dotnet_style_prefer_auto_properties = true:suggestion -dotnet_style_prefer_conditional_expression_over_assignment = true:silent -dotnet_style_prefer_conditional_expression_over_return = true:silent +dotnet_style_prefer_conditional_expression_over_assignment = true:suggestion +dotnet_style_prefer_conditional_expression_over_return = true:suggestion csharp_prefer_simple_default_expression = true:suggestion # Expression-bodied members @@ -436,9 +430,12 @@ curly_bracket_next_line = true indent_brace_style = Allman # Xml project files -[*.{csproj,vcxproj,vcxproj.filters,proj,nativeproj,locproj}] +[*.{csproj,vbproj,vcxproj,vcxproj.filters,proj,nativeproj,locproj}] indent_size = 2 +[*.{csproj,vbproj,proj,nativeproj,locproj}] +charset = utf-8 + # Xml build files [*.builds] indent_size = 2 @@ -451,8 +448,13 @@ indent_size = 2 [*.{props,targets,config,nuspec}] indent_size = 2 +# YAML config files +[*.{yml,yaml}] +indent_size = 2 + # Shell scripts [*.sh] end_of_line = lf -[*.{cmd, bat}] + +[*.{cmd,bat}] end_of_line = crlf diff --git a/version.json b/version.json index bb2e7b696b..df72b119dc 100644 --- a/version.json +++ b/version.json @@ -1,6 +1,6 @@ { "$schema": "https://raw.githubusercontent.com/dotnet/Nerdbank.GitVersioning/master/src/NerdBank.GitVersioning/version.schema.json", - "version": "13.2", + "version": "13.3", "publicReleaseRefSpec": [ "^refs/heads/master$", // we release out of master "^refs/heads/main$", From 5e3623a529d495155a6f51a38638220342d35ddb Mon Sep 17 00:00:00 2001 From: Glenn Watson Date: Wed, 12 May 2021 12:14:06 +1000 Subject: [PATCH 4/4] Fix tests --- ...provalTests.ReactiveUI.net472.approved.txt | 26 +++++++++++++++++++ ...provalTests.ReactiveUI.net5.0.approved.txt | 26 +++++++++++++++++++ ...ests.ReactiveUI.netcoreapp3.1.approved.txt | 26 +++++++++++++++++++ src/ReactiveUI/IsExternalInit.cs | 7 ++--- 4 files changed, 82 insertions(+), 3 deletions(-) diff --git a/src/ReactiveUI.Tests/API/ApiApprovalTests.ReactiveUI.net472.approved.txt b/src/ReactiveUI.Tests/API/ApiApprovalTests.ReactiveUI.net472.approved.txt index d4a511ec6d..6d507f4cc2 100644 --- a/src/ReactiveUI.Tests/API/ApiApprovalTests.ReactiveUI.net472.approved.txt +++ b/src/ReactiveUI.Tests/API/ApiApprovalTests.ReactiveUI.net472.approved.txt @@ -631,6 +631,32 @@ namespace ReactiveUI public ReactivePropertyChangingEventArgs(TSender sender, string? propertyName) { } public TSender Sender { get; } } + [System.Runtime.Serialization.DataContract] + public class ReactiveRecord : ReactiveUI.IHandleObservableErrors, ReactiveUI.IReactiveNotifyPropertyChanged, ReactiveUI.IReactiveObject, Splat.IEnableLogger, System.ComponentModel.INotifyPropertyChanged, System.ComponentModel.INotifyPropertyChanging, System.IEquatable + { + public ReactiveRecord() { } + protected ReactiveRecord(ReactiveUI.ReactiveRecord original) { } + [System.Runtime.Serialization.IgnoreDataMember] + public System.IObservable> Changed { get; } + [System.Runtime.Serialization.IgnoreDataMember] + public System.IObservable> Changing { get; } + protected virtual System.Type EqualityContract { get; } + [System.Runtime.Serialization.IgnoreDataMember] + public System.IObservable ThrownExceptions { get; } + public event System.ComponentModel.PropertyChangedEventHandler? PropertyChanged; + public event System.ComponentModel.PropertyChangingEventHandler? PropertyChanging; + public virtual ReactiveUI.ReactiveRecord $() { } + public bool AreChangeNotificationsEnabled() { } + public System.IDisposable DelayChangeNotifications() { } + public virtual bool Equals(ReactiveUI.ReactiveRecord? other) { } + public override bool Equals(object? obj) { } + public override int GetHashCode() { } + protected virtual bool PrintMembers(System.Text.StringBuilder builder) { } + public System.IDisposable SuppressChangeNotifications() { } + public override string ToString() { } + public static bool operator !=(ReactiveUI.ReactiveRecord? r1, ReactiveUI.ReactiveRecord? r2) { } + public static bool operator ==(ReactiveUI.ReactiveRecord? r1, ReactiveUI.ReactiveRecord? r2) { } + } public static class Reflection { public static string ExpressionToPropertyNames(System.Linq.Expressions.Expression expression) { } diff --git a/src/ReactiveUI.Tests/API/ApiApprovalTests.ReactiveUI.net5.0.approved.txt b/src/ReactiveUI.Tests/API/ApiApprovalTests.ReactiveUI.net5.0.approved.txt index bd6ceac01d..57330ed16f 100644 --- a/src/ReactiveUI.Tests/API/ApiApprovalTests.ReactiveUI.net5.0.approved.txt +++ b/src/ReactiveUI.Tests/API/ApiApprovalTests.ReactiveUI.net5.0.approved.txt @@ -626,6 +626,32 @@ namespace ReactiveUI public ReactivePropertyChangingEventArgs(TSender sender, string? propertyName) { } public TSender Sender { get; } } + [System.Runtime.Serialization.DataContract] + public class ReactiveRecord : ReactiveUI.IHandleObservableErrors, ReactiveUI.IReactiveNotifyPropertyChanged, ReactiveUI.IReactiveObject, Splat.IEnableLogger, System.ComponentModel.INotifyPropertyChanged, System.ComponentModel.INotifyPropertyChanging, System.IEquatable + { + public ReactiveRecord() { } + protected ReactiveRecord(ReactiveUI.ReactiveRecord original) { } + [System.Runtime.Serialization.IgnoreDataMember] + public System.IObservable> Changed { get; } + [System.Runtime.Serialization.IgnoreDataMember] + public System.IObservable> Changing { get; } + protected virtual System.Type EqualityContract { get; } + [System.Runtime.Serialization.IgnoreDataMember] + public System.IObservable ThrownExceptions { get; } + public event System.ComponentModel.PropertyChangedEventHandler? PropertyChanged; + public event System.ComponentModel.PropertyChangingEventHandler? PropertyChanging; + public virtual ReactiveUI.ReactiveRecord $() { } + public bool AreChangeNotificationsEnabled() { } + public System.IDisposable DelayChangeNotifications() { } + public virtual bool Equals(ReactiveUI.ReactiveRecord? other) { } + public override bool Equals(object? obj) { } + public override int GetHashCode() { } + protected virtual bool PrintMembers(System.Text.StringBuilder builder) { } + public System.IDisposable SuppressChangeNotifications() { } + public override string ToString() { } + public static bool operator !=(ReactiveUI.ReactiveRecord? r1, ReactiveUI.ReactiveRecord? r2) { } + public static bool operator ==(ReactiveUI.ReactiveRecord? r1, ReactiveUI.ReactiveRecord? r2) { } + } public static class Reflection { public static string ExpressionToPropertyNames(System.Linq.Expressions.Expression expression) { } diff --git a/src/ReactiveUI.Tests/API/ApiApprovalTests.ReactiveUI.netcoreapp3.1.approved.txt b/src/ReactiveUI.Tests/API/ApiApprovalTests.ReactiveUI.netcoreapp3.1.approved.txt index 20ce1ee2ca..7b2e43d2b9 100644 --- a/src/ReactiveUI.Tests/API/ApiApprovalTests.ReactiveUI.netcoreapp3.1.approved.txt +++ b/src/ReactiveUI.Tests/API/ApiApprovalTests.ReactiveUI.netcoreapp3.1.approved.txt @@ -624,6 +624,32 @@ namespace ReactiveUI public ReactivePropertyChangingEventArgs(TSender sender, string? propertyName) { } public TSender Sender { get; } } + [System.Runtime.Serialization.DataContract] + public class ReactiveRecord : ReactiveUI.IHandleObservableErrors, ReactiveUI.IReactiveNotifyPropertyChanged, ReactiveUI.IReactiveObject, Splat.IEnableLogger, System.ComponentModel.INotifyPropertyChanged, System.ComponentModel.INotifyPropertyChanging, System.IEquatable + { + public ReactiveRecord() { } + protected ReactiveRecord(ReactiveUI.ReactiveRecord original) { } + [System.Runtime.Serialization.IgnoreDataMember] + public System.IObservable> Changed { get; } + [System.Runtime.Serialization.IgnoreDataMember] + public System.IObservable> Changing { get; } + protected virtual System.Type EqualityContract { get; } + [System.Runtime.Serialization.IgnoreDataMember] + public System.IObservable ThrownExceptions { get; } + public event System.ComponentModel.PropertyChangedEventHandler? PropertyChanged; + public event System.ComponentModel.PropertyChangingEventHandler? PropertyChanging; + public virtual ReactiveUI.ReactiveRecord $() { } + public bool AreChangeNotificationsEnabled() { } + public System.IDisposable DelayChangeNotifications() { } + public virtual bool Equals(ReactiveUI.ReactiveRecord? other) { } + public override bool Equals(object? obj) { } + public override int GetHashCode() { } + protected virtual bool PrintMembers(System.Text.StringBuilder builder) { } + public System.IDisposable SuppressChangeNotifications() { } + public override string ToString() { } + public static bool operator !=(ReactiveUI.ReactiveRecord? r1, ReactiveUI.ReactiveRecord? r2) { } + public static bool operator ==(ReactiveUI.ReactiveRecord? r1, ReactiveUI.ReactiveRecord? r2) { } + } public static class Reflection { public static string ExpressionToPropertyNames(System.Linq.Expressions.Expression expression) { } diff --git a/src/ReactiveUI/IsExternalInit.cs b/src/ReactiveUI/IsExternalInit.cs index 6124ad128c..2255ac3117 100644 --- a/src/ReactiveUI/IsExternalInit.cs +++ b/src/ReactiveUI/IsExternalInit.cs @@ -1,5 +1,6 @@ -// Copyright (c) 2019-2021 ReactiveUI Association Incorporated. All rights reserved. -// ReactiveUI Association Incorporated licenses this file to you under the MIT license. +// Copyright (c) 2021 .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 System.ComponentModel; @@ -14,4 +15,4 @@ namespace System.Runtime.CompilerServices internal static class IsExternalInit { } -} \ No newline at end of file +}