【问题标题】:List vs IEnumerable in private, lazy-loaded propertyList vs IEnumerable in private,lazy-loaded property
【发布时间】:2014-01-14 06:54:23
【问题描述】:

我有一个公共属性 (AllCustomers),它由一个私有属性支持,用于延迟加载。

我了解公共属性应该是 IEnumerable ("program to interfaces, not implementations")。

但是,我可以看到两种构建私有财产的方法。

第一个选项,带有私有列表-

private List<Customer> _AllCustomers;
public IEnumerable<Customer> AllCustomers
{
    get
    {
        if (_AllCustomers == null)
        {
            _AllCustomers = DAL.GetAllCustomers().ToList(); 
        }
        return _AllCustomers;
    }
}

第二个选项,带有私有 IEnumerable-

private IEnumerable<Customer> _AllCustomers;
public IEnumerable<Customer> AllCustomers
{
    get
    {
        if (_AllCustomers == null)
        {
            _AllCustomers = DAL.GetAllCustomers(); 
        }
        return _AllCustomers;
    }
}

我认为第一个选项看起来更正确,因为它会访问数据库一次并存储结果,而第二个会导致多次数据库访问。

我的问题是-

  • 我的分析是否正确?
  • 不同方法的含义是什么?
  • 是否有任何时候首选第二个选项?
  • 有没有更好、更惯用的方式来表达 第二个选项?

【问题讨论】:

  • 如果不知道DAL.GetAllCustomers() 做了什么,我们不可能回答。例如,它可能会返回List&lt;Customer&gt;,即使它声明返回IEnumerable&lt;Customer&gt;
  • 为什么你的私有成员是属性,而不是字段?
  • @MarcinJuraszek - 谢谢,私人成员已更新到字段。
  • @JonSkeet - 所以如果 DAL.GetAllCustomers() 返回一个 List,那么样本之间就没有区别了吗?
  • @Spongeboy:不一定。第一个版本创建列表的副本。例如,DAL.GetAllCustomers() 可能正在缓存它返回的列表 - 在这种情况下,您可以通过将 AllCustomers 的结果转换回 List&lt;Customer&gt; 并对其进行修改来轻松展示不同的行为。

标签: c# asp.net list ienumerable private-members


【解决方案1】:

这里涉及到几个级别的“懒惰”。一个是 IEnumerable 实现固有的惰性,另一个是您自己添加到属性中的惰性实现。

您的第一个实现将访问数据库一次,第一次访问 AllCustomers。它将在GetAllCustomers 中构造查询,并在您调用ToList 时执行它,并将结果存储在本地。

您的第二个实现只会访问数据库一次(假设您的 LINQ 实现还不错)。但是,这将比第一种情况 - 即使调用您的AllCustomer 属性也只会返回一个IQueryable,这只会在实际访问或枚举AllCustomers 时执行。这可能是紧随其后,也可能不是——这是一个更懒惰的实现。同样,假设您的 LINQ 提供程序不是太愚蠢,遍历整个集合仍然只会访问数据库一次。

为什么要选择第一个选项?因为(同样,取决于实现),迭代AllCustomers 两次 可能会命中数据库两次。 Resharper 非常方便地警告我们,当我们可能有一个 IEnumerable 的多个枚举时。将其存储在本地 List 将确保我们保留缓存的本地副本。为确保在代码中明确表达这一点,请考虑公开 IReadOnlyList 而不是 IEnumerable

【讨论】:

  • 您确定您的声明iterating over the entire collection will still only hit the DB once.?这绝对是错误的,LINQ 提供程序返回 IQueryable,它在每次枚举时都会命中 DB。
  • @AkashKava 你是对的,我不清楚。对于每个枚举,它将访问数据库一次,但不是对于该枚举的每次迭代。在这种情况下,我可能误解了 OP 的担忧,但我认为我的编辑澄清了这一点。
  • 即使每次迭代,阅读器仍然是打开的。所以枚举查询保持数据库连接打开。还是调用 ToList 再枚举就更好了。
  • 这取决于 LINQ 提供程序,正如 @MarcinJuraszek 在另一个答案中所述。
【解决方案2】:

当第一次调用 getter 时,两者都只会访问 DB 一次,因为 _AllCustomers 支持字段仅是 NULL

根据个人喜好,我通常更喜欢在我的接口和方法声明中使用IEnumerable,但是支持字段是具体的类,例如List&lt;stuff&gt;。让修改您的收藏之类的事情变得更容易。

【讨论】:

  • 不,第二个可能在每次请求客户时访问数据库。我们无法知道,因为我们不知道DAL.GetAllCustomers() 是如何实现的。它可能会返回一个每次都会执行的查询,或者它可能会返回一个物化列表...
  • 如果 GetAllCustomers 返回一个 IQueryable 那么它每次都会命中数据库。
  • @AkashKava 没有必要。您可以使用AsQueryable 方法将List&lt;T&gt; 作为IQueryable&lt;T&gt; 返回。
  • @MarcinJuraszek 即使在 AsQueryable 之后,List 的实例仍然是 List 而不是对数据库的查询。
  • @AkashKava 是的,是什么让你的 如果它返回 IQueryable 它每次都会命中 DB 无效。即使声明方法返回IQueryable,也有可能只命中一次DB。
【解决方案3】:

我了解公共属性应该是 IEnumerable(“程序到接口,而不是实现”)。

这种理解是有缺陷的。接口编程的原则并没有说应该编程到哪个接口。在你的例子中。我会公开IList&lt;T&gt;,并将其设为只读,假设您不希望消费者修改列表:

private List<Customer> _AllCustomers;
public IList<Customer> AllCustomers
{
    get
    {
        if (_AllCustomers == null)
        {
            _AllCustomers = DAL.GetAllCustomers().ToList().AsReadOnly(); 
        }
        return _AllCustomers;
    }
}

【讨论】:

  • 但是IList比IEnumerable宽松,“程序到接口”原则使用最宽松。我知道如果数据合同暗示需要随机访问,则应使用 IList。
  • @Spongeboy:我可以看到允许使用方法参数的论点。但是,如果我想向调用者提示实现可能使用惰性求值,我只会使用 IEnumerable 的返回值。返回 IList 或 ICollection 使调用者更容易(Count 属性,更容易重复迭代)。
猜你喜欢
  • 2014-01-19
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2011-07-02
  • 2011-02-10
相关资源
最近更新 更多