可枚举的先决条件

时间:2011-10-18 09:50:11

标签: .net collections code-contracts

鉴于

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给我警告)我对大多数场景的另一种选择是在方法开头强制将集合放入列表中,但考虑到代码契约的性质,在前提条件之前不应该有其他代码。处理这个问题的最佳方法是什么?

3 个答案:

答案 0 :(得分:2)

您至少有两种可能性:

  1. 如果您确定枚举的双重枚举不会让您感到厌烦,请忽略ReSharper警告,可能是因为它是一种内部方法,您知道并且将会知道所有来电者。

  2. 创建一个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
        }
    }
    
  3. 我赞成第二版,特别是如果它是一个公共方法。原因是我昨天无视警告而被烧伤。

答案 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会发出警告。

如果生成计划枚举是一项昂贵的操作,则无关紧要,因为您通常不应在合同验证中包含合同验证。

如果计划枚举是从数据库读取,或类似的,那么合同无论如何都是无效的,因为数据可能在验证和处理之间发生变化。在这种情况下,该方法接受列表或其他不会变异的数据的缓存副本会更正确。