2011-10-18 74 views
3

鉴于前提条件可枚举

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给我的警告)另一种我对大多数情况的做法是强制集合在方法的开始处列表,但考虑到代码合同的性质,其他代码不应该在前提条件之前。处理这个问题的最好方法是什么?

回答

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的会发出这样的警告。

如果生成的时间表枚举是昂贵的操作,也不要紧,因为你通常不应包括合同确认您发布的代码反正。

如果时间表枚举从数据库,或者类似的东西阅读,那么合同是无效的,无论如何,因为数据可能会验证和处理之间切换。在这种情况下,该方法接受不会变异的列表或某些其他缓存的数据副本会更加正确。

+0

我刚刚阅读了关于'ReadOnlyCollection'。在这种情况下,这可以被视为替代方案吗? – Jonn

+0

@John - 是的,我认为'ReadOnlyCollection'将是一个不错的选择。 –

2

您至少有两种可能性:

  1. 忽略ReSharper的警告,如果你是确保枚举的双重枚举是不会咬你的,也许是因为它是一个内部方法,你知道并会知道所有的呼叫者。

  2. 创建一个接受List<Schedule>ProcessSchedules过载:

    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

FWIW时间表来自NHib,所以是的,我会prolly引火烧身为1哈哈 – Jonn

+0

@约翰 - 如果你的数据从数据库中来,那么,合同无效。数据库中的数据可以随时更改,而不会有任何警告。因此,合同永远不能满足。契约检查只适用于不会变异的对象,因为它是不可变的,或者可变对象只被调用线程引用。 –

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

这是非常违反代码合同的精神,它需要无副作用的表达式。这也导致了一个奇怪的合同,因为它有'schedules = schedules.ToList()'而不是'schedule'的合约。由于该表达式成为甚至用户可见的文档的一部分。 – CodesInChaos

+0

@CodeInChaos枚举它时已经创建了一个副作用。至少我sideeffect不会产生更坏的sideeffect(reenumerating两次了IEnumerable>有关文档的一部分,你是对的,但你可以写一个扩展方法(像什么我已经写了)呼吁Materialise公司,使其更清晰你正在做什么。 – xanatos

+0

我喜欢Materialise的是如何工作的。那会除去R#警告在任何情况下? – Jonn