Skip to content

Commit

Permalink
feature: Add record base C# 9 (#2751)
Browse files Browse the repository at this point in the history
* Create ReactiveRecord.cs

* Add IsExternalInit

* Add version file bump

* Fix tests
  • Loading branch information
glennawatson authored May 12, 2021
1 parent 9f05e4a commit b84fb67
Show file tree
Hide file tree
Showing 7 changed files with 225 additions and 16 deletions.
32 changes: 17 additions & 15 deletions .editorconfig
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand All @@ -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
Expand All @@ -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
Expand All @@ -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
Expand All @@ -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
Expand Down Expand Up @@ -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
Expand All @@ -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
Original file line number Diff line number Diff line change
Expand Up @@ -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>, ReactiveUI.IReactiveObject, Splat.IEnableLogger, System.ComponentModel.INotifyPropertyChanged, System.ComponentModel.INotifyPropertyChanging, System.IEquatable<ReactiveUI.ReactiveRecord>
{
public ReactiveRecord() { }
protected ReactiveRecord(ReactiveUI.ReactiveRecord original) { }
[System.Runtime.Serialization.IgnoreDataMember]
public System.IObservable<ReactiveUI.IReactivePropertyChangedEventArgs<ReactiveUI.IReactiveObject>> Changed { get; }
[System.Runtime.Serialization.IgnoreDataMember]
public System.IObservable<ReactiveUI.IReactivePropertyChangedEventArgs<ReactiveUI.IReactiveObject>> Changing { get; }
protected virtual System.Type EqualityContract { get; }
[System.Runtime.Serialization.IgnoreDataMember]
public System.IObservable<System.Exception> ThrownExceptions { get; }
public event System.ComponentModel.PropertyChangedEventHandler? PropertyChanged;
public event System.ComponentModel.PropertyChangingEventHandler? PropertyChanging;
public virtual ReactiveUI.ReactiveRecord <Clone>$() { }
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) { }
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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>, ReactiveUI.IReactiveObject, Splat.IEnableLogger, System.ComponentModel.INotifyPropertyChanged, System.ComponentModel.INotifyPropertyChanging, System.IEquatable<ReactiveUI.ReactiveRecord>
{
public ReactiveRecord() { }
protected ReactiveRecord(ReactiveUI.ReactiveRecord original) { }
[System.Runtime.Serialization.IgnoreDataMember]
public System.IObservable<ReactiveUI.IReactivePropertyChangedEventArgs<ReactiveUI.IReactiveObject>> Changed { get; }
[System.Runtime.Serialization.IgnoreDataMember]
public System.IObservable<ReactiveUI.IReactivePropertyChangedEventArgs<ReactiveUI.IReactiveObject>> Changing { get; }
protected virtual System.Type EqualityContract { get; }
[System.Runtime.Serialization.IgnoreDataMember]
public System.IObservable<System.Exception> ThrownExceptions { get; }
public event System.ComponentModel.PropertyChangedEventHandler? PropertyChanged;
public event System.ComponentModel.PropertyChangingEventHandler? PropertyChanging;
public virtual ReactiveUI.ReactiveRecord <Clone>$() { }
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) { }
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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>, ReactiveUI.IReactiveObject, Splat.IEnableLogger, System.ComponentModel.INotifyPropertyChanged, System.ComponentModel.INotifyPropertyChanging, System.IEquatable<ReactiveUI.ReactiveRecord>
{
public ReactiveRecord() { }
protected ReactiveRecord(ReactiveUI.ReactiveRecord original) { }
[System.Runtime.Serialization.IgnoreDataMember]
public System.IObservable<ReactiveUI.IReactivePropertyChangedEventArgs<ReactiveUI.IReactiveObject>> Changed { get; }
[System.Runtime.Serialization.IgnoreDataMember]
public System.IObservable<ReactiveUI.IReactivePropertyChangedEventArgs<ReactiveUI.IReactiveObject>> Changing { get; }
protected virtual System.Type EqualityContract { get; }
[System.Runtime.Serialization.IgnoreDataMember]
public System.IObservable<System.Exception> ThrownExceptions { get; }
public event System.ComponentModel.PropertyChangedEventHandler? PropertyChanged;
public event System.ComponentModel.PropertyChangingEventHandler? PropertyChanging;
public virtual ReactiveUI.ReactiveRecord <Clone>$() { }
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) { }
Expand Down
18 changes: 18 additions & 0 deletions src/ReactiveUI/IsExternalInit.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
// 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;

namespace System.Runtime.CompilerServices
{
/// <summary>
/// Reserved to be used by the compiler for tracking metadata.
/// This class should not be used by developers in source code.
/// </summary>
[EditorBrowsable(EditorBrowsableState.Never)]
internal static class IsExternalInit
{
}
}
111 changes: 111 additions & 0 deletions src/ReactiveUI/ReactiveObject/ReactiveRecord.cs
Original file line number Diff line number Diff line change
@@ -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
{
/// <summary>
/// ReactiveObject is the base object for ViewModel classes, and it
/// implements INotifyPropertyChanged. In addition, ReactiveObject provides
/// Changing and Changed Observables to monitor object changes.
/// </summary>
[DataContract]
public record ReactiveRecord : IReactiveNotifyPropertyChanged<IReactiveObject>, IHandleObservableErrors, IReactiveObject
{
private readonly Lazy<IObservable<IReactivePropertyChangedEventArgs<IReactiveObject>>> _changing;
private readonly Lazy<IObservable<IReactivePropertyChangedEventArgs<IReactiveObject>>> _changed;
private readonly Lazy<Unit> _propertyChangingEventsSubscribed;
private readonly Lazy<Unit> _propertyChangedEventsSubscribed;
private readonly Lazy<IObservable<Exception>> _thrownExceptions;

/// <summary>
/// Initializes a new instance of the <see cref="ReactiveRecord"/> class.
/// </summary>
public ReactiveRecord()
{
_changing = new Lazy<IObservable<IReactivePropertyChangedEventArgs<IReactiveObject>>>(() => ((IReactiveObject)this).GetChangingObservable(), LazyThreadSafetyMode.PublicationOnly);
_changed = new Lazy<IObservable<IReactivePropertyChangedEventArgs<IReactiveObject>>>(() => ((IReactiveObject)this).GetChangedObservable(), LazyThreadSafetyMode.PublicationOnly);
_propertyChangingEventsSubscribed = new Lazy<Unit>(
() =>
{
this.SubscribePropertyChangingEvents();
return Unit.Default;
},
LazyThreadSafetyMode.PublicationOnly);
_propertyChangedEventsSubscribed = new Lazy<Unit>(
() =>
{
this.SubscribePropertyChangedEvents();
return Unit.Default;
},
LazyThreadSafetyMode.PublicationOnly);
_thrownExceptions = new Lazy<IObservable<Exception>>(this.GetThrownExceptionsObservable, LazyThreadSafetyMode.PublicationOnly);
}

/// <inheritdoc/>
public event PropertyChangingEventHandler? PropertyChanging
{
add
{
_ = _propertyChangingEventsSubscribed.Value;
PropertyChangingHandler += value;
}
remove => PropertyChangingHandler -= value;
}

/// <inheritdoc/>
public event PropertyChangedEventHandler? PropertyChanged
{
add
{
_ = _propertyChangedEventsSubscribed.Value;
PropertyChangedHandler += value;
}
remove => PropertyChangedHandler -= value;
}

private event PropertyChangingEventHandler? PropertyChangingHandler;

private event PropertyChangedEventHandler? PropertyChangedHandler;

/// <inheritdoc />
[IgnoreDataMember]
public IObservable<IReactivePropertyChangedEventArgs<IReactiveObject>> Changing => _changing.Value;

/// <inheritdoc />
[IgnoreDataMember]
public IObservable<IReactivePropertyChangedEventArgs<IReactiveObject>> Changed => _changed.Value;

/// <inheritdoc/>
[IgnoreDataMember]
public IObservable<Exception> ThrownExceptions => _thrownExceptions.Value;

/// <inheritdoc/>
void IReactiveObject.RaisePropertyChanging(PropertyChangingEventArgs args) => PropertyChangingHandler?.Invoke(this, args);

/// <inheritdoc/>
void IReactiveObject.RaisePropertyChanged(PropertyChangedEventArgs args) => PropertyChangedHandler?.Invoke(this, args);

/// <inheritdoc/>
public IDisposable SuppressChangeNotifications() => IReactiveObjectExtensions.SuppressChangeNotifications(this);

/// <summary>
/// Determines if change notifications are enabled or not.
/// </summary>
/// <returns>A value indicating whether change notifications are enabled.</returns>
public bool AreChangeNotificationsEnabled() => IReactiveObjectExtensions.AreChangeNotificationsEnabled(this);

/// <summary>
/// Delays notifications until the return IDisposable is disposed.
/// </summary>
/// <returns>A disposable which when disposed will send delayed notifications.</returns>
public IDisposable DelayChangeNotifications() => IReactiveObjectExtensions.DelayChangeNotifications(this);
}
}
2 changes: 1 addition & 1 deletion version.json
Original file line number Diff line number Diff line change
@@ -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$",
Expand Down

0 comments on commit b84fb67

Please sign in to comment.