Skip to content

Commit

Permalink
Initial version of the application
Browse files Browse the repository at this point in the history
  • Loading branch information
glennawatson committed Aug 25, 2019
1 parent a7d3190 commit 75db423
Show file tree
Hide file tree
Showing 24 changed files with 1,161 additions and 0 deletions.
32 changes: 32 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,32 @@
# Reactive Marbles Property Changed

A framework for providing an observable with the latest value of a property expression.

Will use Expression trees on platforms that support it (no iOS based platforms).

```cs
this.WhenPropertyChanges(x => x.Property1.Property2.Property3);
```

The above will generate a `IObservable<T>` where T is the type of `Property3`. It will signal each time a value has changed. It is aware of all property changes in the property chain.

# Limitations compared to ReactiveUI

At the moment it only supports `INotifyPropertyChanged` properties. More property types to come such as WPF DependencyProperty.

# Benchmark Comparisons

```ini
| Method | N | Mean | Error | StdDev | Gen 0 | Gen 1 | Gen 2 | Allocated |
|----------------------- |----- |-------------:|-----------:|-------------:|----------:|------:|------:|-----------:|
| ReactiveMarblesChanges | 1 | 80.61 us | 1.542 us | 1.515 us | 8.9111 | - | - | 13.82 KB |
| ReactiveUIChanges | 1 | 246.01 us | 19.087 us | 55.069 us | - | - | - | 20.38 KB |
| ReactiveMarblesChanges | 10 | 258.15 us | 2.114 us | 1.977 us | 27.3438 | - | - | 42.32 KB |
| ReactiveUIChanges | 10 | 581.21 us | 10.042 us | 8.902 us | 36.1328 | - | - | 55.75 KB |
| ReactiveMarblesChanges | 100 | 1,968.07 us | 28.810 us | 25.539 us | 212.8906 | - | - | 328.04 KB |
| ReactiveUIChanges | 100 | 4,754.17 us | 49.251 us | 43.659 us | 281.2500 | - | - | 437.24 KB |
| ReactiveMarblesChanges | 1000 | 19,262.79 us | 275.029 us | 257.262 us | 2062.5000 | - | - | 3180.23 KB |
| ReactiveUIChanges | 1000 | 45,432.12 us | 902.420 us | 1,843.404 us | 2000.0000 | - | - | 4232.83 KB |
```

The above benchmarks for compares ReactiveUI `WhenAnyValue` to `WhenPropertyChanges`. On every platform apart from iOS/TVOS/WatchOS the new property changes out performs ReactiveUI versions.
60 changes: 60 additions & 0 deletions azure-pipelines.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,60 @@
trigger:
- master

pool:
vmImage: 'windows-latest'

variables:
solution: '**/*.sln'
buildPlatform: 'Any CPU'
buildConfiguration: 'Release'
dotNetVersion: '3.0.100-preview8-013656'

steps:
- task: NuGetToolInstaller@0
inputs:
versionSpec: '5.2.0'

- task: DotNetCoreInstaller@0
displayName: Install Dot Net Core v$(dotNetVersion)
inputs:
version: $(dotNetVersion)

- task: NuGetCommand@2
displayName: 'NuGet restore'
inputs:
restoreSolution: '$(solution)'

- task: DotNetCoreCLI@2
inputs:
command: custom
custom: tool
arguments: install --tool-path . nbgv
displayName: Install NBGV tool
env:
COREHOST_TRACE: 0
DOTNET_SKIP_FIRST_TIME_EXPERIENCE: 1
DOTNET_CLI_TELEMETRY_OPTOUT: 1

- script: nbgv cloud
displayName: Set Version
env:
COREHOST_TRACE: 0
DOTNET_SKIP_FIRST_TIME_EXPERIENCE: 1
DOTNET_CLI_TELEMETRY_OPTOUT: 1

- task: MSBuild@1
displayName: Build Solutions
inputs:
solution: '$(solution)'
msbuildArguments: /t:build;pack /p:NoPackageAnalysis=true /p:PackageOutputPath=$(Build.ArtifactStagingDirectory)\artifacts
configuration: $(BuildConfiguration)
maximumCpuCount: true

- task: PublishBuildArtifacts@1
displayName: Publish Build Artifacts
inputs:
PathtoPublish: $(Build.ArtifactStagingDirectory)\artifacts
ArtifactName: artifacts
publishLocation: Container
condition: always()
67 changes: 67 additions & 0 deletions src/Directory.Build.props
Original file line number Diff line number Diff line change
@@ -0,0 +1,67 @@
<Project>
<PropertyGroup>
<GenerateDocumentationFile>true</GenerateDocumentationFile>
<NoWarn>$(NoWarn);1591;1701;1702;1705;VSX1000</NoWarn>
<Platform>AnyCPU</Platform>
<IsTestProject>$(MSBuildProjectName.Contains('Tests'))</IsTestProject>
<DebugType>embedded</DebugType>
<CodeAnalysisRuleSet>$(MSBuildThisFileDirectory)analyzers.ruleset</CodeAnalysisRuleSet>

<Authors>Glenn Watson</Authors>
<Copyright>Copyright (c) 2019 Glenn Watson</Copyright>
<PackageLicenseExpression>MIT</PackageLicenseExpression>
<PackageProjectUrl>https://github.com/reactivemarbles/PropertyChanged</PackageProjectUrl>
<PackageDescription>Allows to get an observables for property changed events.</PackageDescription>
<Owners>glennawatson</Owners>
<PackageTags>system.reactive;propertychanged;inpc;reactive;functional</PackageTags>
<PackageReleaseNotes>https://github.com/reactivemarbles/PropertyChanged/releases</PackageReleaseNotes>
<RepositoryUrl>https://github.com/reactivemarbles/PropertyChanged</RepositoryUrl>
<RepositoryType>git</RepositoryType>

<!-- disable sourcelink on mono, to workaround https://github.com/dotnet/sourcelink/issues/155 -->
<EnableSourceLink Condition=" '$(OS)' != 'Windows_NT' AND '$(MSBuildRuntimeType)' != 'Core' ">false</EnableSourceLink>
<EnableSourceControlManagerQueries>$(EnableSourceLink)</EnableSourceControlManagerQueries>
<!-- Publish the repository URL in the built .nupkg (in the NuSpec <Repository> element) -->
<PublishRepositoryUrl>true</PublishRepositoryUrl>
<!-- Embed source files that are not tracked by the source control manager in the PDB -->
<EmbedUntrackedSources>true</EmbedUntrackedSources>
<!-- Include PDB in the built .nupkg -->
<AllowedOutputExtensionsInPackageBuildOutputFolder>$(AllowedOutputExtensionsInPackageBuildOutputFolder);.pdb</AllowedOutputExtensionsInPackageBuildOutputFolder>
</PropertyGroup>

<ItemGroup Condition="$(IsTestProject)">
<PackageReference Include="Microsoft.NET.Test.Sdk" Version="16.2.0" />
<PackageReference Include="xunit" Version="2.4.1" />
<PackageReference Include="xunit.runner.console" Version="2.4.1" />
<PackageReference Include="xunit.runner.visualstudio" Version="2.4.1" />
<PackageReference Include="Xunit.StaFact" Version="0.3.18" />
<PackageReference Include="Shouldly" Version="4.0.0-beta0002" />
<PackageReference Include="PublicApiGenerator" Version="9.3.0" />
</ItemGroup>

<ItemGroup Condition="'$(IsTestProject)' != 'true' and '$(SourceLinkEnabled)' != 'false'">
<PackageReference Include="Microsoft.SourceLink.GitHub" Version="1.0.0-beta2-19367-01" PrivateAssets="All" />
</ItemGroup>

<PropertyGroup>
<SolutionDir Condition="'$(SolutionDir)' == ''">$(MSBuildThisFileDirectory)</SolutionDir>
</PropertyGroup>

<ItemGroup>
<None Include="$(MSBuildThisFileDirectory)..\LICENSE" Pack="true" PackagePath="LICENSE" />
</ItemGroup>

<ItemGroup>
<PackageReference Include="Nerdbank.GitVersioning" Version="3.0.25" PrivateAssets="all" />
</ItemGroup>

<ItemGroup>
<PackageReference Include="stylecop.analyzers" Version="1.1.118" PrivateAssets="all" />
<PackageReference Include="Microsoft.CodeAnalysis.FxCopAnalyzers" Version="2.6.3" PrivateAssets="all" />
<PackageReference Include="Roslynator.Analyzers" Version="2.1.0" PrivateAssets="All" />
<PackageReference Condition="'$(OS)' == 'Windows_NT'" Include="Microsoft.CodeAnalysis.FxCopAnalyzers" Version="2.9.3" PrivateAssets="all" />
</ItemGroup>
<ItemGroup>
<AdditionalFiles Include="$(MSBuildThisFileDirectory)stylecop.json" Link="stylecop.json" />
</ItemGroup>
</Project>
30 changes: 30 additions & 0 deletions src/Directory.build.targets
Original file line number Diff line number Diff line change
@@ -0,0 +1,30 @@
<Project>
<PropertyGroup>
<Product>$(AssemblyName) ($(TargetFramework))</Product>
</PropertyGroup>

<PropertyGroup Condition="$(TargetFramework.StartsWith('net4'))">
<DefineConstants>$(DefineConstants);NET_45;XAML</DefineConstants>
</PropertyGroup>
<PropertyGroup Condition="$(TargetFramework.StartsWith('uap'))">
<DefineConstants>$(DefineConstants);NETFX_CORE;XAML;WINDOWS_UWP</DefineConstants>
</PropertyGroup>
<PropertyGroup Condition="$(TargetFramework.StartsWith('Xamarin.iOS'))">
<DefineConstants>$(DefineConstants);MONO;UIKIT;COCOA</DefineConstants>
</PropertyGroup>
<PropertyGroup Condition="$(TargetFramework.StartsWith('Xamarin.Mac'))">
<DefineConstants>$(DefineConstants);MONO;COCOA</DefineConstants>
</PropertyGroup>
<PropertyGroup Condition="$(TargetFramework.StartsWith('Xamarin.TVOS'))">
<DefineConstants>$(DefineConstants);MONO;UIKIT;COCOA</DefineConstants>
</PropertyGroup>
<PropertyGroup Condition="$(TargetFramework.StartsWith('Xamarin.WatchOS'))">
<DefineConstants>$(DefineConstants);MONO;UIKIT;COCOA</DefineConstants>
</PropertyGroup>
<PropertyGroup Condition="$(TargetFramework.StartsWith('MonoAndroid'))">
<DefineConstants>$(DefineConstants);MONO;ANDROID</DefineConstants>
</PropertyGroup>
<PropertyGroup Condition="$(TargetFramework.StartsWith('tizen'))">
<DefineConstants>$(DefineConstants);TIZEN</DefineConstants>
</PropertyGroup>
</Project>
71 changes: 71 additions & 0 deletions src/PropertyChanged.sln
Original file line number Diff line number Diff line change
@@ -0,0 +1,71 @@

Microsoft Visual Studio Solution File, Format Version 12.00
# Visual Studio 15
VisualStudioVersion = 15.0.26124.0
MinimumVisualStudioVersion = 15.0.26124.0
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "ReactiveMarbles.PropertyChanged", "ReactiveMarbles.PropertyChanged\ReactiveMarbles.PropertyChanged.csproj", "{15B3E9B3-869B-4692-ADBB-A57E47C7D3DA}"
EndProject
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "ReactiveMarbles.PropertyChanged.Tests", "ReactiveMarbles.PropertyChanged.Tests\ReactiveMarbles.PropertyChanged.Tests.csproj", "{211669E0-34C1-48EF-B59C-C5271F721D38}"
EndProject
Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "Solution Items", "Solution Items", "{15316C41-0C07-4A56-894A-43E2E2C4296A}"
ProjectSection(SolutionItems) = preProject
Directory.build.targets = Directory.build.targets
Directory.Build.props = Directory.Build.props
analyzers.ruleset = analyzers.ruleset
global.json = global.json
stylecop.json = stylecop.json
EndProjectSection
EndProject
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "ReactiveMarbles.PropertyChanged.Benchmarks", "ReactiveMarbles.PropertyChanged.Benchmarks\ReactiveMarbles.PropertyChanged.Benchmarks.csproj", "{DFE06734-1998-4ED1-B0BB-75DC718347B9}"
EndProject
Global
GlobalSection(SolutionConfigurationPlatforms) = preSolution
Debug|Any CPU = Debug|Any CPU
Debug|x64 = Debug|x64
Debug|x86 = Debug|x86
Release|Any CPU = Release|Any CPU
Release|x64 = Release|x64
Release|x86 = Release|x86
EndGlobalSection
GlobalSection(SolutionProperties) = preSolution
HideSolutionNode = FALSE
EndGlobalSection
GlobalSection(ProjectConfigurationPlatforms) = postSolution
{15B3E9B3-869B-4692-ADBB-A57E47C7D3DA}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
{15B3E9B3-869B-4692-ADBB-A57E47C7D3DA}.Debug|Any CPU.Build.0 = Debug|Any CPU
{15B3E9B3-869B-4692-ADBB-A57E47C7D3DA}.Debug|x64.ActiveCfg = Debug|Any CPU
{15B3E9B3-869B-4692-ADBB-A57E47C7D3DA}.Debug|x64.Build.0 = Debug|Any CPU
{15B3E9B3-869B-4692-ADBB-A57E47C7D3DA}.Debug|x86.ActiveCfg = Debug|Any CPU
{15B3E9B3-869B-4692-ADBB-A57E47C7D3DA}.Debug|x86.Build.0 = Debug|Any CPU
{15B3E9B3-869B-4692-ADBB-A57E47C7D3DA}.Release|Any CPU.ActiveCfg = Release|Any CPU
{15B3E9B3-869B-4692-ADBB-A57E47C7D3DA}.Release|Any CPU.Build.0 = Release|Any CPU
{15B3E9B3-869B-4692-ADBB-A57E47C7D3DA}.Release|x64.ActiveCfg = Release|Any CPU
{15B3E9B3-869B-4692-ADBB-A57E47C7D3DA}.Release|x64.Build.0 = Release|Any CPU
{15B3E9B3-869B-4692-ADBB-A57E47C7D3DA}.Release|x86.ActiveCfg = Release|Any CPU
{15B3E9B3-869B-4692-ADBB-A57E47C7D3DA}.Release|x86.Build.0 = Release|Any CPU
{211669E0-34C1-48EF-B59C-C5271F721D38}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
{211669E0-34C1-48EF-B59C-C5271F721D38}.Debug|Any CPU.Build.0 = Debug|Any CPU
{211669E0-34C1-48EF-B59C-C5271F721D38}.Debug|x64.ActiveCfg = Debug|Any CPU
{211669E0-34C1-48EF-B59C-C5271F721D38}.Debug|x64.Build.0 = Debug|Any CPU
{211669E0-34C1-48EF-B59C-C5271F721D38}.Debug|x86.ActiveCfg = Debug|Any CPU
{211669E0-34C1-48EF-B59C-C5271F721D38}.Debug|x86.Build.0 = Debug|Any CPU
{211669E0-34C1-48EF-B59C-C5271F721D38}.Release|Any CPU.ActiveCfg = Release|Any CPU
{211669E0-34C1-48EF-B59C-C5271F721D38}.Release|Any CPU.Build.0 = Release|Any CPU
{211669E0-34C1-48EF-B59C-C5271F721D38}.Release|x64.ActiveCfg = Release|Any CPU
{211669E0-34C1-48EF-B59C-C5271F721D38}.Release|x64.Build.0 = Release|Any CPU
{211669E0-34C1-48EF-B59C-C5271F721D38}.Release|x86.ActiveCfg = Release|Any CPU
{211669E0-34C1-48EF-B59C-C5271F721D38}.Release|x86.Build.0 = Release|Any CPU
{DFE06734-1998-4ED1-B0BB-75DC718347B9}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
{DFE06734-1998-4ED1-B0BB-75DC718347B9}.Debug|Any CPU.Build.0 = Debug|Any CPU
{DFE06734-1998-4ED1-B0BB-75DC718347B9}.Debug|x64.ActiveCfg = Debug|Any CPU
{DFE06734-1998-4ED1-B0BB-75DC718347B9}.Debug|x64.Build.0 = Debug|Any CPU
{DFE06734-1998-4ED1-B0BB-75DC718347B9}.Debug|x86.ActiveCfg = Debug|Any CPU
{DFE06734-1998-4ED1-B0BB-75DC718347B9}.Debug|x86.Build.0 = Debug|Any CPU
{DFE06734-1998-4ED1-B0BB-75DC718347B9}.Release|Any CPU.ActiveCfg = Release|Any CPU
{DFE06734-1998-4ED1-B0BB-75DC718347B9}.Release|Any CPU.Build.0 = Release|Any CPU
{DFE06734-1998-4ED1-B0BB-75DC718347B9}.Release|x64.ActiveCfg = Release|Any CPU
{DFE06734-1998-4ED1-B0BB-75DC718347B9}.Release|x64.Build.0 = Release|Any CPU
{DFE06734-1998-4ED1-B0BB-75DC718347B9}.Release|x86.ActiveCfg = Release|Any CPU
{DFE06734-1998-4ED1-B0BB-75DC718347B9}.Release|x86.Build.0 = Release|Any CPU
EndGlobalSection
EndGlobal
17 changes: 17 additions & 0 deletions src/ReactiveMarbles.PropertyChanged.Benchmarks/Moqs/A.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,17 @@
// Copyright (c) 2019 Glenn Watson. All rights reserved.
// Glenn Watson licenses this file to you under the MIT license.
// See the LICENSE file in the project root for full license information.

namespace ReactiveMarbles.PropertyChanged.Benchmarks.Moqs
{
internal class A : BaseTestClass
{
private B _b;

public B B
{
get => _b;
set => RaiseAndSetIfChanged(ref _b, value);
}
}
}
17 changes: 17 additions & 0 deletions src/ReactiveMarbles.PropertyChanged.Benchmarks/Moqs/B.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,17 @@
// Copyright (c) 2019 Glenn Watson. All rights reserved.
// Glenn Watson licenses this file to you under the MIT license.
// See the LICENSE file in the project root for full license information.

namespace ReactiveMarbles.PropertyChanged.Benchmarks.Moqs
{
internal class B : BaseTestClass
{
private C _c;

public C C
{
get => _c;
set => RaiseAndSetIfChanged(ref _c, value);
}
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,31 @@
// Copyright (c) 2019 Glenn Watson. All rights reserved.
// Glenn Watson licenses this file to you under the MIT license.
// See the LICENSE file in the project root for full license information.

using System.Collections.Generic;
using System.ComponentModel;
using System.Runtime.CompilerServices;

namespace ReactiveMarbles.PropertyChanged.Benchmarks.Moqs
{
internal abstract class BaseTestClass : INotifyPropertyChanged
{
public event PropertyChangedEventHandler PropertyChanged;

protected void RaiseAndSetIfChanged<T>(ref T fieldValue, T value, [CallerMemberName] string propertyName = null)
{
if (EqualityComparer<T>.Default.Equals(fieldValue, value))
{
return;
}

fieldValue = value;
OnPropertyChanged(propertyName);
}

protected virtual void OnPropertyChanged(string propertyName)
{
PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(propertyName));
}
}
}
17 changes: 17 additions & 0 deletions src/ReactiveMarbles.PropertyChanged.Benchmarks/Moqs/C.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,17 @@
// Copyright (c) 2019 Glenn Watson. All rights reserved.
// Glenn Watson licenses this file to you under the MIT license.
// See the LICENSE file in the project root for full license information.

namespace ReactiveMarbles.PropertyChanged.Benchmarks.Moqs
{
internal class C : BaseTestClass
{
private string _test;

public string Test
{
get => _test;
set => RaiseAndSetIfChanged(ref _test, value);
}
}
}
24 changes: 24 additions & 0 deletions src/ReactiveMarbles.PropertyChanged.Benchmarks/Program.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,24 @@
// Copyright (c) 2019 Glenn Watson. All rights reserved.
// Glenn Watson licenses this file to you under the MIT license.
// See the LICENSE file in the project root for full license information.

using System;
using BenchmarkDotNet.Running;

namespace ReactiveMarbles.PropertyChanged.Benchmarks
{
/// <summary>
/// Class which hosts the main entry point into the application.
/// </summary>
public static class Program
{
/// <summary>
/// The main entry point into the benchmarking application.
/// </summary>
/// <param name="args">Arguments from the command line.</param>
public static void Main(string[] args)
{
BenchmarkSwitcher.FromAssembly(typeof(Program).Assembly).Run(args);
}
}
}
Loading

0 comments on commit 75db423

Please sign in to comment.