如何利用C#泛型实现自动生成相似DataSet的公共逻辑
这确实是自动生成复杂类型时常见的头疼问题——没法改生成的代码,又想复用逻辑。以下几个可行方案,你可以根据自己的场景选择:
1. 反射(Reflection):动态访问公共成员
反射是最直接的方式,它能在运行时获取类型的公共属性、方法和字段,完全不依赖编译时类型匹配。
比如你要读取某个DataSet里公共表的行数,可以写一个通用方法:
public static int GetCommonTableRowCount(object dataSet, string tableName) { var dataSetType = dataSet.GetType(); // 缓存Type信息,避免重复反射提升性能 var tableProperty = dataSetType.GetProperty(tableName, BindingFlags.Public | BindingFlags.Instance); if (tableProperty == null) throw new ArgumentException($"表 {tableName} 在该DataSet中不存在"); var dataTable = tableProperty.GetValue(dataSet) as DataTable; return dataTable?.Rows.Count ?? 0; }
注意:如果频繁调用,一定要缓存反射得到的PropertyInfo或MethodInfo,不然性能会受影响。进阶一点可以用Expression树编译成委托,性能接近直接调用。
2. 动态类型(dynamic):绕过编译时检查
用dynamic类型可以直接调用公共成员,编译器不会检查类型,运行时只要成员存在就会执行。这种方式代码更简洁,适合简单场景:
public static void ProcessCommonDataSet(dynamic dataSet) { // 直接调用公共属性,只要所有DataSet都有这个表 var customerTable = dataSet.CustomerTable; foreach (var row in customerTable) { // 处理公共列 Console.WriteLine(row.CustomerId); } }
注意:如果某个DataSet没有对应的成员,运行时会抛出RuntimeBinderException,所以最好提前做成员存在性检查(可以结合反射),或者加异常处理。
3. 适配器模式(Adapter Pattern):封装统一接口
虽然不能修改自动生成的DataSet,但可以给每个DataSet写一个适配器类,实现同一个自定义接口。接口定义所有你需要的公共属性和方法:
// 定义统一接口 public interface ICommonDataSet { DataTable CustomerTable { get; } DataTable OrderTable { get; } void SaveChanges(); } // 针对第一个DataSet的适配器 public class DataSet1Adapter : ICommonDataSet { private readonly DataSet1 _dataSet; public DataSet1Adapter(DataSet1 dataSet) => _dataSet = dataSet; public DataTable CustomerTable => _dataSet.CustomerTable; public DataTable OrderTable => _dataSet.OrderTable; public void SaveChanges() => _dataSet.SaveChanges(); } // 其他DataSet的适配器同理
然后你的通用逻辑就可以针对ICommonDataSet接口编写,完全不用关心具体是哪个DataSet:
public static void ProcessDataSet(ICommonDataSet commonDataSet) { // 操作统一接口的成员 commonDataSet.SaveChanges(); }
优点:类型安全,编译时就能发现错误;缺点:每次DataSet重新生成后,如果公共成员有变更,需要同步更新适配器类,但比起修改14000行的DataSet代码,这点维护成本可以接受。
4. Roslyn代码生成:自动生成适配层
如果适配器的维护还是觉得麻烦,可以用Roslyn写一个简单的代码生成器。读取自动生成的DataSet类的公共成员,自动生成接口和适配器类。每次重新生成DataSet后,运行代码生成器更新适配层代码,完全省去手动编写适配器的工作量。
比如可以写一个控制台程序,用Roslyn分析DataSet的程序集,提取公共属性和方法,然后生成对应的接口和适配器代码文件。
额外建议
- 如果性能要求高,优先选适配器模式或Expression树优化后的反射;
- 如果追求代码简洁,且成员变更不频繁,可以用dynamic;
- 不管用哪种方式,最好写单元测试覆盖不同DataSet的场景,避免运行时错误。
内容的提问来源于stack exchange,提问作者Ant Waters

