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

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