-
Notifications
You must be signed in to change notification settings - Fork 10.5k
Open
Labels
api-suggestionEarly API idea and discussion, it is NOT ready for implementationEarly API idea and discussion, it is NOT ready for implementationarea-blazorIncludes: Blazor, Razor ComponentsIncludes: Blazor, Razor Componentscopilot-candidate
Milestone
Description
Background and Motivation
RenderFragment is currently invariant, which makes passing render fragments that accept a base class awkward when composing generic components. Marking TValue as contravariant enables passing a fragment that handles a base type to places expecting a fragment for a derived type (or a nongeneric to a generic wrapper) without manual adapters.
@code {
// Non-generic fragment that renders from the base list type
RenderFragment<IList> ItemsTemplate = (IList models) => @<div>
@foreach (var item in models.Items) { <span>@item.ToString()</span> }
</div>;
// We dynamically pick a T at runtime (e.g., via reflection/type factory)
var itemType = actualType; // e.g., typeof(Product)
var componentType = typeof(Pager<>).MakeGenericType(itemType);
// But we can’t assign Found where RenderFragment<List<T>> is expected
var parameters = new Dictionary<string, object?>
{
["ItemsTemplate"] = ItemsTemplate, // ❌ invariant delegate blocks this
};
<DynamicComponent Type="@componentType" Parameters="parameters" />
}
Proposed API
Add contravariance to TValue of RenderFragment.
public delegate RenderFragment RenderFragment<in TValue>(TValue value);
Usage Examples
The above mentioned code would run without exceptions.
Alternative Designs
The alternative is to make this adapter
// Adapter: IList -> List<T> signature
static object CreateTypedTemplate(Type itemType, RenderFragment<IList> baseTemplate)
{
// Make a delegate: RenderFragment<List<T>> (List<T> list) => baseTemplate(list)
var listType = typeof(List<>).MakeGenericType(itemType);
// Build a strongly-typed lambda via reflection
var param = System.Linq.Expressions.Expression.Parameter(listType, "list");
// Convert List<T> to IList for the base template call
var convert = System.Linq.Expressions.Expression.Convert(param, typeof(System.Collections.IList));
// baseTemplate(converted)
var invoke = System.Linq.Expressions.Expression.Invoke(
System.Linq.Expressions.Expression.Constant(baseTemplate),
convert
);
var lambdaType = typeof(RenderFragment<>).MakeGenericType(listType);
var lambda = System.Linq.Expressions.Expression.Lambda(lambdaType, invoke, param);
return lambda.Compile();
}
Risks
Breaking assumed invariance via reflection in code/libraries that assume it.
brunoAltinet
Metadata
Metadata
Assignees
Labels
api-suggestionEarly API idea and discussion, it is NOT ready for implementationEarly API idea and discussion, it is NOT ready for implementationarea-blazorIncludes: Blazor, Razor ComponentsIncludes: Blazor, Razor Componentscopilot-candidate