Understanding CS0019: How to Compare Generic Types in C#

When attempting to write a generic helper method for INotifyPropertyChanged, using the standard inequality operator (!=) on an unconstrained generic type T triggers compiler error CS0019 (Operator '!=' cannot be applied to operands of type 'T' and 'T'). This occurs because C# cannot guarantee at compile time that type T overrides the inequality operator.

To check for inequality safely across both value types (like int or custom structs) and reference types (handling null values gracefully), use EqualityComparer<T>.Default.

protected bool SetProperty<T>(ref T backingField, T value, [CallerMemberName] string propertyName = "")
{
    if (EqualityComparer<T>.Default.Equals(backingField, value))
    {
        return false;
    }

    backingField = value;
    NotifyPropertyChanged(propertyName);
    return true;
}

Classic Boilerplate: The SetProperty Pattern

By returning a bool from your helper method, you can easily execute additional logic when a property actually changes. Here is how a custom base ViewModel class traditionally looks:

public abstract class ViewModelBase : INotifyPropertyChanged
{
    public event PropertyChangedEventHandler? PropertyChanged;

    protected void NotifyPropertyChanged([CallerMemberName] string propertyName = "")
    {
        PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(propertyName));
    }

    protected bool SetProperty<T>(ref T backingField, T value, [CallerMemberName] string propertyName = "")
    {
        if (EqualityComparer<T>.Default.Equals(backingField, value))
            return false;

        backingField = value;
        NotifyPropertyChanged(propertyName);
        return true;
    }
}

And here is how you use it inside a property setter:

private string _name = string.Empty;
public string Name
{
    get => _name;
    set => SetProperty(ref _name, value);
}

The Modern Approach: CommunityToolkit.Mvvm

If you are building modern .NET applications—especially in .NET MAUI, WPF, or WinUI 3—you generally should not write `INotifyPropertyChanged` boilerplate manually anymore. The official Microsoft solution is the CommunityToolkit.Mvvm package (formerly MVVM Light/Toolkit).

It uses C# Source Generators to eliminate backing fields and property boilerplate entirely during compilation.

1. Inheriting from ObservableObject

Using the toolkit, your base class functionality is provided by ObservableObject:

using CommunityToolkit.Mvvm.ComponentModel;

public class UserViewModel : ObservableObject
{
    private string _name = string.Empty;

    public string Name
    {
        get => _name;
        set => SetProperty(ref _name, value); // Built-in SetProperty
    }
}

2. Ultimate Syntactic Sugar: [ObservableProperty]

By applying the [ObservableProperty] attribute to a private field, the toolkit automatically generates the public property, generic inequality check, and property changed notifications for you at compile time:

using CommunityToolkit.Mvvm.ComponentModel;

public partial class UserViewModel : ObservableObject
{
    [ObservableProperty]
    private string _name;
}

The source generator converts the code above into a complete public Name property with full notification support behind the scenes.

Summary & Recommendations

  • To compare generic types safely: Use EqualityComparer<T>.Default.Equals(field, value) instead of operator !=.
  • For custom implementations: Implement the SetProperty(ref T storage, T value) pattern in your base class.
  • For modern .NET/MAUI projects: Install CommunityToolkit.Mvvm and use the [ObservableProperty] source generator attribute to keep code clean and maintainable.