鉴于
void ProcessSchedules(IEnumerable<Schedule> schedules)
{
Contract.Requires<ArgumentException>(Contract.ForAll(schedules,x => x.Date <= DateTime.Today))
foreach( var schedule in schedules )
{
// Do something with schedule
}
}
在这种情况下,计划将被枚举两次。 (Resharper给我警告)我对大多数场景的另一种选择是在方法开头强制将集合放入列表中,但考虑到代码契约的性质,在前提条件之前不应该有其他代码。处理这个问题的最佳方法是什么?
答案 0 :(得分:2)
您至少有两种可能性:
如果您确定枚举的双重枚举不会让您感到厌烦,请忽略ReSharper警告,可能是因为它是一种内部方法,您知道并且将会知道所有来电者。
创建一个ProcessSchedules
的重载,接受List<Schedule>
:
void ProcessSchedules(IEnumerable<Schedule> schedules)
{
Contract.Requires(schedules != null);
ProcessSchedules(schedules.ToList());
}
void ProcessSchedules(List<Schedule> schedules)
{
Contract.Requires<ArgumentException>(Contract.ForAll(schedules,
x => x.Date <=
DateTime.Today));
foreach( var schedule in schedules )
{
// Do something with schedule
}
}
我赞成第二版,特别是如果它是一个公共方法。原因是我昨天无视警告而被烧伤。
答案 1 :(得分:1)
有人会杀了我,我知道: - )
void ProcessSchedules(IEnumerable<Schedule> schedules)
{
Contract.Requires<ArgumentException>(Contract.ForAll((schedules = schedules.ToList()),x => x.Date <= DateTime.Today))
// now schedules is a List<Schedule> :-) Note that the reference is still a
// IEnumerable<Schedule>, but you can cast it, like:
List<Schedule> schedules2 = (List<Schedule>)schedules;
foreach( var schedule in schedules )
{
// Do something with schedule
}
}
这有效because:
简单赋值表达式的结果是赋值的值 左操作数。结果与左操作数和。的类型相同 总是被归类为价值。
我将添加一个扩展方法,检查传递的参数是否已经是List<T>
或Array<T>
,这样它就不会创建它的第二个副本。类似的东西:
public static IList<T> Materialize<T>(this IEnumerable<T> enu)
{
if (enu is IList<T>)
{
return (IList<T>)enu;
}
else
{
return enu.ToList();
}
}
显然,您可以延伸以检查enu
是否已经IList<T>
而不是“纯”IEnumerable<T>
答案 2 :(得分:1)
以下是我可以按照个人喜好的降序排列的选项。
使用静态检查而不是运行时检查。这样就不必枚举列表来进行检查。该选项具有极其昂贵的限制。我自己也无法访问静态检查程序,就像我希望的那样。所以,你也可以......
将方法的定义更改为在未来日期的预设中正常工作,以便不需要检查。
像这样:
void ProcessSchedules(IEnumerable<Schedule> schedules)
{
Contract.Requires<ArgumentNullException>(schedules != null);
foreach(var schedule in schedules )
{
if (schedule.Date <= DateTime.Today)
{
// Do something with schedule
}
}
}
如果这对您的计划没有意义,那么您可以......
像这样:
void ProcessSchedules(List<Schedule> schedules)
{
Contract.Requires<ArgumentException>(Contract.ForAll(schedules,x => x.Date <= DateTime.Today))
foreach(var schedule in schedules )
{
// Do something with schedule
}
}
你已经说过强迫它进入一个列表是一个选项,所以很明显这是一个可接受大小的有限序列。调用者很可能首先使用List<Schedule>
,或者有其他地方可以使用此实现。
如果您真的希望将IEnumerable
保留在签名中,您可以......
使用原始代码:
void ProcessSchedules(IEnumerable<Schedule> schedules)
{
Contract.Requires<ArgumentException>(Contract.ForAll(schedules,x => x.Date <= DateTime.Today))
foreach( var schedule in schedules )
{
// Do something with schedule
}
}
我认真地看不到列举两次列表的问题,我甚至不确定为什么Resharper会发出警告。
如果生成计划枚举是一项昂贵的操作,则无关紧要,因为您通常不应在合同验证中包含合同验证。
如果计划枚举是从数据库读取,或类似的,那么合同无论如何都是无效的,因为数据可能在验证和处理之间发生变化。在这种情况下,该方法接受列表或其他不会变异的数据的缓存副本会更正确。