diff --git a/dotnet Community Toolkit.sln b/dotnet Community Toolkit.sln index e8d004357..688e32367 100644 --- a/dotnet Community Toolkit.sln +++ b/dotnet Community Toolkit.sln @@ -53,7 +53,7 @@ Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "Build", "Build", "{CD16E790 build\Update-Headers.ps1 = build\Update-Headers.ps1 EndProjectSection EndProject -Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "Configuration", "Configuration", "{6640D447-C28D-4DBB-91F4-3ADCE0CA64AD}" +Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "FeatureSwitches", "FeatureSwitches", "{6640D447-C28D-4DBB-91F4-3ADCE0CA64AD}" EndProject Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "CommunityToolkit.Mvvm.DisableINotifyPropertyChanging.UnitTests", "tests\CommunityToolkit.Mvvm.DisableINotifyPropertyChanging.UnitTests\CommunityToolkit.Mvvm.DisableINotifyPropertyChanging.UnitTests.csproj", "{9E09DA49-4389-4ECE-8B68-EBDB1221DA90}" EndProject @@ -81,7 +81,7 @@ Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "CommunityToolkit.Mvvm.Exter EndProject Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "CommunityToolkit.Mvvm.ExternalAssembly.Roslyn431", "tests\CommunityToolkit.Mvvm.ExternalAssembly.Roslyn431\CommunityToolkit.Mvvm.ExternalAssembly.Roslyn431.csproj", "{4FCD501C-1BB5-465C-AD19-356DAB6600C6}" EndProject -Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "CommunityToolkit.Mvvm.CodeFixers", "src\CommunityToolkit.Mvvm.CodeFixers\CommunityToolkit.Mvvm.CodeFixers.csproj", "{E79DCA2A-4C59-499F-85BD-F45215ED6B72}" +Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "CommunityToolkit.Mvvm.CodeFixers", "src\CommunityToolkit.Mvvm.CodeFixers\CommunityToolkit.Mvvm.CodeFixers.csproj", "{E79DCA2A-4C59-499F-85BD-F45215ED6B72}" EndProject Global GlobalSection(SolutionConfigurationPlatforms) = preSolution diff --git a/src/CommunityToolkit.Mvvm/CommunityToolkit.Mvvm.FeatureSwitches.targets b/src/CommunityToolkit.Mvvm/CommunityToolkit.Mvvm.FeatureSwitches.targets new file mode 100644 index 000000000..19c3c56c0 --- /dev/null +++ b/src/CommunityToolkit.Mvvm/CommunityToolkit.Mvvm.FeatureSwitches.targets @@ -0,0 +1,21 @@ + + + + + true + + + + + + + + + + \ No newline at end of file diff --git a/src/CommunityToolkit.Mvvm/CommunityToolkit.Mvvm.SourceGenerators.targets b/src/CommunityToolkit.Mvvm/CommunityToolkit.Mvvm.SourceGenerators.targets new file mode 100644 index 000000000..aa4b8966f --- /dev/null +++ b/src/CommunityToolkit.Mvvm/CommunityToolkit.Mvvm.SourceGenerators.targets @@ -0,0 +1,149 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + @(MVVMToolkitCurrentCompilerAssemblyIdentity->'%(Version)') + + + true + + + + + + + + + + + + + + true + + + + + + + + + roslyn4.3 + roslyn4.0 + + + + + + + + + + + + + + true + + + + + + + + + + true + + + + + + + \ No newline at end of file diff --git a/src/CommunityToolkit.Mvvm/CommunityToolkit.Mvvm.csproj b/src/CommunityToolkit.Mvvm/CommunityToolkit.Mvvm.csproj index 2c1f713e2..b543c3e5f 100644 --- a/src/CommunityToolkit.Mvvm/CommunityToolkit.Mvvm.csproj +++ b/src/CommunityToolkit.Mvvm/CommunityToolkit.Mvvm.csproj @@ -54,6 +54,11 @@ + + + + + @@ -91,11 +96,13 @@ - - - - - + + + + + + + - - - - - - - - - - - - - - - - - - - - - - - - - - - @(MVVMToolkitCurrentCompilerAssemblyIdentity->'%(Version)') - - - true - - - - - - - - - - - - - - true - - - - - - - - - roslyn4.3 - roslyn4.0 - - - - - - - - - - - - - - true - - - - - - - - - - true - - - - - + + + <_CommunityToolkitMvvmFeatureSwitchesTargets>$(MSBuildThisFileDirectory)CommunityToolkit.Mvvm.FeatureSwitches.targets + <_CommunityToolkitMvvmSourceGeneratorsTargets>$(MSBuildThisFileDirectory)CommunityToolkit.Mvvm.SourceGenerators.targets + + + + + \ No newline at end of file diff --git a/src/CommunityToolkit.Mvvm/ComponentModel/ObservableObject.cs b/src/CommunityToolkit.Mvvm/ComponentModel/ObservableObject.cs index f3252a291..895f60311 100644 --- a/src/CommunityToolkit.Mvvm/ComponentModel/ObservableObject.cs +++ b/src/CommunityToolkit.Mvvm/ComponentModel/ObservableObject.cs @@ -57,6 +57,12 @@ protected virtual void OnPropertyChanging(PropertyChangingEventArgs e) { ArgumentNullException.ThrowIfNull(e); + // When support is disabled, just do nothing + if (!FeatureSwitches.EnableINotifyPropertyChangingSupport) + { + return; + } + PropertyChanging?.Invoke(this, e); } @@ -75,7 +81,8 @@ protected void OnPropertyChanged([CallerMemberName] string? propertyName = null) /// (optional) The name of the property that changed. protected void OnPropertyChanging([CallerMemberName] string? propertyName = null) { - if (Configuration.IsINotifyPropertyChangingDisabled) + // When support is disabled, avoid instantiating the event args entirely + if (!FeatureSwitches.EnableINotifyPropertyChangingSupport) { return; } diff --git a/src/CommunityToolkit.Mvvm/Properties/Configuration.cs b/src/CommunityToolkit.Mvvm/Properties/Configuration.cs deleted file mode 100644 index d0ca0ade0..000000000 --- a/src/CommunityToolkit.Mvvm/Properties/Configuration.cs +++ /dev/null @@ -1,37 +0,0 @@ -// 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 more information. - -using System; -using System.ComponentModel; - -/// -/// A container for all shared configuration switches for the MVVM Toolkit. -/// -internal static class Configuration -{ - /// - /// The configuration property name for . - /// - private const string DisableINotifyPropertyChangingSupport = "MVVMTOOLKIT_DISABLE_INOTIFYPROPERTYCHANGING"; - - /// - /// Indicates whether or not support for is disabled. - /// - public static readonly bool IsINotifyPropertyChangingDisabled = GetConfigurationValue(DisableINotifyPropertyChangingSupport); - - /// - /// Gets a configuration value for a specified property. - /// - /// The property name to retrieve the value for. - /// The value of the specified configuration setting. - private static bool GetConfigurationValue(string propertyName) - { - if (AppContext.TryGetSwitch(propertyName, out bool isEnabled)) - { - return isEnabled; - } - - return false; - } -} diff --git a/src/CommunityToolkit.Mvvm/Properties/FeatureSwitches.cs b/src/CommunityToolkit.Mvvm/Properties/FeatureSwitches.cs new file mode 100644 index 000000000..13d9ef595 --- /dev/null +++ b/src/CommunityToolkit.Mvvm/Properties/FeatureSwitches.cs @@ -0,0 +1,81 @@ +// Copyright (c) Microsoft Corporation. +// Licensed under the MIT License. + +using System; +using System.Runtime.CompilerServices; + +namespace CommunityToolkit.Mvvm; + +/// +/// A container for all shared configuration switches for the MVVM Toolkit. +/// +/// +/// +/// This type uses a very specific setup for configuration switches to ensure ILLink can work the best. +/// This mirrors the architecture of feature switches in the runtime as well, and it's needed so that +/// no static constructor is generated for the type. +/// +/// +/// For more info, see . +/// +/// +internal static class FeatureSwitches +{ + /// + /// The configuration property name for . + /// + private const string EnableINotifyPropertyChangingSupportPropertyName = "MVVMTOOLKIT_ENABLE_INOTIFYPROPERTYCHANGING_SUPPORT"; + + /// + /// The backing field for . + /// + private static int enableINotifyPropertyChangingSupport; + + /// + /// Gets a value indicating whether or not support for should be enabled (defaults to ). + /// + public static bool EnableINotifyPropertyChangingSupport + { + [MethodImpl(MethodImplOptions.AggressiveInlining)] + get => GetConfigurationValue(EnableINotifyPropertyChangingSupportPropertyName, ref enableINotifyPropertyChangingSupport, true); + } + + /// + /// Gets a configuration value for a specified property. + /// + /// The property name to retrieve the value for. + /// The cached result for the target configuration value. + /// The default value for the feature switch, if not set. + /// The value of the specified configuration setting. + private static bool GetConfigurationValue(string propertyName, ref int cachedResult, bool defaultValue) + { + // The cached switch value has 3 states: + // 0: unknown. + // 1: true + // -1: false + // + // This method doesn't need to worry about concurrent accesses to the cached result, + // as even if the configuration value is retrieved twice, that'll always be the same. + if (cachedResult < 0) + { + return false; + } + + if (cachedResult > 0) + { + return true; + } + + // Get the configuration switch value, or its default. + // All feature switches have a default set in the .targets file. + if (!AppContext.TryGetSwitch(propertyName, out bool isEnabled)) + { + isEnabled = defaultValue; + } + + // Update the cached result + cachedResult = isEnabled ? 1 : -1; + + return isEnabled; + } +} \ No newline at end of file diff --git a/src/CommunityToolkit.Mvvm/Properties/ILLink.Substitutions.xml b/src/CommunityToolkit.Mvvm/Properties/ILLink.Substitutions.xml new file mode 100644 index 000000000..a934a249c --- /dev/null +++ b/src/CommunityToolkit.Mvvm/Properties/ILLink.Substitutions.xml @@ -0,0 +1,10 @@ + + + + + + + + + + \ No newline at end of file diff --git a/tests/CommunityToolkit.Mvvm.DisableINotifyPropertyChanging.UnitTests/Test_DisableINotifyPropertyChanging.cs b/tests/CommunityToolkit.Mvvm.DisableINotifyPropertyChanging.UnitTests/Test_DisableINotifyPropertyChanging.cs index c5fc2c4a0..d2a215455 100644 --- a/tests/CommunityToolkit.Mvvm.DisableINotifyPropertyChanging.UnitTests/Test_DisableINotifyPropertyChanging.cs +++ b/tests/CommunityToolkit.Mvvm.DisableINotifyPropertyChanging.UnitTests/Test_DisableINotifyPropertyChanging.cs @@ -14,7 +14,7 @@ public class Test_DisableINotifyPropertyChanging { static Test_DisableINotifyPropertyChanging() { - AppContext.SetSwitch("MVVMTOOLKIT_DISABLE_INOTIFYPROPERTYCHANGING", true); + AppContext.SetSwitch("MVVMTOOLKIT_ENABLE_INOTIFYPROPERTYCHANGING_SUPPORT", false); } [TestMethod]