Skip to content

Blazor Parameter Comparison ​

This document covers Fusion's optimized parameter comparison system for Blazor components: how it works, how to customize it, and when to use different comparison modes.

Overview ​

Blazor's default behavior calls SetParametersAsync on every parent render, even when parameters haven't changed. This can cause unnecessary re-renders in child components.

Fusion's FusionComponentBase introduces optimized parameter comparison that:

  • Skips unnecessary parameter updates when values haven't changed
  • Uses efficient comparers for different parameter types
  • Supports customization via attributes and configuration

ParameterComparisonMode ​

The ParameterComparisonMode enum controls how parameters are compared:

ModeDescription
InheritedUse the parent's comparison mode (default)
CustomUse custom comparers from ParameterComparerProvider
StandardAlways use Blazor's standard parameter setting

Setting the Default Mode ​

csharp
// In Program.cs or startup
FusionComponentBase.DefaultParameterComparisonMode = ParameterComparisonMode.Custom;

FusionComponentAttribute ​

Apply this attribute to a component class to specify its parameter comparison mode:

csharp
[FusionComponent(ParameterComparisonMode.Custom)]
public class MyComponent : ComputedStateComponent<MyData>
{
    [Parameter] public long Id { get; set; }
    [Parameter] public string Name { get; set; } = "";
}

Note: This attribute is not inherited. Each component that needs custom comparison must be explicitly marked.

ParameterComparer System ​

ParameterComparer Base Class ​

All comparers inherit from ParameterComparer:

csharp
public abstract class ParameterComparer
{
    public abstract bool AreEqual(object? oldValue, object? newValue);
}

DefaultParameterComparer ​

The default comparer mimics Blazor's comparison logic:

  • Primitives, strings, DateTime, Guid, Decimal: Uses .Equals() comparison
  • Enums: Uses .Equals() comparison
  • EventCallback, EventCallback<T>: Uses .Equals() comparison
  • Other types: Uses reference equality
csharp
// These use value comparison
[Parameter] public int Count { get; set; }           // Equals
[Parameter] public string Name { get; set; } = "";   // Equals
[Parameter] public DateTime Date { get; set; }       // Equals

// These use reference equality
[Parameter] public List<Item> Items { get; set; }    // Reference
[Parameter] public MyClass Data { get; set; }        // Reference

ByValueParameterComparer ​

Forces value-based comparison using .Equals() regardless of type:

csharp
[Parameter] public TimeSpan Duration { get; set; }  // Uses ByValue

Built-in Parameter Comparers ​

Fusion provides 9 built-in comparers for different scenarios:

Basic Comparers ​

ComparerDescriptionUse Case
DefaultParameterComparerMimics Blazor's default: value equality for immutable types, reference equality otherwiseDefault fallback
ByValueParameterComparerAlways uses .Equals()Value types, records
ByRefParameterComparerAlways uses ReferenceEquals()Force reference comparison
ByNoneParameterComparerAlways returns true (equal)Never trigger re-render for this parameter

Entity/Model Comparers ​

These comparers work with objects implementing specific interfaces from ActualLab:

ComparerRequired Interface(s)Compares By
ByIdParameterComparer<TId>IHasId<TId>Id property only
ByVersionParameterComparer<TVersion>IHasVersion<TVersion>Version property only
ByIdAndVersionParameterComparer<TId, TVersion>IHasId<TId>, IHasVersion<TVersion>Both Id and Version
ByUuidParameterComparerIHasUuidUuid property only
ByUuidAndVersionParameterComparer<TVersion>IHasUuid, IHasVersion<TVersion>Both Uuid and Version
ComparerSource
DefaultParameterComparerView
ByValueParameterComparerView
ByRefParameterComparerView
ByNoneParameterComparerView
ByIdParameterComparer<TId>View
ByVersionParameterComparer<TVersion>View
ByIdAndVersionParameterComparer<TId, TVersion>View
ByUuidParameterComparerView
ByUuidAndVersionParameterComparer<TVersion>View

Usage Examples ​

csharp
// Use ByNone to ignore a parameter that changes frequently but doesn't affect rendering
[Parameter]
[ParameterComparer(typeof(ByNoneParameterComparer))]
public Action? OnHover { get; set; }

// Use ById for entity parameters - re-render only when the entity ID changes
[Parameter]
[ParameterComparer(typeof(ByIdParameterComparer<long>))]
public User? User { get; set; }  // User implements IHasId<long>

// Use ByIdAndVersion for entities with optimistic concurrency
[Parameter]
[ParameterComparer(typeof(ByIdAndVersionParameterComparer<long, long>))]
public Order? Order { get; set; }  // Order implements IHasId<long>, IHasVersion<long>

// Use ByRef to force reference comparison even for types that override Equals
[Parameter]
[ParameterComparer(typeof(ByRefParameterComparer))]
public MyClass? Data { get; set; }

Types Handled by DefaultParameterComparer (Value Equality) ​

These types use .Equals() comparison in DefaultParameterComparer:

CategoryTypes
Primitivesbool, byte, sbyte, char, short, ushort, int, uint, long, ulong, float, double
Other Value Typesdecimal, DateTime, Guid
Reference Typesstring
Special TypesEnum (all enum types), EventCallback, EventCallback<T>

All other types use reference equality in DefaultParameterComparer.

Types Pre-Registered with ByValueParameterComparer ​

These types are registered in ParameterComparerProvider.KnownComparerTypes by default:

TypeNamespace
SymbolActualLab
TimeSpanSystem
MomentActualLab
DateTimeOffsetSystem
DateOnlySystem (.NET 6+)
TimeOnlySystem (.NET 6+)

Additionally, when UseByValueParameterComparerForEnumProperties is true (default), all enum types automatically use ByValueParameterComparer.

Customizing Parameter Comparison ​

Per-Property Customization ​

Use [ParameterComparer] on a property:

csharp
public class MyComponent : ComputedStateComponent<MyData>
{
    [Parameter]
    [ParameterComparer(typeof(ByValueParameterComparer))]
    public MyStruct Value { get; set; }
}

Per-Type Customization ​

Apply [ParameterComparer] to a type to use that comparer for all parameters of that type:

csharp
[ParameterComparer(typeof(MyCustomComparer))]
public record MyRecord(string Name, int Value);

// Now all parameters of type MyRecord use MyCustomComparer
public class SomeComponent : ComputedStateComponent<Data>
{
    [Parameter] public MyRecord Record { get; set; }  // Uses MyCustomComparer
}

Creating Custom Comparers ​

csharp
public class DeepEqualityComparer : ParameterComparer
{
    public static DeepEqualityComparer Instance { get; } = new();

    public override bool AreEqual(object? oldValue, object? newValue)
    {
        if (ReferenceEquals(oldValue, newValue))
            return true;

        if (oldValue is null || newValue is null)
            return false;

        // Use your deep comparison logic
        return JsonSerializer.Serialize(oldValue) == JsonSerializer.Serialize(newValue);
    }
}

Registering Known Comparers ​

Register comparers for types globally:

csharp
ParameterComparerProvider.Instance.KnownComparerTypes[typeof(MyType)]
    = typeof(MyCustomComparer);

ComponentInfo ​

ComponentInfo caches metadata about a component's parameters for efficient comparison.

How It Works ​

  1. On first use, reflects over the component's [Parameter] and [CascadingParameter] properties
  2. Caches the comparer for each parameter
  3. Provides ShouldSetParameters() to determine if parameters need updating

Key Properties ​

PropertyTypeDescription
TypeTypeThe component type
HasCustomParameterComparersboolTrue if any parameter uses custom comparison
ParameterComparisonModeParameterComparisonModeEffective comparison mode
ParametersIReadOnlyDictionary<string, ComponentParameterInfo>Parameter metadata

ComponentParameterInfo ​

Metadata about a single component parameter:

PropertyTypeDescription
PropertyPropertyInfoThe reflected property
IsCascadingboolTrue if [CascadingParameter]
IsCapturingUnmatchedValuesboolTrue if captures unmatched values
ComparerParameterComparerThe comparer for this parameter
GetterFunc<IComponent, object>Fast getter delegate
SetterAction<IComponent, object>Fast setter delegate

ParameterComparerProvider ​

The provider resolves comparers for parameters using this precedence:

  1. [ParameterComparer] attribute on the property
  2. Known comparer for the property's type (from KnownComparerTypes)
  3. [ParameterComparer] attribute on the property's type
  4. [ParameterComparer] attribute on the declaring class
  5. DefaultParameterComparer (fallback)

Configuration ​

csharp
// Access the singleton provider
var provider = ParameterComparerProvider.Instance;

// Enable/disable by-value comparison for enums (default: true)
provider.UseByValueParameterComparerForEnumProperties = true;

// Register known comparers
provider.KnownComparerTypes[typeof(MyStruct)] = typeof(ByValueParameterComparer);

Best Practices ​

When to Use Custom Comparison ​

ScenarioRecommendation
Primitive typesUse default (already optimized)
Records / value typesConsider ByValueParameterComparer
Large collectionsKeep reference equality (avoid deep comparison)
Immutable objectsUse reference equality
Mutable objects with equalityUse custom comparer

Performance Considerations ​

  1. Avoid expensive comparisons: Deep equality checks can be slower than re-rendering
  2. Reference equality is fast: For large objects, reference equality is often best
  3. Cache comparers: Custom comparers are cached per property type

Example: Optimizing a Data Grid Component ​

csharp
[FusionComponent(ParameterComparisonMode.Custom)]
public class DataGrid<T> : ComputedStateComponent<GridState<T>>
{
    // Use reference equality for large collections
    [Parameter]
    public IReadOnlyList<T> Items { get; set; } = Array.Empty<T>();

    // Use value comparison for small config objects
    [Parameter]
    [ParameterComparer(typeof(ByValueParameterComparer))]
    public GridOptions Options { get; set; } = GridOptions.Default;

    // Primitives already use value comparison
    [Parameter]
    public int PageSize { get; set; } = 20;
}

Debugging Parameter Changes ​

To understand when parameters are being set:

csharp
public class MyComponent : ComputedStateComponent<MyData>
{
    public override async Task SetParametersAsync(ParameterView parameters)
    {
        Console.WriteLine($"SetParametersAsync called, ParameterSetIndex: {ParameterSetIndex}");
        await base.SetParametersAsync(parameters);
    }
}

The ParameterSetIndex property tracks how many times SetParametersAsync has been called (0 = not yet initialized).