Blazor Cascading Parameters don’t “just work” with lambdas or method callback

TL;DR: Blazor Cascading Parameters are matched by Type not necessarily by Name. The simplest solution is to declare the Parameter type as Delegate. If you need to distinguish more than one delegate cascading parameter, then declare a named delegate type (that's a one-liner in C#) and use that as the parameter type.

The problem

You want to pass a callback as a cascading parameter. You try passing a lambda, or possibly a method, and create a CascadingParameter property, with matching name, of type Action or Func<...> or similar, to receive it. But the cascading parameter is always set to null.

Cascading parameters are matched up by Type, not by name, and who knows what the compiled type of a lambda will be? The C# reference tells you that that lambdas can be converted to delegates or expression trees but 'can be converted to' isn't good enough for matching up by Type.

The quickest fix

Declare your parameter to be of type Delegate. Most callable things in C# are of type Delegate.

// Declaration in the child component
[CascadingParameter]public Delegate OnChange { get; set; }

// Use in the child component. The null is what DynamicInvoke requires for 'no parameters'
OnChange.DynamicInvoke(null);

# In the parent container
<CascadingValue Value="StateHasChanged" >

If that's not good enough

If you must distinguish between several Delegate parameters, or just want to better express intent in your code, you can define a named delegate:

public delegate void StateHasChangedHook();

and use that as the Type of your CascadingParameter:

// Declaration in the child component
[CascadingParameter]public StateHasChangedHook OnChange { get; set; }

// Use in the child component is slightly simpler
OnChange();

# In the parent container
<CascadingValue Value="(StateHasChangedHook)this.StateHasChanged" >

When you use named delegates, you can invoke them with onChange() syntax instead of .Invoke() or DynamicInvoke(null).

You can inspect what was set in the child parameter

log.LogDebug("OnChange was set {OnChange}.MethodInfo={MethodInfo}",OnChange,OnChange?.GetMethodInfo())

Recall that Action<> and Func<> are themselves named delegates, not types. They are declared in the System assembly as for instance: public delegate void Action<in T>(T obj)

C# nullable refs and virtual vs abstract properties

Consider:

public class InitialStep2Sqlite : ScriptMigration
{
    protected override string UpPath => "Sqlite2.Up.sql";
    protected override string DownPath => "Sqlite2.Down.sql";
}

public abstract class ScriptMigration : Migration
{
  protected virtual string UpPath {get;}
  protected virtual string DownPath {get;}

  protected override void Up(MigrationBuilder mb) 
    => mb.RunSqlFile(UpPath);
  protected override void Down(MigrationBuilder mb) 
    => mb.RunSqlFile(DownPath);
}

which, compiled under nullable enable, says “[CS8618] Non-nullable property 'UpPath' must contain a non-null value when exiting constructor. Consider declaring the property as nullable.”

Changing the property's virtual to abstact removes the warning.

The lesson: If your non-null proof of a property depends crucially on the subclass making it non-null, then making the parent property abstract not virtual is the correct statement of intent.

Generally, if a parent Member depends on the subclass for correctness – if the parent can't provide a correct implementation itself – then, logically, its correctness requires it to be abstract not virtual.

Simpson’s Paradox

Alice and Bob compete. Bob wins convincingly both times. But overall, Alice is better. How come?

This happens for real in medical trials and cases where you don't have much control over the size of groups you test on:


AliceBob
Trial 1 Score8010
–Out Of /100 /10
––Percentage => 80%=> 100%
Trial 2 Score240
–Out Of /10 /100
––Percentage=> 20%=> 40%
Total Score8250
–Total Trials /110 /110
––Percentage=> 75%=> 45%

The points to notice:

• In each trial, they got different sized groups assigned to them. There can be good reasons for this. One procedure may be known (or thought) to be better for specific circumstances so it would unethical to assign procedure based on “we want to simplify our statistical analysis” rather than on best possible outcome. Or you may simply be combining statistics about things not in your control.

• Alice's score for her largest group is better than Bob's score for his largest group, but those ‘largest groups’ were in different trials so they only ever get compared in the overall figure.

More on wikipedia: https://en.wikipedia.org/wiki/Simpson%27s_paradox.