【问题标题】:Why use ICollection and not IEnumerable or List<T> on many-many/one-many relationships?为什么在多对多/一对多关系上使用 ICollection 而不是 IEnumerable 或 List<T>?
【发布时间】:2012-04-11 20:14:02
【问题描述】:

我在教程中看到很多,导航属性为ICollection&lt;T&gt;

这是实体框架的强制性要求吗?我可以使用IEnumerable吗?

使用ICollection而不是IEnumerable甚至List&lt;T&gt;的主要目的是什么?

【问题讨论】:

  • 所以答案是,使用 ICollection 不是强制性的。如果迭代是我想要的,我可以使用 IEnumerable。对吗?

标签: c# entity-framework code-first icollection


【解决方案1】:

通常您选择什么取决于您需要访问哪些方法。一般来说 - IEnumerable&lt;&gt; (MSDN: http://msdn.microsoft.com/en-us/library/system.collections.ienumerable.aspx) 用于仅需要迭代的对象列表,ICollection&lt;&gt; (MSDN: http://msdn.microsoft.com/en-us/library/92t2ye13.aspx) 用于需要迭代和修改的对象列表, List&lt;&gt; 获取需要迭代、修改、排序等的对象列表(完整列表请参见此处:http://msdn.microsoft.com/en-us/library/6sh2ey19.aspx)。

从更具体的角度来看,延迟加载与选择类型有关。默认情况下,Entity Framework 中的导航属性带有更改跟踪并且是代理。为了将动态代理创建为导航属性,虚拟类型必须实现ICollection

表示关系“多”端的导航属性必须返回实现 ICollection 的类型,其中 T 是关系另一端的对象的类型。 -Requirements for Creating POCO ProxiesMSDN

More information on Defining and Managing RelationshipsMSDN

【讨论】:

  • 那么,List 应该会好很多吧?
  • @JanCarloViray - 我经常使用List。虽然开销最大,但提供的功能最多。
  • 列表更多地由它们的索引器定义,而不是由它们排序的能力(使用整数索引器可以很容易地对某些内容进行排序,但这不是必需的)。
  • 关于您的编辑,将属性限制为接口类型与内存无关,而是与封装有关。考虑:private IEnumerable&lt;int&gt; _integers = new List&lt;int&gt; { 1, 2, 3 }; 使用与 private List&lt;int&gt; _integers = new List&lt;int&gt; { 1, 2, 3 }; 相同的内存
  • @TravisJ: List&lt;T&gt; 有一个GetEnumerator() 方法,与IEnumerable&lt;T&gt; 的实现分开,它返回一个可变结构类型List&lt;T&gt;.Enumerator。在大多数情况下,该类型将产生比独立堆对象稍好的性能。在生成foreach 代码时,使用鸭式枚举器(如 C# 和 vb.net 所做的)的编译器可以利用这一点。如果在foreach 之前将List&lt;T&gt; 强制转换为IEnumrable&lt;T&gt;,则IEnumerable&lt;T&gt;.GetEnumerator() 方法将返回一个堆分配对象,从而无法进行优化。
【解决方案2】:

使用ICollection&lt;T&gt; 是因为IEnumerable&lt;T&gt; 接口无法提供添加项目、删除项目或以其他方式修改集合的方式。

【讨论】:

  • 与 List 比较怎么样?
  • List&lt;T&gt; 实现ICollection&lt;T&gt;
  • 非泛型 ICollection 不允许以任何方式添加项目,但它仍然是 IEnumerable&lt;T&gt; 的有用附属物,因为它提供了一个通常比枚举快得多的 Count 成员一切。请注意,如果将IList&lt;Cat&gt;ICollection&lt;Cat&gt; 传递给期望IEnumerable&lt;Animal&gt; 的代码,则Count() 扩展方法如果实现非泛型ICollection 将很快,但如果它仅实现泛型接口则不会因为典型的ICollection&lt;Cat&gt; 不会实现ICollection&lt;Animal&gt;
【解决方案3】:

回答你关于List&lt;T&gt;的问题:

List&lt;T&gt; 是一个类;指定接口允许更大的实现灵活性。一个更好的问题是“为什么不IList&lt;T&gt;?”

要回答这个问题,请考虑IList&lt;T&gt; 添加到ICollection&lt;T&gt; 的内容:整数索引,这意味着项目具有任意顺序,并且可以通过引用该顺序来检索。这在大多数情况下可能没有意义,因为项目可能需要在不同的上下文中以不同的方式排序。

【讨论】:

    【解决方案4】:

    ICollection 和 IEnumerable 之间有一些基本的区别

    • IEnumerable - 仅包含 GetEnumerator 方法来获取 Enumerator 并允许循环
    • ICollection 包含其他方法:Add、Remove、Contains、Count、CopyTo
    • ICollection 继承自 IEnumerable
    • 使用 ICollection,您可以使用添加/删除等方法修改集合。您无权对 IEnumerable 做同样的事情。

    简单程序:

    using System;
    using System.Collections;
    using System.Collections.Generic;
    
    namespace StackDemo
    {
        class Program 
        {
            static void Main(string[] args)
            {
                List<Person> persons = new List<Person>();
                persons.Add(new Person("John",30));
                persons.Add(new Person("Jack", 27));
    
                ICollection<Person> personCollection = persons;
                IEnumerable<Person> personEnumeration = persons;
    
                // IEnumeration
                // IEnumration Contains only GetEnumerator method to get Enumerator and make a looping
                foreach (Person p in personEnumeration)
                {                                   
                   Console.WriteLine("Name:{0}, Age:{1}", p.Name, p.Age);
                }
    
                // ICollection
                // ICollection Add/Remove/Contains/Count/CopyTo
                // ICollection is inherited from IEnumerable
                personCollection.Add(new Person("Tim", 10));
    
                foreach (Person p in personCollection)
                {
                    Console.WriteLine("Name:{0}, Age:{1}", p.Name, p.Age);        
                }
                Console.ReadLine();
    
            }
        }
    
        class Person
        {
            public string Name { get; set; }
            public int Age { get; set; }
            public Person(string name, int age)
            {
                this.Name = name;
                this.Age = age;
            }
        }
    }
    

    【讨论】:

      【解决方案5】:

      我记得是这样的:

      1. IEnumerable 有一种方法 GetEnumerator(),它允许读取集合中的值但不能写入。 C# 中的 for each 语句为我们处理了使用枚举器的大部分复杂性。 IEnumerable 有一个属性:Current,它返回当前元素。

      2. ICollection 实现了 IEnumerable 并添加了一些其他属性,其中最常用的是 Count。 ICollection 的通用版本实现了 Add() 和 Remove() 方法。

      3. IList 实现了 IEnumerable 和 ICollection,并添加了对项目的整数索引访问(通常不需要,因为排序是在数据库中完成的)。

      【讨论】:

      • 根据你写的ICollection和IList是一样的。请添加 ICollection 中不存在的添加到 IList 的内容。
      • ICollection VS IList,仅在 System.Collection 中的 IList 接口包含 IEnumerable 和 ICollection 的所有功能以及其他功能。 IList 有 Insert 和 Remove 方法。这两种方法都在其参数中接受索引。因此,它支持基于索引的集合操作。
      【解决方案6】:

      使用ICollection 的基本思想是提供一个接口来只读访问一些有限数量的数据。事实上,您有一个 ICollection.Count 属性。 IEnumerable 更适合您读取到某个逻辑点、消费者明确指定的某些条件或直到枚举结束的某些数据链。

      【讨论】:

      【解决方案7】:

      导航属性通常定义为虚拟属性,以便它们可以利用某些实体框架功能,例如延迟加载。

      如果导航属性可以包含多个实体(如在多对多或一对多关系中),则其类型必须是可以添加、删除和更新条目的列表,例如 ICollection。

      https://www.asp.net/mvc/overview/getting-started/getting-started-with-ef-using-mvc/creating-an-entity-framework-data-model-for-an-asp-net-mvc-application

      【讨论】:

        【解决方案8】:

        我过去所做的是使用IList&lt;Class&gt;ICollection&lt;Class&gt;IEnumerable&lt;Class&gt;(如果是静态列表)声明我的内部类集合,具体取决于我是否必须在我的存储库中的方法:枚举、排序/排序或修改。当我只需要对对象进行枚举(并且可能是排序)时,我会创建一个 temp List&lt;Class&gt; 以在 IEnumerable 方法中使用该集合。我认为这种做法只有在集合相对较小的情况下才会有效,但总的来说它可能是一种很好的做法,idk。如果有证据表明这不是好的做法,请纠正我。

        【讨论】:

          【解决方案9】:

          让我们尝试用/按逻辑跳出框框思考,并清楚地理解您问题中的这三个接口:

          当某个实例的类实现了 System.Collection.IEnumerable 接口时,简单来说,我们可以说这个实例既是可枚举的又是可迭代的,这意味着该实例允许以某种方式在单个循环中进行 go/get /pass/traverse/迭代/遍历此实例包含的所有项目和元素。

          这意味着这也可以枚举该实例包含的所有项目和元素。

          实现 System.Collection.IEnumerable 接口的每个类也实现了 GetEnumerator 方法,该方法不接受任何参数并返回 System.Collections.IEnumerator 实例。

          System.Collections.IEnumerator 接口的实例的行为与 C++ 迭代器非常相似。

          当某个实例的类实现了 System.Collection.ICollection 接口时,简单来说,我们可以说这个实例是一些事物的集合。

          此接口的通用版本,即 System.Collection.Generic.ICollection,提供更多信息,因为此通用接口明确说明集合中事物的类型。

          System.Collections.ICollection 接口继承自 System.Collections.IEnumerable 接口,这一切都是合理的、合理的、合乎逻辑的,因为理论上每个集合也是可枚举和可迭代的,这在理论上可以遍历所有每个集合中的项目和元素。

          System.Collections.ICollection 接口表示一个可变的有限动态集合,这意味着可以从集合中删除现有项目,并且可以将新项目添加到同一集合中。

          这解释了为什么 System.Collections.ICollection 接口具有“添加”和“删除”方法。

          因为 System.Collections.ICollection 接口的实例是有限集合,所以“有限”一词意味着该接口的每个集合中始终包含有限数量的项目和元素。

          System.Collections.ICollection 接口的属性 Count 应该返回这个数字。

          System.Collections.IEnumerable 接口没有 System.Collections.ICollection 接口所具有的这些方法和属性,因为 System.Collections.IEnumerable 将具有 System.Collections.ICollection 的这些方法和属性没有任何意义界面有。

          逻辑还说,每个既可枚举又可迭代的实例不一定是 一个集合,不一定是可变的。

          当我说可变的时,我的意思是不要立即认为您可以从可枚举和可迭代的东西中添加或删除一些东西。

          如果我刚刚创建了一些有限的素数序列,例如,这个有限的素数序列确实是 System.Collections.IEnumerable 接口的一个实例,因为现在我可以遍历这个有限序列中的所有素数一个单一的循环,对它们中的每一个做任何我想做的事情,比如将它们中的每一个打印到控制台窗口或屏幕上,但是这个有限的素数序列不是 System.Collections.ICollection 接口的实例,因为这不是将合数添加到这个有限的素数序列是有意义的。

          您还希望在下一次迭代中获得与当前迭代中的当前素数最接近的较大素数,如果是这样,您也不想从这个有限的素数序列中删除现有素数。

          您可能还想在 System.Collections.IEnumerable 接口的 GetEnumerator 方法中使用、编码和编写“yield return”来生成素数,而不是在内存堆上分配任何内容,然后执行垃圾收集器 (GC ) 从堆中释放和释放这块内存,因为这显然既浪费了操作系统内存,又降低了性能。

          在调用 System.Collections.ICollection 接口的方法和属性时应该在堆上进行动态内存分配和释放,而不是在调用 System.Collections.IEnumerable 接口的方法和属性时(尽管 System.Collections.IEnumerable接口只有 1 个方法和 0 个属性)。

          根据其他人在此 Stack Overflow 网页中所说的,System.Collections.IList 接口仅表示 可订购 集合,这解释了为什么 System.Collections.IList 接口的方法与索引相反System.Collections.ICollection 接口的这些。

          简而言之,System.Collections.ICollection 接口并不意味着它的实例是可订购的,但 System.Collections.IList 接口确实暗示了这一点。

          理论上有序集是无序集的特例。

          这也是有道理的,并解释了为什么 System.Collections.IList 接口继承 System.Collections.ICollection 接口。

          【讨论】:

            猜你喜欢
            • 1970-01-01
            • 2015-11-13
            • 1970-01-01
            • 1970-01-01
            • 2015-09-16
            • 1970-01-01
            • 2019-07-17
            • 2018-06-15
            • 2021-04-01
            相关资源
            最近更新 更多