如何优化通用表格打印方法?多集合差异化表头打印需求
你的直觉完全正确——这种靠一堆GetType()判断分支的实现确实不够优雅,不仅维护起来头疼,还违背了开闭原则(新增类型就得改老代码)。咱们来梳理几个更合理的优化方案,你可以根据自己的实际场景选最合适的:
1. 最直接:用方法重载替代泛型+类型判断
既然你要处理的类型是明确的(string、SortedList<someClass, string>、Dictionary<int, List<someClass>>、List<someClass>),直接为每个类型写重载方法是最清晰的选择。这种方式编译时就能确保类型安全,不用再做 runtime 类型判断和强制转换。
示例代码:
// 处理string的重载 private void PrintExample(string fileName, string message) { using (StreamWriter sw = new StreamWriter(fileName, true)) { // 实现string的打印逻辑 sw.WriteLine($"字符串内容:{message}"); } } // 处理SortedList<someClass, string>的重载 private void PrintExample(string fileName, SortedList<someClass, string> list) { using (StreamWriter sw = new StreamWriter(fileName, true)) { PrintTableHeader(sw, "| 1 | 2 | 3 |"); foreach (var kvp in list) { // 替换为实际的字段打印逻辑 sw.WriteLine($"| {kvp.Key.Id} | {kvp.Value} | 占位 |"); } PrintTableFooter(sw); } } // 处理Dictionary<int, List<someClass>>的重载 private void PrintExample(string fileName, Dictionary<int, List<someClass>> dict) { using (StreamWriter sw = new StreamWriter(fileName, true)) { PrintTableHeader(sw, "| SOMETHING | Name | Something2 |"); foreach (var kvp in dict) { foreach (var item in kvp.Value) { sw.WriteLine($"| {kvp.Key} | {item.Name} | {item.Something2} |"); } PrintDivider(sw); } sw.WriteLine(); } } // 处理List<someClass>的重载 private void PrintExample(string fileName, List<someClass> list) { using (StreamWriter sw = new StreamWriter(fileName, true)) { PrintTableHeader(sw, "| other header | other header 7777 |"); foreach (var item in list) { sw.WriteLine($"| {item.OtherProp} | {item.OtherProp7777} |"); } PrintTableFooter(sw); } } // 提取重复逻辑为辅助方法,减少冗余 private void PrintTableHeader(StreamWriter sw, string headerLine) { var divider = new string('-', headerLine.Length); sw.WriteLine(divider); sw.WriteLine(headerLine); sw.WriteLine(divider); } private void PrintTableFooter(StreamWriter sw) { sw.WriteLine(new string('-', 50)); sw.WriteLine(); } private void PrintDivider(StreamWriter sw) { sw.WriteLine(new string('-', 50)); }
这种方案的优点:
- 代码结构清晰,每个方法只负责一种类型的打印逻辑
- 编译时类型安全,避免了
as强制转换的风险 - 扩展简单,新增类型只需要加新的重载方法,不用修改原有代码
2. 更灵活:策略模式(适合动态扩展场景)
如果以后需要频繁新增打印类型,或者希望打印逻辑和业务代码解耦,可以用策略模式。核心是把每种类型的打印逻辑封装成独立的策略类,通过字典或依赖注入来匹配对应的策略。
首先定义打印策略接口:
public interface IPrintStrategy<T> { void Print(string fileName, T data); }
然后为每个类型实现策略:
public class StringPrintStrategy : IPrintStrategy<string> { public void Print(string fileName, string data) { using (StreamWriter sw = new StreamWriter(fileName, true)) { sw.WriteLine($"字符串内容:{data}"); } } } public class SortedListPrintStrategy : IPrintStrategy<SortedList<someClass, string>> { public void Print(string fileName, SortedList<someClass, string> data) { using (StreamWriter sw = new StreamWriter(fileName, true)) { var header = "| 1 | 2 | 3 |"; var divider = new string('-', header.Length); sw.WriteLine(divider); sw.WriteLine(header); sw.WriteLine(divider); foreach (var kvp in data) { sw.WriteLine($"| {kvp.Key.Id} | {kvp.Value} | 占位 |"); } sw.WriteLine(divider); sw.WriteLine(); } } } // 其他类型的策略类同理...
最后在主类中注册并调用策略:
private readonly Dictionary<Type, object> _printStrategies = new(); // 构造函数中注册策略 public YourClassName() { _printStrategies.Add(typeof(string), new StringPrintStrategy()); _printStrategies.Add(typeof(SortedList<someClass, string>), new SortedListPrintStrategy()); // 注册其他策略... } // 统一入口方法 private void PrintExample<T>(string fileName, T data) { if (_printStrategies.TryGetValue(typeof(T), out var strategy)) { ((IPrintStrategy<T>)strategy).Print(fileName, data); } else { throw new NotSupportedException($"暂不支持类型{typeof(T)}的打印"); } }
这种方案的优点:
- 打印逻辑和业务代码完全解耦,符合单一职责原则
- 可以动态添加/替换策略,甚至在运行时切换打印格式
- 适合复杂场景,比如多个模块需要复用打印逻辑
3. 利用多态:让数据类型自己负责打印(若可修改实体类)
如果someClass是你自己定义的,可以让实体类实现统一的打印接口,把打印逻辑封装到实体内部,再配合扩展方法处理集合的表头和循环。
首先定义打印接口:
public interface IPrintable { void PrintToStream(StreamWriter sw); }
让someClass实现接口:
public class someClass : IPrintable { public string Name { get; set; } public int Something2 { get; set; } public string OtherProp { get; set; } public void PrintToStream(StreamWriter sw) { // 这里可以根据不同的打印场景重载,或者保持基础格式 sw.WriteLine($"| {Name} | {Something2} |"); } }
然后写扩展方法处理集合:
public static class PrintExtensions { public static void PrintAsTable<T>(this IEnumerable<T> items, StreamWriter sw, string header) where T : IPrintable { var divider = new string('-', header.Length); sw.WriteLine(divider); sw.WriteLine(header); sw.WriteLine(divider); foreach (var item in items) { item.PrintToStream(sw); } sw.WriteLine(divider); sw.WriteLine(); } public static void PrintAsTable<TKey>(this Dictionary<TKey, List<someClass>> dict, StreamWriter sw, string header) { var divider = new string('-', header.Length); sw.WriteLine(divider); sw.WriteLine(header); sw.WriteLine(divider); foreach (var kvp in dict) { sw.WriteLine($"| {kvp.Key} |"); foreach (var item in kvp.Value) { item.PrintToStream(sw); } sw.WriteLine(divider); } sw.WriteLine(); } }
调用时就非常简洁:
private void PrintExample(string fileName, List<someClass> list) { using (StreamWriter sw = new StreamWriter(fileName, true)) { list.PrintAsTable(sw, "| other header | other header 7777 |"); } }
这种方案的优点:
- 实体类自己控制打印格式,符合封装原则
- 扩展方法可以复用,减少重复代码
- 后续修改实体的打印格式,只需要修改实体类的实现
总结
如果你的场景比较简单,优先选方法重载,它最直接、易维护;如果以后需要频繁扩展打印类型,或者想解耦逻辑,就用策略模式;如果能修改实体类的代码,多态+扩展方法会让代码更简洁优雅。
内容的提问来源于stack exchange,提问作者Tomas




