-
Notifications
You must be signed in to change notification settings - Fork 1.4k
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Microsoft.Toolkit.Mvvm package #3229
Merged
michael-hawker
merged 256 commits into
CommunityToolkit:master
from
Sergio0694:feature/mvvm-apis
Aug 1, 2020
Merged
Changes from 250 commits
Commits
Show all changes
256 commits
Select commit
Hold shift + click to select a range
2c34963
Minor code refactoring
Sergio0694 1a72762
Added IoC.IsRegistered<TService>() API
Sergio0694 52a79f2
Minor bug fixes
Sergio0694 9dcca03
Added Ioc.GetAllRegisteredServices() API
Sergio0694 bfb8ccb
Minor code refactoring
Sergio0694 a58ecdd
Added Ioc.Unregister<TService>() API
Sergio0694 4554d3a
Added Ioc.Reset() API, minor bug fixes
Sergio0694 2cb2675
Added tests for the Ioc class
Sergio0694 f337280
Added Ioc.GetAllCreatedServices() API
Sergio0694 4f65e95
Minor code refactoring
Sergio0694 df973ea
Added Ioc.GetInstanceWithoutCaching<TService() API
Sergio0694 ce4b023
Minor code refactorings
Sergio0694 155b9de
Fixed an XML comment
Sergio0694 9d57eaf
Merge branch 'master' into feature/mvvm-apis
Sergio0694 e409d94
Fixed refactoring bugs
Sergio0694 4647330
Speed optimizations to Ioc.GetInstanceWithoutCaching<TService>()
Sergio0694 1c7cfe7
Added Ioc.TryGetInstance<TService> API
Sergio0694 6969545
Minor code refactoring
Sergio0694 5c88279
Fixed unit tests
Sergio0694 3b13c9e
Added empty Microsoft.Toolkit.Mvvm package
Sergio0694 ed5b3a1
Moved Ioc class to separate package
Sergio0694 47f1373
Added ObservableObject class
Sergio0694 bcd2674
Minor code style tweaks
Sergio0694 08dfe8a
Minor code refactorings
Sergio0694 ad613c3
Added RelayCommand type
Sergio0694 f93ae4f
Added the RelayCommand<T> type
Sergio0694 1e75231
Minor code refactoring
Sergio0694 79f4f4b
Ported minimal DictionarySlim<TKey, TValue> type from CoreFXLabs
Sergio0694 bb7db86
Added the IDictionary<in TKey> interface
Sergio0694 8963f3a
Initial draft of the Messenger class
Sergio0694 2e95d48
Added missing file headers
Sergio0694 805d940
Code tweaks and performance improvements
Sergio0694 5eacb12
Minor code style tweaks
Sergio0694 ae6ff24
Added DictionarySlim<TKey, TValue>.ContainsKey method
Sergio0694 0f7f2ac
Added Messenger.IsRegistered methods
Sergio0694 d0b110b
Added IReadOnlyDictionary<in TKey, out TValue> interface
Sergio0694 43c7202
Fixed some XML docs
Sergio0694 45c794d
Fixed a bug with new values being created when not needed
Sergio0694 6c68dcf
Renamed some interfaces to avoid naming collisions
Sergio0694 852e85e
Added Messenger.Unregister<TToken>() method
Sergio0694 3af3aff
Added more comments in the Messenger class
Sergio0694 ea68036
Added DictionarySlim<TKey, TValue>.Clear method
Sergio0694 7ab96f3
Added Messenger.Reset method
Sergio0694 6dc71d0
Added initial Messenger unit tests
Sergio0694 8c2cced
Minor code style tweaks
Sergio0694 6664609
Merge pull request #20 from Sergio0694/feature/high-perf-messenger
Sergio0694 bca38f5
Code refactoring
Sergio0694 de0d2ba
Added Messenger APIs with no input message
Sergio0694 6d22101
Added RequestMessageBase<T> type
Sergio0694 2cd0a20
Added Messenger request methods
Sergio0694 595474c
Minor code refactoring
Sergio0694 82bf497
Added PropertyChangedMessage<T> type
Sergio0694 82946f9
Added ViewModelBase class
Sergio0694 e549aef
The Messenger class is not static anymore
Sergio0694 1121fbe
Added messaging customization to ViewModelBase
Sergio0694 1e3e1e4
The Ioc class is not static anymore
Sergio0694 7396d08
Added dependency injection of service provider in ViewModelBase
Sergio0694 6fa643b
Code refactoring
Sergio0694 c40fc58
Added IIoc interface
Sergio0694 dd7e973
Added IMessenger interface
Sergio0694 fc63822
Moved request methods to extensions
Sergio0694 5b1258b
Code refactoring
Sergio0694 59a6a87
Added tests for ObservableObject
Sergio0694 245745b
Added tests for ViewModelBase
Sergio0694 fd3aba3
Added tests for relay commands
Sergio0694 c0a863b
Messenger XML comments improved
Sergio0694 2b38bac
Added ObservableObject.SetAndNotifyOnCompletion<TTask> method
Sergio0694 22aa752
Fixed a comment
Sergio0694 53982ad
Removed unnecessary using directives
Sergio0694 774ed79
Fixed an incorrect comment
Sergio0694 61248d2
Minor code tweaks
Sergio0694 58f3c64
Added AsyncRelayCommand type
Sergio0694 29daf8e
Added AsyncRelayCommand<T> type
Sergio0694 9c85a3c
Bug fixes in ObservableObject.SetAndNotifyOnCompletion
Sergio0694 61dd57d
Added ICommand<T> interface
Sergio0694 cc93648
Merge branch 'master' into feature/mvvm-apis
Sergio0694 7ea0d31
Code refactoring, removed build warnings
Sergio0694 6e90258
Code refactoring, added the IRelayCommand interface
Sergio0694 3497b58
Minor improvements and code tweaks
Sergio0694 6352082
Code refactoring, minor performance improvements
Sergio0694 a3ada63
Removed unnecessary notnull constraints
Sergio0694 05ef4fa
Minor code style tweaks
Sergio0694 0d80519
Simplified commands namespace structure
Sergio0694 900cc98
Renamed Services namespace to DependencyInjection
Sergio0694 2913e8f
Code style updates
Sergio0694 4128421
Fixed an incorrect using directive
Sergio0694 fdf01e3
Minor performance improvements to the Messenger class
Sergio0694 6bb3d4c
Minor memory improvements to the Messenger class
Sergio0694 d077563
Minor bug fixes
Sergio0694 7e623c0
Added Type2 struct
Sergio0694 1bffb04
Minor code tweaks
Sergio0694 63baa83
Fixed missing cleanup from Messenger.Unsubscribe(object)
Sergio0694 277effc
Fixed missing file headers
Sergio0694 399b10b
Minor API rename for consistency with WinUI
Sergio0694 56dfbaa
Updated project description
Sergio0694 f03dadb
Fixed an incorrect XML docs
Sergio0694 543172e
Renamed Commands folder to Input to follow namespace
Sergio0694 14e216b
Added more Messenger unregistration tests
Sergio0694 7c5928b
Improved IMessenger XML docs
Sergio0694 0bbfe9e
Added more fail tests in unit tests, improved documentation
Sergio0694 34c7c6d
Added more Messenger unit tests
Sergio0694 604cf7a
Improved internal documentation
Sergio0694 295b808
Added IAsyncRelayCommand and IAsyncRelayCommand<T> interfaces
Sergio0694 eab207c
Minor codegen improvement
Sergio0694 d5b8ece
Added nullability notation to command parameters
Sergio0694 97e7ace
Fixed an XML comment
Sergio0694 0655edb
Added nullability attributes for uncostrained T parameters
Sergio0694 9ea2e98
Improved nullability handling in commands
Sergio0694 06139b0
Added TaskExtensions class
Sergio0694 474d72e
Added tests for the TaskExtensions class
Sergio0694 c5f0b59
Added interoperability with IServiceProvider interface
Sergio0694 74cc8dc
Renamed some APIs
Sergio0694 01662e7
Code refactorings
Sergio0694 2c9ec04
Refactored Ioc class to use ASP.NET service provider
Sergio0694 36a819f
Added support for ServiceProviderOptions
Sergio0694 717fb9a
Updated project description
Sergio0694 86d3b49
Ioc optimization, ViewModelBase refactoring
Sergio0694 719e77d
Code refactoring, improved API surface
Sergio0694 dcb059f
Minor code style tweaks
Sergio0694 2f7c49e
Added Set<T> overload with target expression
Sergio0694 468929c
Added ValueChangedMessage<T> type
Sergio0694 f0b5bf0
Merge remote-tracking branch 'upstream/master' into master2
Sergio0694 6129c69
Merge remote-tracking branch 'upstream/master' into master2
Sergio0694 fdaa307
Merge remote-tracking branch 'upstream/master' into master2
Sergio0694 db41e0d
Merge remote-tracking branch 'upstream/master' into master2
Sergio0694 6b5c46b
Merge branch 'master2' into feature/mvvm-apis
Sergio0694 d1ae930
Updated multiline comments style
Sergio0694 0b36b21
Added lbugnion/mvvmlight license to ThirdPartyNotices.txt
Sergio0694 84eb856
Added note to refer to the ThirdPartyNotices.txt file
Sergio0694 a86d1c5
Fixed file headers
Sergio0694 0cc2aef
Minor code fixes
Sergio0694 5d6189e
Code refactoring to ObservableObject and ViewModelBase
Sergio0694 0de7441
Enabled ViewModelBase.IsActive broadcast
Sergio0694 fa4b2ef
Added .NET Standard 2.1 target
Sergio0694 24557f9
Added default implementations to IMessenger
Sergio0694 e0199ab
Renamed IMessengerExtensions class
Sergio0694 81c64ff
Copied the Unit type to the extensions class
Sergio0694 015913a
Added new IMessenger extensions
Sergio0694 2a75480
Removed unnecessary interface methods
Sergio0694 f0fb4cb
Removed .NET Standard 2.1 target
Sergio0694 93c653e
Removed original Unit type
Sergio0694 bcf2476
Renamed request extensions file
Sergio0694 672e861
Minor XML docs improvements
Sergio0694 72cff23
Merge branch 'dev/7.0.0' of https://github.com/windows-toolkit/Window…
Sergio0694 7ad0cd7
Merge branch 'windows-toolkit-dev/7.0.0' into feature/mvvm-apis
Sergio0694 99d8806
Moved Mvvm tests to shared project
Sergio0694 d5fbe05
Fixed merge error in 3rd party notices
Sergio0694 283af26
Updated Microsoft.Extensions.DependencyInjection package
Sergio0694 bb97787
Tweaked some XML docs
Sergio0694 65fa303
Added support for proxy fields with LINQ expression
Sergio0694 0b7d0a0
Improved value type parameters when canExecute is not used
Sergio0694 69a9ff3
Code refactoring, improved Request pattern APIs
Sergio0694 7df8a85
Minor API tweaks and comments improvements
Sergio0694 c6b4cb6
Minor bug fix
Sergio0694 121d564
Merge branch 'dev/7.0.0' into feature/mvvm-apis
Sergio0694 ec675df
Updated NuGet packages, tweaked Mvvm .csproj
Sergio0694 50a6095
Removed unnecessary attributes
Sergio0694 9a9aa6b
Added new Set<T> overloads with callback
Sergio0694 f71923e
Deprecated NotifyTaskCompletion<TResult> type
Sergio0694 6ed7b5e
Code refactoring, added TaskResultConverter
Sergio0694 8a7cd06
Added missing XML note about return type
Sergio0694 0c4669b
Moved al messenger-related functionality to ViewModelBase
Sergio0694 ceba0d4
Merge branch 'dev/7.0.0' into feature/mvvm-apis
Sergio0694 f86a79b
Minor code tweak
Sergio0694 3a10a95
Added override support for events in ObservableObject
Sergio0694 ab467ec
Fixed a typo
Sergio0694 60b1a65
Improved nullability annotations
Sergio0694 2d470ea
Added IDictionarySlim<TKey, TValue>.TryRemove API
Sergio0694 5f06cde
Added object base version of TryRemove API
Sergio0694 2876669
Added IDictionarySlim interface
Sergio0694 cf053f6
Added property to track the total handlers
Sergio0694 279a5e9
~17% speedup in Messenger.Send
Sergio0694 8f2bfe2
Removed unnecessary overload
Sergio0694 10cafd5
~88% speedup in Messenger.Send
Sergio0694 849c3dd
Merge pull request #24 from Sergio0694/optimization/faster-messenger
Sergio0694 4ece5a5
Fixed a refactoring typo
Sergio0694 bee9454
Merge branch 'master' into feature/mvvm-apis
azchohfi 6b3bbc0
Fixed merge issue
Sergio0694 e744928
Added tests for TaskExtensions class
Sergio0694 fcaa59e
Fixed a copy-paste error in filename
Sergio0694 95ac72f
Added tests for TaskResultConverter class
Sergio0694 c5669bb
Fixed build error due to [Obsolete] in a unit test
Sergio0694 8fb8a30
Renamed some APIs in RequestMessage<T>
Sergio0694 ce5132f
Merge branch 'master' into feature/mvvm-apis
Sergio0694 05a0b4e
Added AsyncRequestMessage<T> class
Sergio0694 572663f
Added CollectionRequestMessage<T> class
Sergio0694 6923f58
Added AsyncCollectionRequestMessage<T> class
Sergio0694 26e4053
Improved Messenger XML docs
Sergio0694 430ce04
Added unit tests for new request message types
Sergio0694 8a94bcb
Minor bug fixes and code refactoring
Sergio0694 e46a589
Added missing readonly modifier to Unit type
Sergio0694 53ea2f7
Fixed ambiguous match in XML comment
Sergio0694 1179ab1
Minor code style tweaks
Sergio0694 a70f278
Minor code refactoring
Sergio0694 1726279
Updated Task<T>.ResultOrDefault<T> on .NET Standard 2.1
Sergio0694 dc5398a
Merge branch 'master' into feature/mvvm-apis
Sergio0694 7b755a8
Minor tweaks to AsyncCollectionRequestMessage<T>
Sergio0694 4a1ec14
Added GetResponsesAsync API to AsyncCollectionRequestMessage<T>
Sergio0694 573bbad
Fixed a bug in the unit tests
Sergio0694 0fd19a3
Merge branch 'master' into feature/mvvm-apis
michael-hawker c6d65a2
Added ISubscriber<TMessage> interface, new extensions
Sergio0694 1cbe53c
Minor code refactoring
Sergio0694 7fa5994
Added new unit tests, minor bug fixes
Sergio0694 50b55fe
Merge branch 'feature/mvvm-apis' of https://github.com/Sergio0694/Win…
Sergio0694 34af434
Added ViewModelBase test for message unregistration
Sergio0694 53d1a9d
Added more comments to SetAndNotifyOnCompletion
Sergio0694 866dc8c
Improved remarks for ViewModeBase.OnDeactivated
Sergio0694 7da27bc
Improved tests for ISubscriber<T> APIs
Sergio0694 716d31c
Merge branch 'master' into feature/mvvm-apis
Sergio0694 f9165df
Initial code refactoring to SetAndNotifyOnCompletion
Sergio0694 904cd55
Added IsRunning property to async commands
Sergio0694 88f85ab
Added SetAndNotifyOnCompletion overload with callback
Sergio0694 b33b2fa
More code refactoring to SetAndNotifyOnCompletion
Sergio0694 f634006
Fixed an XML doc
Sergio0694 d98c257
Fixed initial IsRunning property update
Sergio0694 b91b218
Added unit tests for AsyncRelayCommand, bug fixes
Sergio0694 62c6829
Merge pull request #25 from Sergio0694/feature/extended-commands
Sergio0694 5725d45
Improved remarks for SetAndNotifyOnCompletion
Sergio0694 2a9d3e5
Improved Test_ObservableObject_Events
Sergio0694 0938168
Tweaked visibility of some test members
Sergio0694 eab9d9a
Added more tests for faulty monitored tasks
Sergio0694 0d423e8
Added more Ioc tests
Sergio0694 e4137b4
Refactored Task result extensions/converter
Sergio0694 8a5b294
Added sealed modifier to DictionarySlim<TKey, TValue>
Sergio0694 eb9d70a
Removed sealed modifier, this was a bad idea
Sergio0694 3032cb1
Renamed some APIs for consistency
Sergio0694 58805a7
Added more comments to DictionarySlim<,> type
Sergio0694 e8016f4
Fixed missing file rename for 3032cb1
Sergio0694 49d2ea1
Renamed ViewModelBase to ObservableRecipient
Sergio0694 8233d7c
Reduced thread contention in Messenger.Unregister<TToken>
Sergio0694 a671902
Fixed a bug in Messenger.Reset
Sergio0694 1123577
Minor code refactoring
Sergio0694 44451f2
Renamed some IMessenger APIs/extensions for clarity
Sergio0694 4094153
Fixed a bug in MessengerExtensions.RegisterAll
Sergio0694 a1ee70e
Minor code style tweaks
Sergio0694 053814e
Added ObservableObject.Set IEqualityComparer<T> overloads
Sergio0694 2b4e666
Major performance/memory boost in RegisterAll
Sergio0694 21e971f
Another perf boost (with compiled LINQ expressions)
Sergio0694 96a2a3f
Merge branch 'master' into feature/mvvm-apis
Sergio0694 ea0b85a
Renamed Set -> SetProperty for consistency with other libs
Sergio0694 4a1a61f
Merge branch 'master' into feature/mvvm-apis
Sergio0694 1542c0e
Removed unnecessary overhead in request messages
Sergio0694 afed416
Merge branch 'master' into feature/mvvm-apis
michael-hawker 1c15e36
Merge remote-tracking branch 'upstream/master' into feature/mvvm-apis
Sergio0694 4238e6e
Code tweaks to support #3293
Sergio0694 cd7a981
Switched property name args to string?
Sergio0694 6c8ddde
Renamed IServiceCollection arg to services
Sergio0694 445d7a5
Changed sender type to object in notification message
Sergio0694 ffefa55
Renamed Task.ResultOrDefault extensions
Sergio0694 ffcc807
Updated package description for ViewModelBase
Sergio0694 File filter
Filter by extension
Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
There are no files selected for viewing
421 changes: 421 additions & 0 deletions
421
Microsoft.Toolkit.Mvvm/ComponentModel/ObservableObject.cs
Large diffs are not rendered by default.
Oops, something went wrong.
234 changes: 234 additions & 0 deletions
234
Microsoft.Toolkit.Mvvm/ComponentModel/ObservableRecipient.cs
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,234 @@ | ||
// 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. | ||
|
||
#pragma warning disable SA1512 | ||
|
||
// This file is inspired from the MvvmLight libray (lbugnion/mvvmlight), | ||
// more info in ThirdPartyNotices.txt in the root of the project. | ||
|
||
using System; | ||
using System.Collections.Generic; | ||
using System.Runtime.CompilerServices; | ||
using Microsoft.Toolkit.Mvvm.Messaging; | ||
using Microsoft.Toolkit.Mvvm.Messaging.Messages; | ||
|
||
namespace Microsoft.Toolkit.Mvvm.ComponentModel | ||
{ | ||
/// <summary> | ||
/// A base class for observable objects that also acts as recipients for messages. This class is an extension of | ||
/// <see cref="ObservableObject"/> which also provides built-in support to use the <see cref="IMessenger"/> type. | ||
/// </summary> | ||
public abstract class ObservableRecipient : ObservableObject | ||
{ | ||
/// <summary> | ||
/// Initializes a new instance of the <see cref="ObservableRecipient"/> class. | ||
/// </summary> | ||
/// <remarks> | ||
/// This constructor will produce an instance that will use the <see cref="Messaging.Messenger.Default"/> instance | ||
/// to perform requested operations. It will also be available locally through the <see cref="Messenger"/> property. | ||
/// </remarks> | ||
protected ObservableRecipient() | ||
: this(Messaging.Messenger.Default) | ||
{ | ||
} | ||
|
||
/// <summary> | ||
/// Initializes a new instance of the <see cref="ObservableRecipient"/> class. | ||
/// </summary> | ||
/// <param name="messenger">The <see cref="IMessenger"/> instance to use to send messages.</param> | ||
protected ObservableRecipient(IMessenger messenger) | ||
{ | ||
Messenger = messenger; | ||
} | ||
|
||
/// <summary> | ||
/// Gets the <see cref="IMessenger"/> instance in use. | ||
/// </summary> | ||
protected IMessenger Messenger { get; } | ||
|
||
private bool isActive; | ||
|
||
/// <summary> | ||
/// Gets or sets a value indicating whether the current view model is currently active. | ||
/// </summary> | ||
public bool IsActive | ||
{ | ||
get => this.isActive; | ||
set | ||
{ | ||
if (SetProperty(ref this.isActive, value, true)) | ||
{ | ||
if (value) | ||
{ | ||
OnActivated(); | ||
} | ||
else | ||
{ | ||
OnDeactivated(); | ||
} | ||
} | ||
} | ||
} | ||
|
||
/// <summary> | ||
/// Raised whenever the <see cref="IsActive"/> property is set to <see langword="true"/>. | ||
/// Use this method to register to messages and do other initialization for this instance. | ||
/// </summary> | ||
/// <remarks> | ||
/// The base implementation registers all messages for this recipients that have been declared | ||
/// explicitly through the <see cref="IRecipient{TMessage}"/> interface, using the default channel. | ||
/// For more details on how this works, see the <see cref="MessengerExtensions.RegisterAll"/> method. | ||
/// If you need more fine tuned control, want to register messages individually or just prefer | ||
/// the lambda-style syntax for message registration, override this method and register manually. | ||
/// </remarks> | ||
protected virtual void OnActivated() | ||
{ | ||
Messenger.RegisterAll(this); | ||
} | ||
|
||
/// <summary> | ||
/// Raised whenever the <see cref="IsActive"/> property is set to <see langword="false"/>. | ||
/// Use this method to unregister from messages and do general cleanup for this instance. | ||
/// </summary> | ||
/// <remarks> | ||
/// The base implementation unregisters all messages for this recipient. It does so by | ||
/// invoking <see cref="IMessenger.UnregisterAll"/>, which removes all registered | ||
/// handlers for a given subscriber, regardless of what token was used to register them. | ||
/// That is, all registered handlers across all subscription channels will be removed. | ||
/// </remarks> | ||
protected virtual void OnDeactivated() | ||
{ | ||
Messenger.UnregisterAll(this); | ||
} | ||
|
||
/// <summary> | ||
/// Broadcasts a <see cref="PropertyChangedMessage{T}"/> with the specified | ||
/// parameters, without using any particular token (so using the default channel). | ||
/// </summary> | ||
/// <typeparam name="T">The type of the property that changed.</typeparam> | ||
/// <param name="oldValue">The value of the property before it changed.</param> | ||
/// <param name="newValue">The value of the property after it changed.</param> | ||
/// <param name="propertyName">The name of the property that changed.</param> | ||
/// <remarks> | ||
/// You should override this method if you wish to customize the channel being | ||
/// used to send the message (eg. if you need to use a specific token for the channel). | ||
/// </remarks> | ||
protected virtual void Broadcast<T>(T oldValue, T newValue, string propertyName) | ||
{ | ||
var message = new PropertyChangedMessage<T>(this, propertyName, oldValue, newValue); | ||
|
||
Messenger.Send(message); | ||
} | ||
|
||
/// <summary> | ||
/// Compares the current and new values for a given property. If the value has changed, | ||
/// raises the <see cref="ObservableObject.PropertyChanging"/> event, updates the property with | ||
/// the new value, then raises the <see cref="ObservableObject.PropertyChanged"/> event. | ||
/// </summary> | ||
/// <typeparam name="T">The type of the property that changed.</typeparam> | ||
/// <param name="field">The field storing the property's value.</param> | ||
/// <param name="newValue">The property's value after the change occurred.</param> | ||
/// <param name="broadcast">If <see langword="true"/>, <see cref="Broadcast{T}"/> will also be invoked.</param> | ||
/// <param name="propertyName">(optional) The name of the property that changed.</param> | ||
/// <returns><see langword="true"/> if the property was changed, <see langword="false"/> otherwise.</returns> | ||
/// <remarks> | ||
/// This method is just like <see cref="ObservableObject.SetProperty{T}(ref T,T,string)"/>, just with the addition | ||
/// of the <paramref name="broadcast"/> parameter. As such, following the behavior of the base method, | ||
/// the <see cref="ObservableObject.PropertyChanging"/> and <see cref="ObservableObject.PropertyChanged"/> events | ||
/// are not raised if the current and new value for the target property are the same. | ||
/// </remarks> | ||
protected bool SetProperty<T>(ref T field, T newValue, bool broadcast, [CallerMemberName] string propertyName = null!) | ||
{ | ||
return SetProperty(ref field, newValue, EqualityComparer<T>.Default, broadcast, propertyName); | ||
} | ||
|
||
/// <summary> | ||
/// Compares the current and new values for a given property. If the value has changed, | ||
/// raises the <see cref="ObservableObject.PropertyChanging"/> event, updates the property with | ||
/// the new value, then raises the <see cref="ObservableObject.PropertyChanged"/> event. | ||
/// See additional notes about this overload in <see cref="SetProperty{T}(ref T,T,bool,string)"/>. | ||
/// </summary> | ||
/// <typeparam name="T">The type of the property that changed.</typeparam> | ||
/// <param name="field">The field storing the property's value.</param> | ||
/// <param name="newValue">The property's value after the change occurred.</param> | ||
/// <param name="comparer">The <see cref="IEqualityComparer{T}"/> instance to use to compare the input values.</param> | ||
/// <param name="broadcast">If <see langword="true"/>, <see cref="Broadcast{T}"/> will also be invoked.</param> | ||
/// <param name="propertyName">(optional) The name of the property that changed.</param> | ||
/// <returns><see langword="true"/> if the property was changed, <see langword="false"/> otherwise.</returns> | ||
protected bool SetProperty<T>(ref T field, T newValue, IEqualityComparer<T> comparer, bool broadcast, [CallerMemberName] string propertyName = null!) | ||
{ | ||
if (!broadcast) | ||
{ | ||
return SetProperty(ref field, newValue, comparer, propertyName); | ||
} | ||
|
||
T oldValue = field; | ||
|
||
if (SetProperty(ref field, newValue, comparer, propertyName)) | ||
{ | ||
Broadcast(oldValue, newValue, propertyName); | ||
|
||
return true; | ||
} | ||
|
||
return false; | ||
} | ||
|
||
/// <summary> | ||
/// Compares the current and new values for a given property. If the value has changed, | ||
/// raises the <see cref="ObservableObject.PropertyChanging"/> event, updates the property with | ||
/// the new value, then raises the <see cref="ObservableObject.PropertyChanged"/> event. Similarly to | ||
/// the <see cref="ObservableObject.SetProperty{T}(T,T,Action{T},string)"/> method, this overload should only be | ||
/// used when <see cref="ObservableObject.SetProperty{T}(ref T,T,string)"/> can't be used directly. | ||
/// </summary> | ||
/// <typeparam name="T">The type of the property that changed.</typeparam> | ||
/// <param name="oldValue">The current property value.</param> | ||
/// <param name="newValue">The property's value after the change occurred.</param> | ||
/// <param name="callback">A callback to invoke to update the property value.</param> | ||
/// <param name="broadcast">If <see langword="true"/>, <see cref="Broadcast{T}"/> will also be invoked.</param> | ||
/// <param name="propertyName">(optional) The name of the property that changed.</param> | ||
/// <returns><see langword="true"/> if the property was changed, <see langword="false"/> otherwise.</returns> | ||
/// <remarks> | ||
/// This method is just like <see cref="ObservableObject.SetProperty{T}(T,T,Action{T},string)"/>, just with the addition | ||
/// of the <paramref name="broadcast"/> parameter. As such, following the behavior of the base method, | ||
/// the <see cref="ObservableObject.PropertyChanging"/> and <see cref="ObservableObject.PropertyChanged"/> events | ||
/// are not raised if the current and new value for the target property are the same. | ||
/// </remarks> | ||
protected bool SetProperty<T>(T oldValue, T newValue, Action<T> callback, bool broadcast, [CallerMemberName] string propertyName = null!) | ||
{ | ||
return SetProperty(oldValue, newValue, EqualityComparer<T>.Default, callback, broadcast, propertyName); | ||
} | ||
|
||
/// <summary> | ||
/// Compares the current and new values for a given property. If the value has changed, | ||
/// raises the <see cref="ObservableObject.PropertyChanging"/> event, updates the property with | ||
/// the new value, then raises the <see cref="ObservableObject.PropertyChanged"/> event. | ||
/// See additional notes about this overload in <see cref="SetProperty{T}(T,T,Action{T},bool,string)"/>. | ||
/// </summary> | ||
/// <typeparam name="T">The type of the property that changed.</typeparam> | ||
/// <param name="oldValue">The current property value.</param> | ||
/// <param name="newValue">The property's value after the change occurred.</param> | ||
/// <param name="comparer">The <see cref="IEqualityComparer{T}"/> instance to use to compare the input values.</param> | ||
/// <param name="callback">A callback to invoke to update the property value.</param> | ||
/// <param name="broadcast">If <see langword="true"/>, <see cref="Broadcast{T}"/> will also be invoked.</param> | ||
/// <param name="propertyName">(optional) The name of the property that changed.</param> | ||
/// <returns><see langword="true"/> if the property was changed, <see langword="false"/> otherwise.</returns> | ||
protected bool SetProperty<T>(T oldValue, T newValue, IEqualityComparer<T> comparer, Action<T> callback, bool broadcast, [CallerMemberName] string propertyName = null!) | ||
{ | ||
if (!broadcast) | ||
{ | ||
return SetProperty(oldValue, newValue, comparer, callback, propertyName); | ||
} | ||
|
||
if (SetProperty(oldValue, newValue, comparer, callback, propertyName)) | ||
{ | ||
Broadcast(oldValue, newValue, propertyName); | ||
|
||
return true; | ||
} | ||
|
||
return false; | ||
} | ||
} | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,145 @@ | ||
// 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.Threading; | ||
using Microsoft.Extensions.DependencyInjection; | ||
|
||
#nullable enable | ||
|
||
namespace Microsoft.Toolkit.Mvvm.DependencyInjection | ||
{ | ||
/// <summary> | ||
/// A type that facilitates the use of the <see cref="IServiceProvider"/> type. | ||
/// The <see cref="Ioc"/> provides the ability to configure services in a singleton, thread-safe | ||
/// service provider instance, which can then be used to resolve service instances. | ||
/// The first step to use this feature is to declare some services, for instance: | ||
/// <code> | ||
/// public interface ILogger | ||
/// { | ||
/// void Log(string text); | ||
/// } | ||
/// </code> | ||
/// <code> | ||
/// public class ConsoleLogger : ILogger | ||
/// { | ||
/// void Log(string text) => Console.WriteLine(text); | ||
/// } | ||
/// </code> | ||
/// Then the services configuration should then be done at startup, by calling one of | ||
/// the available <see cref="ConfigureServices(IServiceCollection)"/> overloads, like so: | ||
/// <code> | ||
/// Ioc.Default.ConfigureServices(collection => | ||
/// { | ||
/// collection.AddSingleton<ILogger, Logger>(); | ||
/// }); | ||
/// </code> | ||
/// Finally, you can use the <see cref="Ioc"/> instance (which implements <see cref="IServiceProvider"/>) | ||
/// to retrieve the service instances from anywhere in your application, by doing as follows: | ||
/// <code> | ||
/// Ioc.Default.GetService<ILogger>().Log("Hello world!"); | ||
/// </code> | ||
/// </summary> | ||
public sealed class Ioc : IServiceProvider | ||
{ | ||
/// <summary> | ||
/// Gets the default <see cref="Ioc"/> instance. | ||
/// </summary> | ||
public static Ioc Default { get; } = new Ioc(); | ||
|
||
/// <summary> | ||
/// The <see cref="ServiceProvider"/> instance to use, if initialized. | ||
/// </summary> | ||
private ServiceProvider? serviceProvider; | ||
|
||
/// <inheritdoc/> | ||
object? IServiceProvider.GetService(Type serviceType) | ||
{ | ||
// As per section I.12.6.6 of the official CLI ECMA-335 spec: | ||
// "[...] read and write access to properly aligned memory locations no larger than the native | ||
// word size is atomic when all the write accesses to a location are the same size. Atomic writes | ||
// shall alter no bits other than those written. Unless explicit layout control is used [...], | ||
// data elements no larger than the natural word size [...] shall be properly aligned. | ||
// Object references shall be treated as though they are stored in the native word size." | ||
// The field being accessed here is of native int size (reference type), and is only ever accessed | ||
// directly and atomically by a compare exhange instruction (see below), or here. We can therefore | ||
// assume this read is thread safe with respect to accesses to this property or to invocations to one | ||
// of the available configuration methods. So we can just read the field directly and make the necessary | ||
// check with our local copy, without the need of paying the locking overhead from this get accessor. | ||
ServiceProvider? provider = this.serviceProvider; | ||
|
||
if (provider is null) | ||
{ | ||
ThrowInvalidOperationExceptionForMissingInitialization(); | ||
} | ||
|
||
return provider!.GetService(serviceType); | ||
} | ||
|
||
/// <summary> | ||
/// Initializes the shared <see cref="IServiceProvider"/> instance. | ||
/// </summary> | ||
/// <param name="setup">The configuration delegate to use to add services.</param> | ||
public void ConfigureServices(Action<IServiceCollection> setup) | ||
{ | ||
ConfigureServices(setup, new ServiceProviderOptions()); | ||
} | ||
|
||
/// <summary> | ||
/// Initializes the shared <see cref="IServiceProvider"/> instance. | ||
/// </summary> | ||
/// <param name="setup">The configuration delegate to use to add services.</param> | ||
/// <param name="options">The <see cref="ServiceProviderOptions"/> instance to configure the service provider behaviors.</param> | ||
public void ConfigureServices(Action<IServiceCollection> setup, ServiceProviderOptions options) | ||
{ | ||
var collection = new ServiceCollection(); | ||
|
||
setup(collection); | ||
|
||
ConfigureServices(collection, options); | ||
} | ||
|
||
/// <summary> | ||
/// Initializes the shared <see cref="IServiceProvider"/> instance. | ||
/// </summary> | ||
/// <param name="collection">The input <see cref="IServiceCollection"/> instance to use.</param> | ||
public void ConfigureServices(IServiceCollection collection) | ||
Sergio0694 marked this conversation as resolved.
Show resolved
Hide resolved
|
||
{ | ||
ConfigureServices(collection, new ServiceProviderOptions()); | ||
} | ||
|
||
/// <summary> | ||
/// Initializes the shared <see cref="IServiceProvider"/> instance. | ||
/// </summary> | ||
/// <param name="collection">The input <see cref="IServiceCollection"/> instance to use.</param> | ||
/// <param name="options">The <see cref="ServiceProviderOptions"/> instance to configure the service provider behaviors.</param> | ||
public void ConfigureServices(IServiceCollection collection, ServiceProviderOptions options) | ||
{ | ||
ServiceProvider newServices = collection.BuildServiceProvider(options); | ||
|
||
ServiceProvider? oldServices = Interlocked.CompareExchange(ref this.serviceProvider, newServices, null); | ||
|
||
if (!(oldServices is null)) | ||
{ | ||
ThrowInvalidOperationExceptionForRepeatedConfiguration(); | ||
} | ||
} | ||
|
||
/// <summary> | ||
/// Throws an <see cref="InvalidOperationException"/> when the <see cref="ServiceProvider"/> property is used before initialization. | ||
/// </summary> | ||
private static void ThrowInvalidOperationExceptionForMissingInitialization() | ||
{ | ||
throw new InvalidOperationException("The service provider has not been configured yet"); | ||
} | ||
|
||
/// <summary> | ||
/// Throws an <see cref="InvalidOperationException"/> when a configuration is attempted more than once. | ||
/// </summary> | ||
private static void ThrowInvalidOperationExceptionForRepeatedConfiguration() | ||
{ | ||
throw new InvalidOperationException("The default service provider has already been configured"); | ||
} | ||
} | ||
} |
Oops, something went wrong.
Add this suggestion to a batch that can be applied as a single commit.
This suggestion is invalid because no changes were made to the code.
Suggestions cannot be applied while the pull request is closed.
Suggestions cannot be applied while viewing a subset of changes.
Only one suggestion per line can be applied in a batch.
Add this suggestion to a batch that can be applied as a single commit.
Applying suggestions on deleted lines is not supported.
You must change the existing code in this line in order to create a valid suggestion.
Outdated suggestions cannot be applied.
This suggestion has been applied or marked resolved.
Suggestions cannot be applied from pending reviews.
Suggestions cannot be applied on multi-line comments.
Suggestions cannot be applied while the pull request is queued to merge.
Suggestion cannot be applied right now. Please check back later.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
What's the reasoning behind this change?
"ViewModelBase" is a widely used name and its purpose is implied from its name.
The name "ObservableRecipient" makes me think it is something that will receive "observables".
This new name seems to be very much focused on the ability to receive messages. Will there be an equivalent object that can be used as a general-purpose base class for anyone not using messages? Or should they just use ObservableObject directly? If this is intended to be an alternative to ObservableObject that also broadcasts messages when properties are changed, why not a name that reflects this?
This class doesn't receive anything, but rather it sends/broadcasts messages. Were names like "BroadcastingObservableObject" also considered? Why was "ObservableRecipient" deemed the most appropriate name to use?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Hey Matt, that's a good question, and something I plan to properly document in the docs too! 😄
I made these changes after going through all the feedbacks from @deanchalk and discussing this with @michael-hawker. Basically, the idea is that the
ViewModelBase
name was itself confusing (and rightly so) for many users: the name implied it was just a "general purpose" base for viewmodels, when in fact that wasn't really the case. All the basic viewmodel functionality was already provided just byObservableObject
, andViewModelBase
was solely focused on doing proper integration with theIMessenger
type. As a result, users might've felt "forced" to use the messenger in all cases (which wasn't the intent here), or could've just looked at thatMessenger
property thinking "I don't need this, why is it here?".As a result, I've now renamed that
ObservableRecipient
, which also is more consistent with the general naming scheme: "Observable" like the other base class, and "recipient" which is the term used in all the messenger APIs. This class is now explicitly a more specialized version ofObservableObject
that is meant to be used to supportIMessenger
, providing both new overloads for theSet
method that integrate with it, as well as adding automatic setup for messages, if needed. And also, with a couple of constructors to use with DI, either automatically grabbing the sharedIMessenger
, or letting users inject their own from a DI service provider, so offer maximum flexibility.Yes, people not interested in using messages should just inherit from
ObservableObject
, that class already provides all the basic functionalities to support theINotifyProperty[Changed|Changing]
interface(s).This is not correct, this class does act as a recipient. In fact, in many cases (eg. in my experience) it often acts more as a recipient than a broadcaster (hence the name too). You can see that the base implementation of
OnActivated
(as suggested by Michael) automatically registers all the declared message handlers (through theIRecipient<TMessage>
interface), and users also have the ability to fine tune the subscription more by overriding that method. TheOnDeactivated
method also automatically unregisters all the message handlers, and these two are invoked by the setter forIsActive
. In general, this class offers ready to use support for receiving messages, first and foremost. The ability to also broadcast messages is just a bonus, also because sending messages requires less setup than receiving one.Hope this helps!
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
The terminology and lack of documentation certainly have the potential to be very confusing. As is, this has the potential to receive messages but doesn't (as far as I can tell) receive anything itself. (yet?)