C#泛型抽象类Table<T>的Func参数类型安全改造咨询
问题
现有如下抽象类:
public abstract class Table<T> { public ReadOnlyCollection<T> list; protected abstract Func<T, object[], bool> Func { get; } protected T Find(params object[] objects) => list.First(element => Func(element, objects)); }
每个派生类必须实现抽象的Func<T, object[], bool>,例如针对以下实体类:
public class Person { public string FirstName; } public class Person2 { public string FirstName; public string LastName; } public class Employee { public int ID; }
对应的派生类实现如下:
public class TablePerson : Table<Person> { protected override Func<Person, object[], bool> Func => (p, obj) => p.FirstName == (string)obj[0]; public Person GetPerson(string firstName) => Find(firstName); } public class TablePerson2 : Table<Person2> { protected override Func<Person2, object[], bool> Func => (p, obj) => p.FirstName == (string)obj[0] && p.LastName == (string)obj[1]; public Person2 GetPerson2(string firstName, string lastName) => Find(firstName, lastName); } public class TableEmployee : Table<Employee> { protected override Func<Employee, object[], bool> Func => (p, obj) => p.ID == (int)obj[0]; public Employee GetEmployee(int id) => Find(id); }
需求是将Func<T, object[], bool>的object[]参数改为具备类型感知的形式,同时需要评估当前实现方式的合理性。
解决方案
方案1:为Table<T>新增查询参数泛型
通过引入泛型参数指定查询参数类型,让委托完全强类型化,彻底避免类型转换问题:
// 抽象类新增泛型参数Q,代表查询参数的类型 public abstract class Table<T, Q> { public ReadOnlyCollection<T> list; // 强类型匹配委托 protected abstract Func<T, Q, bool> MatchFunc { get; } // 强类型查询方法 protected T Find(Q queryParams) => list.First(element => MatchFunc(element, queryParams)); } // Person表:用string作为查询参数 public class TablePerson : Table<Person, string> { protected override Func<Person, string, bool> MatchFunc => (p, firstName) => p.FirstName == firstName; public Person GetPerson(string firstName) => Find(firstName); } // Person2表:自定义查询参数类 public class Person2Query { public string FirstName { get; set; } public string LastName { get; set; } } public class TablePerson2 : Table<Person2, Person2Query> { protected override Func<Person2, Person2Query, bool> MatchFunc => (p, query) => p.FirstName == query.FirstName && p.LastName == query.LastName; public Person2 GetPerson2(string firstName, string lastName) => Find(new Person2Query { FirstName = firstName, LastName = lastName }); } // Employee表:用int作为查询参数 public class TableEmployee : Table<Employee, int> { protected override Func<Employee, int, bool> MatchFunc => (p, id) => p.ID == id; public Employee GetEmployee(int id) => Find(id); }
该方案的优势:编译期就能检查类型错误,无需强制转换,代码可读性和维护性大幅提升,同时避免值类型装箱的性能损耗。
方案2:利用元组简化多参数查询
如果不想定义额外的查询类,可以用C#元组作为查询参数,兼顾类型安全和简洁性:
public class TablePerson2 : Table<Person2, (string FirstName, string LastName)> { protected override Func<Person2, (string, string), bool> MatchFunc => (p, query) => p.FirstName == query.FirstName && p.LastName == query.LastName; public Person2 GetPerson2(string firstName, string lastName) => Find((firstName, lastName)); }
元组自带命名属性,无需额外定义类,适合参数数量较少的场景。
方案3:表达式树支持复杂查询
如果需要更灵活的动态查询逻辑,可以用Expression<Func<T, bool>>代替委托,让派生类构建强类型表达式:
public abstract class Table<T> { public ReadOnlyCollection<T> list; // 根据参数构建强类型查询表达式 protected abstract Expression<Func<T, bool>> BuildQuery(params object[] args); protected T Find(params object[] args) { var predicate = BuildQuery(args).Compile(); return list.First(predicate); } } public class TablePerson : Table<Person> { protected override Expression<Func<Person, bool>> BuildQuery(params object[] args) { string firstName = (string)args[0]; return p => p.FirstName == firstName; } public Person GetPerson(string firstName) => Find(firstName); }
该方案适合需要动态拼接查询条件的场景,虽然仍需一次类型转换,但表达式本身是强类型的,查询逻辑更清晰。
当前实现的合理性分析
当前实现的核心问题很明显:
- 类型不安全:
object[]的强制转换无法在编译期验证,运行时可能抛出InvalidCastException。 - 可读性差:
obj[0]、obj[1]的索引访问无法直观体现参数含义,后期维护难度大。 - 性能损耗:值类型的装箱和拆箱操作会带来不必要的性能开销。
唯一的优势是支持任意数量和类型的参数,但这种灵活性是以类型安全、可读性和性能为代价的,在绝大多数业务场景下都不推荐使用。
内容的提问来源于stack exchange,提问作者elgato
相关产品推荐
相关产品推荐

