如何实现支持自定义起止时间属性名的C#泛型合并函数?
实现适配任意起止属性名的C#泛型时间块合并函数
问题背景
现有泛型函数MakeBlocks依赖固定的StartDT和EndDT属性名实现时间连续项合并,需要修改为支持任意命名的起止时间属性,适配不同的数据结构。
解决方案
C#作为强类型语言,虽然不能直接像JavaScript那样传入属性名字符串,但可以通过委托(Func/Action)实现类型安全的属性访问逻辑,或者通过反射实现字符串指定属性的方式,以下是两种可行方案:
方案1:使用委托(推荐,类型安全+高性能)
通过传递获取起始时间、获取结束时间、设置结束时间的委托,让函数适配任意属性名,编译时即可检查属性是否存在,避免运行时错误。
修改后的泛型函数:
using System; using System.Collections.Generic; using System.Linq; public static class TimeBlockMerger { public static IEnumerable<T> MakeBlocks<T>( IEnumerable<T> input, Func<T, DateTime> getStartTime, Func<T, DateTime> getEndTime, Action<T, DateTime> setEndTime) { var outList = new List<T>(); if (!input.Any()) return outList; T currentEntry = input.First(); foreach (var nextEntry in input.Skip(1)) { if (nextEntry != null && getStartTime(nextEntry) == getEndTime(currentEntry)) { // 合并时间块:更新当前项的结束时间为下一项的结束时间 setEndTime(currentEntry, getEndTime(nextEntry)); } else { outList.Add(currentEntry); currentEntry = nextEntry; } } outList.Add(currentEntry); return outList; } }
调用示例:
假设我们有一个自定义的Meeting类:
public class Meeting { public DateTime Start { get; set; } public DateTime End { get; set; } // 其他属性... }
调用合并函数时,只需传入对应属性的委托:
List<Meeting> meetings = GetMeetings(); // 获取会议列表 var mergedMeetings = TimeBlockMerger.MakeBlocks( meetings, m => m.Start, // 获取起始时间的委托 m => m.End, // 获取结束时间的委托 (m, newEnd) => m.End = newEnd // 设置结束时间的委托 );
方案2:使用反射(适配字符串指定属性名)
如果需要类似JavaScript那样通过字符串指定属性名,可以借助反射实现,但此方案编译时无法检查属性是否存在,性能略低于委托方案,适合非高频调用场景。
修改后的泛型函数:
using System; using System.Collections.Generic; using System.Linq; using System.Reflection; public static class TimeBlockMerger { public static IEnumerable<T> MakeBlocks<T>( IEnumerable<T> input, string startPropName, string endPropName) { var outList = new List<T>(); if (!input.Any()) return outList; // 获取属性信息 PropertyInfo startProp = typeof(T).GetProperty(startPropName); PropertyInfo endProp = typeof(T).GetProperty(endPropName); if (startProp == null) throw new ArgumentException($"类型 {typeof(T).Name} 不存在属性 {startPropName}"); if (endProp == null) throw new ArgumentException($"类型 {typeof(T).Name} 不存在属性 {endPropName}"); if (startProp.PropertyType != typeof(DateTime) || endProp.PropertyType != typeof(DateTime)) throw new ArgumentException("起止属性必须为DateTime类型"); T currentEntry = input.First(); foreach (var nextEntry in input.Skip(1)) { if (nextEntry != null) { DateTime currentEnd = (DateTime)endProp.GetValue(currentEntry); DateTime nextStart = (DateTime)startProp.GetValue(nextEntry); if (nextStart == currentEnd) { DateTime nextEnd = (DateTime)endProp.GetValue(nextEntry); endProp.SetValue(currentEntry, nextEnd); } else { outList.Add(currentEntry); currentEntry = nextEntry; } } else { outList.Add(currentEntry); currentEntry = nextEntry; } } outList.Add(currentEntry); return outList; } }
调用示例:
List<Meeting> meetings = GetMeetings(); var mergedMeetings = TimeBlockMerger.MakeBlocks(meetings, "Start", "End");
注意事项
- 两种方案都要求输入列表已经按起始时间排序,否则合并逻辑会失效;
- 委托方案优先推荐,兼顾类型安全和性能;
- 反射方案需注意处理属性不存在、类型不匹配的异常情况。
内容的提问来源于stack exchange,提问作者Henrik Clausen
相关产品推荐
相关产品推荐

