Skip to content

Commit b84fb67

Browse files
authored
feature: Add record base C# 9 (#2751)
* Create ReactiveRecord.cs * Add IsExternalInit * Add version file bump * Fix tests
1 parent 9f05e4a commit b84fb67

File tree

7 files changed

+225
-16
lines changed

7 files changed

+225
-16
lines changed

.editorconfig

Lines changed: 17 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -10,12 +10,11 @@ root = true
1010
insert_final_newline = true
1111
indent_style = space
1212
indent_size = 4
13-
14-
[project.json]
15-
indent_size = 2
13+
trim_trailing_whitespace = true
1614

1715
# C# files
1816
[*.cs]
17+
1918
# New line preferences
2019
csharp_new_line_before_open_brace = all
2120
csharp_new_line_before_else = true
@@ -42,11 +41,6 @@ dotnet_style_qualification_for_property = false:suggestion
4241
dotnet_style_qualification_for_method = false:suggestion
4342
dotnet_style_qualification_for_event = false:suggestion
4443

45-
# only use var when it's obvious what the variable type is
46-
csharp_style_var_for_built_in_types = false:none
47-
csharp_style_var_when_type_is_apparent = true:suggestion
48-
csharp_style_var_elsewhere = true:suggestion
49-
5044
# Types: use keywords instead of BCL types, and permit var only when the type is clear
5145
csharp_style_var_for_built_in_types = false:suggestion
5246
csharp_style_var_when_type_is_apparent = false:none
@@ -62,14 +56,14 @@ dotnet_naming_symbols.constant_fields.applicable_kinds = field
6256
dotnet_naming_symbols.constant_fields.required_modifiers = const
6357
dotnet_naming_style.pascal_case_style.capitalization = pascal_case
6458

65-
# static fields should have s_ prefix
59+
# static fields should have _ prefix
6660
dotnet_naming_rule.static_fields_should_have_prefix.severity = suggestion
6761
dotnet_naming_rule.static_fields_should_have_prefix.symbols = static_fields
6862
dotnet_naming_rule.static_fields_should_have_prefix.style = static_prefix_style
6963
dotnet_naming_symbols.static_fields.applicable_kinds = field
7064
dotnet_naming_symbols.static_fields.required_modifiers = static
7165
dotnet_naming_symbols.static_fields.applicable_accessibilities = private, internal, private_protected
72-
dotnet_naming_style.static_prefix_style.required_prefix = s_
66+
dotnet_naming_style.static_prefix_style.required_prefix = _
7367
dotnet_naming_style.static_prefix_style.capitalization = camel_case
7468

7569
# internal and private fields should be _camelCase
@@ -84,7 +78,7 @@ dotnet_naming_style.camel_case_underscore_style.capitalization = camel_case
8478
# Code style defaults
8579
csharp_using_directive_placement = outside_namespace:suggestion
8680
dotnet_sort_system_directives_first = true
87-
csharp_prefer_braces = true:silent
81+
csharp_prefer_braces = true:suggestion
8882
csharp_preserve_single_line_blocks = true:none
8983
csharp_preserve_single_line_statements = false:none
9084
csharp_prefer_static_local_function = true:suggestion
@@ -105,8 +99,8 @@ dotnet_style_prefer_is_null_check_over_reference_equality_method = true:suggesti
10599
dotnet_style_prefer_inferred_tuple_names = true:suggestion
106100
dotnet_style_prefer_inferred_anonymous_type_member_names = true:suggestion
107101
dotnet_style_prefer_auto_properties = true:suggestion
108-
dotnet_style_prefer_conditional_expression_over_assignment = true:silent
109-
dotnet_style_prefer_conditional_expression_over_return = true:silent
102+
dotnet_style_prefer_conditional_expression_over_assignment = true:suggestion
103+
dotnet_style_prefer_conditional_expression_over_return = true:suggestion
110104
csharp_prefer_simple_default_expression = true:suggestion
111105

112106
# Expression-bodied members
@@ -436,9 +430,12 @@ curly_bracket_next_line = true
436430
indent_brace_style = Allman
437431

438432
# Xml project files
439-
[*.{csproj,vcxproj,vcxproj.filters,proj,nativeproj,locproj}]
433+
[*.{csproj,vbproj,vcxproj,vcxproj.filters,proj,nativeproj,locproj}]
440434
indent_size = 2
441435

436+
[*.{csproj,vbproj,proj,nativeproj,locproj}]
437+
charset = utf-8
438+
442439
# Xml build files
443440
[*.builds]
444441
indent_size = 2
@@ -451,8 +448,13 @@ indent_size = 2
451448
[*.{props,targets,config,nuspec}]
452449
indent_size = 2
453450

451+
# YAML config files
452+
[*.{yml,yaml}]
453+
indent_size = 2
454+
454455
# Shell scripts
455456
[*.sh]
456457
end_of_line = lf
457-
[*.{cmd, bat}]
458+
459+
[*.{cmd,bat}]
458460
end_of_line = crlf

src/ReactiveUI.Tests/API/ApiApprovalTests.ReactiveUI.net472.approved.txt

Lines changed: 26 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -631,6 +631,32 @@ namespace ReactiveUI
631631
public ReactivePropertyChangingEventArgs(TSender sender, string? propertyName) { }
632632
public TSender Sender { get; }
633633
}
634+
[System.Runtime.Serialization.DataContract]
635+
public class ReactiveRecord : ReactiveUI.IHandleObservableErrors, ReactiveUI.IReactiveNotifyPropertyChanged<ReactiveUI.IReactiveObject>, ReactiveUI.IReactiveObject, Splat.IEnableLogger, System.ComponentModel.INotifyPropertyChanged, System.ComponentModel.INotifyPropertyChanging, System.IEquatable<ReactiveUI.ReactiveRecord>
636+
{
637+
public ReactiveRecord() { }
638+
protected ReactiveRecord(ReactiveUI.ReactiveRecord original) { }
639+
[System.Runtime.Serialization.IgnoreDataMember]
640+
public System.IObservable<ReactiveUI.IReactivePropertyChangedEventArgs<ReactiveUI.IReactiveObject>> Changed { get; }
641+
[System.Runtime.Serialization.IgnoreDataMember]
642+
public System.IObservable<ReactiveUI.IReactivePropertyChangedEventArgs<ReactiveUI.IReactiveObject>> Changing { get; }
643+
protected virtual System.Type EqualityContract { get; }
644+
[System.Runtime.Serialization.IgnoreDataMember]
645+
public System.IObservable<System.Exception> ThrownExceptions { get; }
646+
public event System.ComponentModel.PropertyChangedEventHandler? PropertyChanged;
647+
public event System.ComponentModel.PropertyChangingEventHandler? PropertyChanging;
648+
public virtual ReactiveUI.ReactiveRecord <Clone>$() { }
649+
public bool AreChangeNotificationsEnabled() { }
650+
public System.IDisposable DelayChangeNotifications() { }
651+
public virtual bool Equals(ReactiveUI.ReactiveRecord? other) { }
652+
public override bool Equals(object? obj) { }
653+
public override int GetHashCode() { }
654+
protected virtual bool PrintMembers(System.Text.StringBuilder builder) { }
655+
public System.IDisposable SuppressChangeNotifications() { }
656+
public override string ToString() { }
657+
public static bool operator !=(ReactiveUI.ReactiveRecord? r1, ReactiveUI.ReactiveRecord? r2) { }
658+
public static bool operator ==(ReactiveUI.ReactiveRecord? r1, ReactiveUI.ReactiveRecord? r2) { }
659+
}
634660
public static class Reflection
635661
{
636662
public static string ExpressionToPropertyNames(System.Linq.Expressions.Expression expression) { }

src/ReactiveUI.Tests/API/ApiApprovalTests.ReactiveUI.net5.0.approved.txt

Lines changed: 26 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -626,6 +626,32 @@ namespace ReactiveUI
626626
public ReactivePropertyChangingEventArgs(TSender sender, string? propertyName) { }
627627
public TSender Sender { get; }
628628
}
629+
[System.Runtime.Serialization.DataContract]
630+
public class ReactiveRecord : ReactiveUI.IHandleObservableErrors, ReactiveUI.IReactiveNotifyPropertyChanged<ReactiveUI.IReactiveObject>, ReactiveUI.IReactiveObject, Splat.IEnableLogger, System.ComponentModel.INotifyPropertyChanged, System.ComponentModel.INotifyPropertyChanging, System.IEquatable<ReactiveUI.ReactiveRecord>
631+
{
632+
public ReactiveRecord() { }
633+
protected ReactiveRecord(ReactiveUI.ReactiveRecord original) { }
634+
[System.Runtime.Serialization.IgnoreDataMember]
635+
public System.IObservable<ReactiveUI.IReactivePropertyChangedEventArgs<ReactiveUI.IReactiveObject>> Changed { get; }
636+
[System.Runtime.Serialization.IgnoreDataMember]
637+
public System.IObservable<ReactiveUI.IReactivePropertyChangedEventArgs<ReactiveUI.IReactiveObject>> Changing { get; }
638+
protected virtual System.Type EqualityContract { get; }
639+
[System.Runtime.Serialization.IgnoreDataMember]
640+
public System.IObservable<System.Exception> ThrownExceptions { get; }
641+
public event System.ComponentModel.PropertyChangedEventHandler? PropertyChanged;
642+
public event System.ComponentModel.PropertyChangingEventHandler? PropertyChanging;
643+
public virtual ReactiveUI.ReactiveRecord <Clone>$() { }
644+
public bool AreChangeNotificationsEnabled() { }
645+
public System.IDisposable DelayChangeNotifications() { }
646+
public virtual bool Equals(ReactiveUI.ReactiveRecord? other) { }
647+
public override bool Equals(object? obj) { }
648+
public override int GetHashCode() { }
649+
protected virtual bool PrintMembers(System.Text.StringBuilder builder) { }
650+
public System.IDisposable SuppressChangeNotifications() { }
651+
public override string ToString() { }
652+
public static bool operator !=(ReactiveUI.ReactiveRecord? r1, ReactiveUI.ReactiveRecord? r2) { }
653+
public static bool operator ==(ReactiveUI.ReactiveRecord? r1, ReactiveUI.ReactiveRecord? r2) { }
654+
}
629655
public static class Reflection
630656
{
631657
public static string ExpressionToPropertyNames(System.Linq.Expressions.Expression expression) { }

src/ReactiveUI.Tests/API/ApiApprovalTests.ReactiveUI.netcoreapp3.1.approved.txt

Lines changed: 26 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -624,6 +624,32 @@ namespace ReactiveUI
624624
public ReactivePropertyChangingEventArgs(TSender sender, string? propertyName) { }
625625
public TSender Sender { get; }
626626
}
627+
[System.Runtime.Serialization.DataContract]
628+
public class ReactiveRecord : ReactiveUI.IHandleObservableErrors, ReactiveUI.IReactiveNotifyPropertyChanged<ReactiveUI.IReactiveObject>, ReactiveUI.IReactiveObject, Splat.IEnableLogger, System.ComponentModel.INotifyPropertyChanged, System.ComponentModel.INotifyPropertyChanging, System.IEquatable<ReactiveUI.ReactiveRecord>
629+
{
630+
public ReactiveRecord() { }
631+
protected ReactiveRecord(ReactiveUI.ReactiveRecord original) { }
632+
[System.Runtime.Serialization.IgnoreDataMember]
633+
public System.IObservable<ReactiveUI.IReactivePropertyChangedEventArgs<ReactiveUI.IReactiveObject>> Changed { get; }
634+
[System.Runtime.Serialization.IgnoreDataMember]
635+
public System.IObservable<ReactiveUI.IReactivePropertyChangedEventArgs<ReactiveUI.IReactiveObject>> Changing { get; }
636+
protected virtual System.Type EqualityContract { get; }
637+
[System.Runtime.Serialization.IgnoreDataMember]
638+
public System.IObservable<System.Exception> ThrownExceptions { get; }
639+
public event System.ComponentModel.PropertyChangedEventHandler? PropertyChanged;
640+
public event System.ComponentModel.PropertyChangingEventHandler? PropertyChanging;
641+
public virtual ReactiveUI.ReactiveRecord <Clone>$() { }
642+
public bool AreChangeNotificationsEnabled() { }
643+
public System.IDisposable DelayChangeNotifications() { }
644+
public virtual bool Equals(ReactiveUI.ReactiveRecord? other) { }
645+
public override bool Equals(object? obj) { }
646+
public override int GetHashCode() { }
647+
protected virtual bool PrintMembers(System.Text.StringBuilder builder) { }
648+
public System.IDisposable SuppressChangeNotifications() { }
649+
public override string ToString() { }
650+
public static bool operator !=(ReactiveUI.ReactiveRecord? r1, ReactiveUI.ReactiveRecord? r2) { }
651+
public static bool operator ==(ReactiveUI.ReactiveRecord? r1, ReactiveUI.ReactiveRecord? r2) { }
652+
}
627653
public static class Reflection
628654
{
629655
public static string ExpressionToPropertyNames(System.Linq.Expressions.Expression expression) { }

src/ReactiveUI/IsExternalInit.cs

Lines changed: 18 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,18 @@
1+
// Copyright (c) 2021 .NET Foundation and Contributors. All rights reserved.
2+
// Licensed to the .NET Foundation under one or more agreements.
3+
// The .NET Foundation licenses this file to you under the MIT license.
4+
// See the LICENSE file in the project root for full license information.
5+
6+
using System.ComponentModel;
7+
8+
namespace System.Runtime.CompilerServices
9+
{
10+
/// <summary>
11+
/// Reserved to be used by the compiler for tracking metadata.
12+
/// This class should not be used by developers in source code.
13+
/// </summary>
14+
[EditorBrowsable(EditorBrowsableState.Never)]
15+
internal static class IsExternalInit
16+
{
17+
}
18+
}
Lines changed: 111 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,111 @@
1+
// Copyright (c) 2021 .NET Foundation and Contributors. All rights reserved.
2+
// Licensed to the .NET Foundation under one or more agreements.
3+
// The .NET Foundation licenses this file to you under the MIT license.
4+
// See the LICENSE file in the project root for full license information.
5+
6+
using System;
7+
using System.ComponentModel;
8+
using System.Reactive;
9+
using System.Runtime.Serialization;
10+
using System.Threading;
11+
12+
namespace ReactiveUI
13+
{
14+
/// <summary>
15+
/// ReactiveObject is the base object for ViewModel classes, and it
16+
/// implements INotifyPropertyChanged. In addition, ReactiveObject provides
17+
/// Changing and Changed Observables to monitor object changes.
18+
/// </summary>
19+
[DataContract]
20+
public record ReactiveRecord : IReactiveNotifyPropertyChanged<IReactiveObject>, IHandleObservableErrors, IReactiveObject
21+
{
22+
private readonly Lazy<IObservable<IReactivePropertyChangedEventArgs<IReactiveObject>>> _changing;
23+
private readonly Lazy<IObservable<IReactivePropertyChangedEventArgs<IReactiveObject>>> _changed;
24+
private readonly Lazy<Unit> _propertyChangingEventsSubscribed;
25+
private readonly Lazy<Unit> _propertyChangedEventsSubscribed;
26+
private readonly Lazy<IObservable<Exception>> _thrownExceptions;
27+
28+
/// <summary>
29+
/// Initializes a new instance of the <see cref="ReactiveRecord"/> class.
30+
/// </summary>
31+
public ReactiveRecord()
32+
{
33+
_changing = new Lazy<IObservable<IReactivePropertyChangedEventArgs<IReactiveObject>>>(() => ((IReactiveObject)this).GetChangingObservable(), LazyThreadSafetyMode.PublicationOnly);
34+
_changed = new Lazy<IObservable<IReactivePropertyChangedEventArgs<IReactiveObject>>>(() => ((IReactiveObject)this).GetChangedObservable(), LazyThreadSafetyMode.PublicationOnly);
35+
_propertyChangingEventsSubscribed = new Lazy<Unit>(
36+
() =>
37+
{
38+
this.SubscribePropertyChangingEvents();
39+
return Unit.Default;
40+
},
41+
LazyThreadSafetyMode.PublicationOnly);
42+
_propertyChangedEventsSubscribed = new Lazy<Unit>(
43+
() =>
44+
{
45+
this.SubscribePropertyChangedEvents();
46+
return Unit.Default;
47+
},
48+
LazyThreadSafetyMode.PublicationOnly);
49+
_thrownExceptions = new Lazy<IObservable<Exception>>(this.GetThrownExceptionsObservable, LazyThreadSafetyMode.PublicationOnly);
50+
}
51+
52+
/// <inheritdoc/>
53+
public event PropertyChangingEventHandler? PropertyChanging
54+
{
55+
add
56+
{
57+
_ = _propertyChangingEventsSubscribed.Value;
58+
PropertyChangingHandler += value;
59+
}
60+
remove => PropertyChangingHandler -= value;
61+
}
62+
63+
/// <inheritdoc/>
64+
public event PropertyChangedEventHandler? PropertyChanged
65+
{
66+
add
67+
{
68+
_ = _propertyChangedEventsSubscribed.Value;
69+
PropertyChangedHandler += value;
70+
}
71+
remove => PropertyChangedHandler -= value;
72+
}
73+
74+
private event PropertyChangingEventHandler? PropertyChangingHandler;
75+
76+
private event PropertyChangedEventHandler? PropertyChangedHandler;
77+
78+
/// <inheritdoc />
79+
[IgnoreDataMember]
80+
public IObservable<IReactivePropertyChangedEventArgs<IReactiveObject>> Changing => _changing.Value;
81+
82+
/// <inheritdoc />
83+
[IgnoreDataMember]
84+
public IObservable<IReactivePropertyChangedEventArgs<IReactiveObject>> Changed => _changed.Value;
85+
86+
/// <inheritdoc/>
87+
[IgnoreDataMember]
88+
public IObservable<Exception> ThrownExceptions => _thrownExceptions.Value;
89+
90+
/// <inheritdoc/>
91+
void IReactiveObject.RaisePropertyChanging(PropertyChangingEventArgs args) => PropertyChangingHandler?.Invoke(this, args);
92+
93+
/// <inheritdoc/>
94+
void IReactiveObject.RaisePropertyChanged(PropertyChangedEventArgs args) => PropertyChangedHandler?.Invoke(this, args);
95+
96+
/// <inheritdoc/>
97+
public IDisposable SuppressChangeNotifications() => IReactiveObjectExtensions.SuppressChangeNotifications(this);
98+
99+
/// <summary>
100+
/// Determines if change notifications are enabled or not.
101+
/// </summary>
102+
/// <returns>A value indicating whether change notifications are enabled.</returns>
103+
public bool AreChangeNotificationsEnabled() => IReactiveObjectExtensions.AreChangeNotificationsEnabled(this);
104+
105+
/// <summary>
106+
/// Delays notifications until the return IDisposable is disposed.
107+
/// </summary>
108+
/// <returns>A disposable which when disposed will send delayed notifications.</returns>
109+
public IDisposable DelayChangeNotifications() => IReactiveObjectExtensions.DelayChangeNotifications(this);
110+
}
111+
}

version.json

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
{
22
"$schema": "https://raw.githubusercontent.com/dotnet/Nerdbank.GitVersioning/master/src/NerdBank.GitVersioning/version.schema.json",
3-
"version": "13.2",
3+
"version": "13.3",
44
"publicReleaseRefSpec": [
55
"^refs/heads/master$", // we release out of master
66
"^refs/heads/main$",

0 commit comments

Comments
 (0)