如何为C#中不同业务对象实现通用的Delete函数
我明白你现在的痛点——一堆is判断不仅写起来繁琐,以后加新业务类还要不断修改DeleteItem,完全不符合开闭原则。咱们可以通过几个小步骤来重构,让这个逻辑变得通用且可扩展:
问题背景
我有几个业务类(bizMember、bizParsha和bizEarningYear),它们都继承自泛型基类bizObject<T>。每个类都有一个Delete(int id)方法用来从数据库中删除条目。
目前我写了一个DeleteItem方法,通过is关键字判断传入对象的类型,然后调用对应的Delete方法,但这种方式扩展性很差。我想要一种更通用的处理方式,能让任何继承自bizObject<T>的类传入后自动调用它的Delete方法。
当前实现代码
private async Task DeleteItem(object item) { if (item is bizEarningYear bey) { await bey.Delete(bey.EarningYearId); await _viewmodelbinder.LoadEarningYearList(); } else if (item is bizParsha bp) { await bp.Delete(bp.ParshaId); await _viewmodelbinder.LoadParshaList(); } else if (item is bizMember m) { await m.Delete(m.MemberId); await _viewmodelbinder.LoadMemberList(); } } private async void Delete_Clicked(object sender, EventArgs e) { if (sender is Button b && await PromptBeforeDelete(b.Parent.BindingContext)) { try { await DeleteItem(b.Parent.BindingContext); } catch (Exception ex) { await DisplayAlert(this.ToString(), ex.Message, "Close"); } } }
我的需求
- 去掉那些繁琐的类型检查(
is语句),换成可扩展的实现 - 写一个通用函数,能处理任何
bizObject子类,自动调用它的Delete方法
约束条件
bizMember、bizParsha、bizEarningYear都继承自bizObject<T>- 每个类都有一个
Delete(int id)方法,参数是对象自身的ID - 删除完成后必须更新
_viewmodelbinder中对应的列表
我尝试过的思路
我一开始想用泛型来实现,写了这样的代码:
private async Task DeleteItem<T>(bizObject<T> item) where T : bizObject<T>, new() { //code here }
但卡在了按钮点击事件里——当时我不知道点击对象的具体类型,没法把BindingContext转换成对应的泛型类型,所以这个方案走不通。
可行的重构方案
第一步:给基类加上统一的删除契约
首先咱们要让所有bizObject<T>的子类都遵循同一个删除规范。修改基类,新增两个抽象方法:一个用来获取对象的ID,一个是统一的删除入口(或者把现有Delete改成抽象方法):
public abstract class bizObject<T> { // 让子类返回自己的ID属性值 protected abstract int GetEntityId(); // 把Delete改成抽象方法,强制子类实现自己的删除逻辑 public abstract Task Delete(int id); // 统一的删除入口,子类不用管这个,只需要实现上面两个方法就行 public async Task DeleteAsync() { await Delete(GetEntityId()); } }
然后每个子类实现这两个方法,比如bizEarningYear:
public class bizEarningYear : bizObject<bizEarningYear> { public int EarningYearId { get; set; } protected override int GetEntityId() { // 返回自己的ID属性 return EarningYearId; } public override async Task Delete(int id) { // 这里保留你原来的删除数据库逻辑 } }
bizParsha和bizMember也是一样的逻辑,分别返回ParshaId和MemberId就行。
第二步:建立类型与列表更新方法的映射
删除后要调用对应的LoadXXXList,咱们可以用一个字典来存“类型”和“对应的列表更新方法”的映射,这样就不用写if-else了。在你的类(比如ViewModel或者页面类)的构造函数里初始化这个字典:
private readonly Dictionary<Type, Func<Task>> _listUpdateActions; // 假设这是你的类构造函数 public YourClassName() { _listUpdateActions = new Dictionary<Type, Func<Task>> { { typeof(bizEarningYear), _viewmodelbinder.LoadEarningYearList }, { typeof(bizParsha), _viewmodelbinder.LoadParshaList }, { typeof(bizMember), _viewmodelbinder.LoadMemberList } }; }
第三步:重构通用的DeleteItem方法
现在DeleteItem就可以写成完全通用的版本了,不需要任何类型检查:
private async Task DeleteItem(object item) { // 先判断是不是bizObject的子类 if (item is not bizObject<object> businessObject) { throw new ArgumentException("传入的对象必须是bizObject<T>的子类", nameof(item)); } // 调用统一的删除方法 await businessObject.DeleteAsync(); // 根据对象类型找到对应的列表更新方法并执行 if (_listUpdateActions.TryGetValue(item.GetType(), out var loadListTask)) { await loadListTask(); } else { // 可选:处理未注册的类型,比如抛出警告或者记录日志 throw new InvalidOperationException($"没有为类型{item.GetType().Name}注册列表更新方法"); } }
可选:用dynamic简化调用(如果不想改基类)
如果暂时没法修改bizObject<T>基类,也可以用dynamic来绕开类型检查,直接调用方法:
private async Task DeleteItem(object item) { dynamic dynamicItem = item; // 注意:这种方式依赖子类的ID属性名和Delete方法签名完全一致 // 比如bizEarningYear的EarningYearId,这里需要统一命名或者用反射获取ID await dynamicItem.Delete(dynamicItem.EarningYearId); // 列表更新部分和上面一样 if (_listUpdateActions.TryGetValue(item.GetType(), out var loadListTask)) { await loadListTask(); } }
不过这种方式没有类型安全保障,如果某个子类没有Delete方法或者ID属性名不对,会在运行时报错,所以还是推荐第一种修改基类的方案。
为什么这个方案更可扩展?
以后新增业务类的时候,你只需要做两件事:
- 让新类继承
bizObject<T>,实现GetEntityId和Delete方法 - 在
_listUpdateActions字典里加一行新的类型映射
完全不需要修改DeleteItem方法,完美符合开闭原则,再也不用写一堆if-else了!
备注:内容来源于stack exchange,提问作者sweetbabygang

