Blazor服务端应用:如何简化接口继承检查与批量调用逻辑?
问题与解答:Blazor动态组件批量调用泛型接口方法
初始场景
我开发了一个Blazor服务端应用,在页面中使用DynamicComponent实现组件的运行时创建与添加。最初定义了如下接口:
public interface IGetData { Task GetDataAsync(string condition); } public interface IGetDataA : IGetData { Task GetDataAsync(CriteriaA criteria); } public interface IGetDataB : IGetData { Task GetDataAsync(CriteriaB criteria); }
在组件创建后,页面中的RefreshAsync方法分两次筛选、转换并批量调用GetDataAsync:
public async Task RefreshAsync() { var itemsA = items.Where(x => x.Ref.Instance is IGetDataA) .Select(x => x.Ref.Instance) .Cast<IGetDataA>(); await Parallel.ForEachAsync(itemsA, async (item, token) => { await item.GetDataAsync(criteriaA); }); var itemsB = items.Where(x => x.Ref.Instance is IGetDataB) .Select(x => x.Ref.Instance) .Cast<IGetDataB>(); await Parallel.ForEachAsync(itemsB, async (item, token) => { await item.GetDataAsync(criteriaB); }); }
我想知道能否将上述逻辑合并为一次转换与循环?
更新后的场景
我对接口进行了简化,改为泛型接口:
public interface IGetData<T> { Task GetDataAsync(string condition); Task GetDataAsync(T data); }
并调整了代码:
bool IsGetData(object o) => o.GetType().GetInterfaces().Any(x => x.IsGenericType && x.GetGenericTypeDefinition() == typeof(IGetData<>)); var list = items.Where(x => IsGetData(x.Ref.Instance)) .Select(x => x.Ref.Instance); await Parallel.ForEachAsync(list, async (item, token) => { if (item is ItemA) await (item as ItemA).GetDataAsync(criteriaA); else if (item is ItemB) await (item as ItemB).GetDataAsync(criteriaB); });
现在代码多了一个辅助方法,还需要用is和as判断类型,请问能否进一步简化这段代码?
简化方案
方案1:模式匹配简化类型判断
直接利用C#模式匹配完成类型转换与筛选,去掉冗余的辅助方法和as操作:
await Parallel.ForEachAsync(items, async (wrapper, token) => { var instance = wrapper.Ref.Instance; switch (instance) { case IGetData<CriteriaA> dataA: await dataA.GetDataAsync(criteriaA); break; case IGetData<CriteriaB> dataB: await dataB.GetDataAsync(criteriaB); break; // 新增IGetData<T>类型时,直接添加对应分支即可 } });
- 无需额外筛选逻辑,模式匹配自动识别实现了
IGetData<T>的实例 - 一次遍历所有items,合并了原有的两次筛选与循环
- 直接在
case中完成类型转换,避免冗余的类型判断代码
方案2:标记接口+模式匹配(更直观的类型筛选)
给泛型接口添加一个非泛型标记接口,简化类型筛选逻辑:
// 新增标记接口 public interface IGetDataMarker { } public interface IGetData<T> : IGetDataMarker { Task GetDataAsync(string condition); Task GetDataAsync(T data); }
调用代码简化为:
await Parallel.ForEachAsync(items.Where(x => x.Ref.Instance is IGetDataMarker), async (wrapper, token) => { var instance = wrapper.Ref.Instance; switch (instance) { case IGetData<CriteriaA> dataA: await dataA.GetDataAsync(criteriaA); break; case IGetData<CriteriaB> dataB: await dataB.GetDataAsync(criteriaB); break; } });
- 标记接口让类型筛选更直观,无需反射判断泛型接口
- 保持了模式匹配的简洁性,代码可读性更高
方案3:字典映射+反射(高扩展性场景)
如果后续会频繁新增IGetData<T>的实现,可以用字典维护类型与参数的映射,避免修改调用逻辑:
// 提前初始化类型-参数映射,可放在类静态构造或初始化方法中 private static readonly Dictionary<Type, object> _criteriaMap = new() { { typeof(CriteriaA), criteriaA }, { typeof(CriteriaB), criteriaB } }; // 调用逻辑 await Parallel.ForEachAsync(items, async (wrapper, token) => { var instance = wrapper.Ref.Instance; var getDataType = instance.GetType().GetInterfaces() .FirstOrDefault(x => x.IsGenericType && x.GetGenericTypeDefinition() == typeof(IGetData<>)); if (getDataType != null) { var genericArg = getDataType.GetGenericArguments()[0]; if (_criteriaMap.TryGetValue(genericArg, out var criteria)) { var method = getDataType.GetMethod(nameof(IGetData<object>.GetDataAsync), new[] { genericArg }); if (method != null) { await (Task)method.Invoke(instance, new[] { criteria }); } } } });
- 新增
IGetData<T>类型时,只需在字典中添加对应参数映射,无需修改调用逻辑 - 适合组件类型较多、扩展性要求高的场景
内容的提问来源于stack exchange,提问作者Ivan Debono
相关产品推荐
相关产品推荐

