C#中关于IEnumerable接口的一些澄清?

3

我是一个新手,之前从Java转到了C#,我对以下用法有疑问:

我有这个类:

namespace DataModel.MaliciousCode
{

    public class PagedMalicious : Shared.Paged
    {
        public IEnumerable<MaliciousSmall> MaliciousCode { get; set; }

    }
}

正如您所看到的,这个类只包含一个 IEnumerable<MaliciousSmall> MaliciousCode

阅读在线文档后,我认为我理解了 IEnumerable 是一个接口,它提供了对非泛型集合的迭代器。

上述说法具体意味着什么?

我有一个在我的应用程序中的模型对象类型是 MaliciousSmall(它包含一些属性,映射数据库表中的字段)

所以我的疑问是:

  • IEnumerable<MaliciousSmall> MaliciousCode:MaliciousCode 是一个可迭代的 MaliciousSmall 对象集合吗?这意味着它表示一个集合和一些提供的方法来迭代它吗?

  • 如果上述说法是正确的,那么好的 MaliciousCode 对象是一个可迭代的集合,但 IEnumerable 是一个接口,那么谁实现了迭代这个集合的方法(来自 Java,我认为接口没有提供实现的方法)

有人可以帮我理解这些吗?

谢谢

Andrea


7
IEnumerable<T> 和 Java 的 Iterable<T> 是完全等价的。 - SLaks
4个回答

4
IEnumerable<MaliciousSmall> MaliciousCode: MaliciousCode 是一个可迭代的MaliciousSmall对象集合吗?那么它表示一组提供了可以迭代的方法的集合吗?
有点类似 - IEnumerable<T>提供了一个方法-GetEnumerator,该方法返回一个IEnumerator<T>。通过THAT接口,您可以依次遍历集合。在Linq之前,所有的IEnumerable允许你做的就是在foreach循环中使用集合(或直接使用提供的IEnumerator,这是很少见的)。Linq随后定义了对IEumerable<T>的扩展方法,以实现更复杂的查询,如SelectWhereCount等。
如果前面的断言是真的,那么好的MaliciousCode对象是一个可迭代的集合,但IEnumerable是一个接口,所以是谁实现了在这个集合上进行迭代的方法的呢?(来自Java,我认为接口没有提供实现的方法)
通常情况下,实现是通过使用底层集合类型,比如List<MaliciousSmall>MaliciousSmall[]来提供的。因此,IEnumerable实现是由那个类提供的。yield关键字在C# 2.0中引入,允许你“返回”一个IEnumerable<T>并让编译器提供实际的实现。
因此,在您的类中,您可能会将集合作为List<T>进行内部实现:
public class PagedMalicious : Shared.Paged
{
    public IEnumerable<MaliciousSmall> MaliciousCode { get; set; }

    public PagedMalicious()
    {
        MaliciousCode = new List<MaliciousSmall>();
    }

    // other private methods that add to MaliciousCode
}

使用IEnumerable<T>允许您在不更改公共接口的情况下更改内部实现。


对于界面内部实现的解释给予加一分,回答得不错! - Evan L
LINQ并没有从语言的角度改变使用IEnumerable所能做的事情。你只是调用一些帮助方法,在幕后,这些方法只是迭代序列并对其进行操作。在LINQ之前就存在许多方法,可以使用IEnumerable并对其进行有用的操作,而且自那以后也出现了更多的方法。LINQ是非常有用、广泛使用和通用的方法,但这些东西都没有改变语言使用IEnumerable的基本能力。 - Servy
@Servy 您是正确的,这就是为什么我明确指出它们是_扩展方法_。虽然方便,但语法扩展方法会导致非常混乱,因为人们不知道接口明确提供了什么,而扩展方法提供了什么。 - D Stanley

1
你的属性MaliciousCode,表示实现了IEnumerable<T>接口的类对象。单独来看,IEnumerable没有实际意义,只提供了一个结构。用户需要自行实现提供的接口方法,以适合自己的方式。

编辑:这里是一个简单的示例:

private void Form3_Load(object sender, EventArgs e)
    {
        Parent parent = new Parent();
        parent.Child = new List<Child>(); // -> this is where implementer is decided.  
        //Before this line, Child property is not instantiated and is not referring to any object.
    }

    public class Parent
    {
        public IEnumerable<Child> Child { get; set; }
    }

    public class Child
    {
        public int MyProperty { get; set; }
    } 

好的,在这种特定情况下,实现IEnumerable<T>接口的类是什么? - AndreaNobili
@AndreaNobili - 你能用Java回答相同的问题吗? - 不行,C#也是一样的:通过接口看不出哪个类会实现它。你可以说一个类实现了这个接口(比如List<MaliciousCode>实现了IEnumerable<MaliciousSmall>),但无法确定在此特定情况下是否使用了List - Alexei Levenkov
更新了响应。 - danish

0

IEnumerable<T> 是 Java 的 Iterable<T> 的等价物。由于早期版本的 C# 没有泛型,因此 IEnumerable 是当时唯一可用的迭代器。您可以将其视为一种 IEnumerable<object>

大多数泛型集合类型都实现了 IEnumerable<T>,包括数组。泛型变体要求实现非泛型变体,因此大多数集合(无论是泛型还是非泛型)都实现了 IEnumerable

然而,这些迭代器不仅限于表示集合。它们提供了允许您枚举项目的方法,这些方法也可以通过算法生成项目。例如,一个理论上无限的平方数枚举可以通过一个不需要在任何地方存储这些数字的枚举来提供。

在您的情况下,IEnumerable<MaliciousSmall> MaliciousCode 属性可以从数据库中逐个产生 MaliciousSmall 对象,而不需要先将它们存储在集合对象中。

自己实现 IEnumerable<T> 需要实现 IEnumerator<T> GetEnumerator() 方法。它返回一个枚举器对象,需要实现方法 bool MoveNext()void Dispose()void Reset() 和属性 T Current { get; }

你可以通过编写大量代码来实现这些接口,也可以使用 C# 的迭代器。迭代器在幕后使用了许多编译器魔法来自动创建可枚举和枚举器。参见:迭代器 (C# 和 Visual Basic)


作为C#迭代器的一个示例,让我们使用它们来实现您的示例(我删除了设置器,因为它在这里妨碍)。
public class PagedMalicious : Shared.Paged
{
    public IEnumerable<MaliciousSmall> MaliciousCode
    {
        get
        {
            using (var conn = new SqlConnection("<my server connection>")) {
                var cmd = new SqlCommand("SELECT name, number FROM myTable", conn);
                conn.Open();
                using (var reader = cmd.ExecuteReader()) {
                    while (reader.Read()) {
                        var maliciousSmall = new MaliciousSmall {
                            Name = reader.GetString(0), 
                            Number = reader.GetInt32(1) 
                        };
                        yield return maliciousSmall;
                    }
                }
            }
        }
    }
}

每次执行yield return时,控制权都会传递回调用者,并获取下一个项。getter方法的状态保持不变,并在此处停止执行,直到调用者继续迭代并需要下一个项为止。当他这样做时,执行就会在yield return语句之后恢复。
从这个例子中可以看出,枚举以一种惰性的方式进行评估。以下代码总结了整个表格的数字。项目永远不会存储在集合中;它们是从数据库中检索并按照枚举创建的。如果您有一百万条记录,这是一个优势!(在生产代码中,您将使用SQL SUM聚合函数。)
var pagedMalicious = new PagedMalicious();
int sum = 0;
foreach (MaliciousSmall item in pagedMalicious.MaliciousCode) {
    sum += item.Number;
}

0
针对您的疑问: 是的和是的 接口本身不能实现任何东西,但您可以分配一个恶意小型数组或List<MaliciousSmall>。它们都实现了IEnumerable<MaliciousSmall>。

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接