Skip to content

RelayCommand<T>.CanExecute(null) returns false for value types without a predicate #1206

Description

@apachezy

Describe the bug

RelayCommand<T>.CanExecute(object?) unconditionally returns false when T is a non-nullable value type (e.g. int, enums) and the parameter is null — even when no canExecute predicate was provided to the constructor.

This violates the documented contract: "The default return value for the CanExecute method is true." A command constructed without a predicate is meant to always be executable, regardless of the parameter value — including null.

In WPF applications on .NET Framework, the framework may call CanExecute(null) during control initialization before the CommandParameter binding has produced a value. With the unconditional return false, commands without predicates (e.g. RelayCommand<SomeEnum>(DoSomething)) are permanently disabled.

Current code (src/CommunityToolkit.Mvvm/Input/RelayCommand{T}.cs):

if (parameter is null && default(T) is not null)
{
    return false;   // unconditional — even when no predicate exists
}

Regression

No response

Steps to reproduce

// 1. Create a RelayCommand without a predicate
var command = new RelayCommand<int>(i => Console.WriteLine(i));

// 2. Call CanExecute with null (as WPF does during initialization)
bool result = ((System.Windows.Input.ICommand)command).CanExecute(null);

// 3. Observe the result
Console.WriteLine(result); // Actual: False, Expected: True

Expected behavior

CanExecute(null) should return true when no canExecute predicate was provided, because:

  1. The class documentation states the default return value is true
  2. Without a predicate, the command has no condition to evaluate — it should always be executable
  3. The null guard's purpose is to prevent a null from being incorrectly mapped to default(T) (e.g., 0 as SelectedIndex), but when there's no predicate, there's nothing to incorrectly map to

For commands with a predicate, returning false is correct — we cannot guess the user's intent. This proposal keeps that behavior unchanged.

Screenshots

No response

IDE and version

Other

IDE version

Insiders [12020.428]

Nuget packages

  • CommunityToolkit.Common
  • CommunityToolkit.Diagnostics
  • CommunityToolkit.HighPerformance
  • CommunityToolkit.Mvvm (aka MVVM Toolkit)

Nuget package version(s)

latest stable

Additional context

  • Related discussion: WindowsCommunityToolkit #3619. That issue correctly identified that mapping null to default(T) for commands with predicates is unsafe. However, the resulting unconditional return false is overly broad — it also disables commands without predicates, which have no such ambiguity.
  • The proposed fix is a one-line change: return false;return this.canExecute is null;
  • TryGetCommandArgumentThrowArgumentExceptionForInvalidCommandArgumentExecute(object?) remain unchanged.

Help us help you

Yes, I'd like to be assigned to work on this item

Activity

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

No one assigned

    Labels

    bug 🐛An unexpected issue that highlights incorrect behavior

    Type

    No type

    Projects

    No projects

      Milestone

      No milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions