// 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. // This file is inspired from the MvvmLight library (lbugnion/MvvmLight), // more info in ThirdPartyNotices.txt in the root of the project. using System; using System.Diagnostics.CodeAnalysis; using System.Runtime.CompilerServices; namespace CommunityToolkit.Mvvm.Input; /// /// A generic command whose sole purpose is to relay its functionality to other /// objects by invoking delegates. The default return value for the CanExecute /// method is . This class allows you to accept command parameters /// in the and callback methods. /// /// The type of parameter being passed as input to the callbacks. public sealed partial class RelayCommand : IRelayCommand { /// /// The to invoke when is used. /// private readonly Action execute; private readonly Action execute1; /// /// The optional action to invoke when is used. /// private readonly Predicate? canExecute; /// public event EventHandler? CanExecuteChanged; /// /// Initializes a new instance of the class that can always execute. /// /// The execution logic. /// /// Due to the fact that the interface exposes methods that accept a /// nullable parameter, it is recommended that if is a reference type, /// you should always declare it as nullable, and to always perform checks within . /// /// Thrown if is . public RelayCommand(Action execute) { ArgumentNullException.ThrowIfNull(execute); this.execute = execute; } public RelayCommand(Action execute) { ArgumentNullException.ThrowIfNull(execute); this.execute1 = execute; } /// /// Initializes a new instance of the class. /// /// The execution logic. /// The execution status logic. /// See notes in . /// Thrown if or are . public RelayCommand(Action execute, Predicate canExecute) { ArgumentNullException.ThrowIfNull(execute); ArgumentNullException.ThrowIfNull(canExecute); this.execute = execute; this.canExecute = canExecute; } public RelayCommand(Action execute, Predicate canExecute) { ArgumentNullException.ThrowIfNull(execute); ArgumentNullException.ThrowIfNull(canExecute); this.execute1 = execute; this.canExecute = canExecute; } /// public void NotifyCanExecuteChanged() { CanExecuteChanged?.Invoke(this, EventArgs.Empty); } /// [MethodImpl(MethodImplOptions.AggressiveInlining)] public bool CanExecute(T? parameter) { return this.canExecute?.Invoke(parameter) != false; } /// public bool CanExecute(object? parameter) { // Special case a null value for a value type argument type. // This ensures that no exceptions are thrown during initialization. if (parameter is null && default(T) is not null) { return false; } if (!TryGetCommandArgument(parameter, out T? result)) { ThrowArgumentExceptionForInvalidCommandArgument(parameter); } return CanExecute(result); } /// [MethodImpl(MethodImplOptions.AggressiveInlining)] public void Execute(object? sender,T? parameter) { if (execute1 == null) execute(parameter); else this.execute1(sender, parameter); } [MethodImpl(MethodImplOptions.AggressiveInlining)] public void Execute(T? parameter) { Execute(null, parameter); } public void Execute(object? sender, object? parameter) { if (!TryGetCommandArgument(parameter, out T? result)) { ThrowArgumentExceptionForInvalidCommandArgument(parameter); } Execute(sender,result); } public void Execute(object? parameter) { if (!TryGetCommandArgument(parameter, out T? result)) { ThrowArgumentExceptionForInvalidCommandArgument(parameter); } Execute(null, result); } /// /// Tries to get a command argument of compatible type from an input . /// /// The input parameter. /// The resulting value, if any. /// Whether or not a compatible command argument could be retrieved. [MethodImpl(MethodImplOptions.AggressiveInlining)] internal static bool TryGetCommandArgument(object? parameter, out T? result) { // If the argument is null and the default value of T is also null, then the // argument is valid. T might be a reference type or a nullable value type. if (parameter is null && default(T) is null) { result = default; return true; } // Check if the argument is a T value, so either an instance of a type or a derived // type of T is a reference type, an interface implementation if T is an interface, // or a boxed value type in case T was a value type. if (parameter is T argument) { result = argument; return true; } result = default; return false; } /// /// Throws an if an invalid command argument is used. /// /// The input parameter. /// Thrown with an error message to give info on the invalid parameter. [DoesNotReturn] internal static void ThrowArgumentExceptionForInvalidCommandArgument(object? parameter) { [MethodImpl(MethodImplOptions.NoInlining)] static Exception GetException(object? parameter) { if (parameter is null) { return new ArgumentException($"Parameter \"{nameof(parameter)}\" (object) must not be null, as the command type requires an argument of type {typeof(T)}.", nameof(parameter)); } return new ArgumentException($"Parameter \"{nameof(parameter)}\" (object) cannot be of type {parameter.GetType()}, as the command type requires an argument of type {typeof(T)}.", nameof(parameter)); } throw GetException(parameter); } }