我可以在IQueryable和IEnumerable上进行Contract.Ensures吗?

时间:2012-11-16 13:00:10

标签: c# .net ienumerable iqueryable code-contracts

让我们看看这段代码:

public IQueryable<Category> GetAllActive()
{
    Contract.Ensures(Contract.Result<IQueryable<Category>>() != null);
    return dataSource.GetCategories(T => T.IsActive);
}

有一个小问题。代码合同是否可以写下这个:

public IQueryable<Category> GetAllActive()
{
    Contract.Ensures(Contract.Result<IQueryable<Category>>() != null);
    Contract.Ensures(Contract.Result<IQueryable<Category>>().All(T => T.IsActive));
    return dataSource.GetCategories(T => T.IsActive);
}

或不?

这样的事情是否会产生不必要的序列枚举,这是非常不可取的?

1 个答案:

答案 0 :(得分:2)

假设您正在使用二进制重写器并在运行时强制执行合同,您应该执行此操作。

当您使用Contract.Ensures时:

Contract.Ensures(Contract.Result<T>() <operation>);
return expression;

它被转换,操作被提升为如下所示:

T expression = <expression>;

// Perform checks on expression.
if (!(expression <operation>) <throw appropriate exception>;

// Return value.
return expression;

在这种情况下,这意味着您的代码将展开:

IQueryable<Category> temp = dataSource.GetCategories(T => T.IsActive);

// Perform checks.
if (!(temp != null)) throw new Exception();
if (!temp.All(T => T.IsActive)) throw new Exception();

// Return values.
return temp;

在这种情况下,您的IQueryable<Category>将被迭代,并将另一个请求发送到基础数据存储。

根据操作的不同,您可能不会注意到它,但查询肯定将执行两次,这对性能有害。

对于这种性质的东西,你应该在IQueryable<T>的消费点检查你是否有任何元素(你可以使用设置为foreach的布尔标志来做通过它,或当你实现它。)

但是,如果您在已编译的程序集上运行二进制重写器,则无法强制执行此类Contract.Result<T>个合同;在这种情况下,它是一个noop,可能不应该在那里,因为它没有做任何事情。