IEnumerable这样的接口如何在没有正确实现的情况下工作?

时间:2014-01-05 00:01:16

标签: c# interface ienumerable

根据我对接口的理解,为了使用它们,你必须通过在冒号后面添加接口的名称来声明一个类正在实现它,然后实现这些方法。

我目前正在学习枚举器,IEnumerable等等,这让我很困惑。这是我的意思的一个例子:

static IEnumerable<int> Fibs(int fibCount)
{
    for (int i = 0, prevFib = 1, curFib = 1; i < fibCount; i++) {
        yield return prevFib;
        int newFib = prevFib + curFib;
        prevFib = curFib;
        curFib = newFib;
    }
}

IEnumerable似乎是一个普通的界面,我甚至检查了方法定义,这就是它的样子。

我怎么可能在方法定义中使用接口作为类型/返回类型,以及何时/如何知道我应该使用某些接口作为此示例中的类型?

编辑:我真的怀疑它与yield关键字有什么关系,因为很多接口都以这种方式用作属性,例如在模型中的MVC中,并像它一样传递给Views。例如:

public IEnumerable<Category> Categories {get;set;}

4 个答案:

答案 0 :(得分:6)

使用yield keyword时会有额外的魔力,即创建迭代器块。编译器会为您创建一个状态机。

所以C#在这里有一个特殊的功能,它只有IEnumerable<>这个功能。所以它是C#语言是神奇的。

接口IEnumerable<>本身就是一种无聊的普通类型。没有魔法。

注意:从技术上讲,yield魔法在“正式”返回类型为IEnumerable<>IEnumerator<>IEnumerableIEnumerator时有效,但通常你使用第一个。当然,不要使用非通用的。

答案 1 :(得分:2)

IEnumerable是一个特例。 yield return语句指示编译器添加实现IEnumerable的代码。
至于您的修改
如果使用接口作为属性的类型,则可以分配实现此接口的类的任何对象,并且该属性将返回实现此接口的对象。在您的示例中,可以将实现IEnumerable<Category>的任何类别集合分配给属性,例如一个List<Category>。与仅使用List<Category>相比,使用界面可以分配更广泛的对象。接口定义了与属性相关的抽象需求。

答案 2 :(得分:0)

在C#中,接口是一种特殊的“种类”,它在几个关键方面与类不同:

  • 您不能包含其方法的任何实施代码。
  • 单个类可以根据需要“继承”(称为“实现”)。
  • 您无法实例化接口的new实例。

(还有一些与接口特别相关的语言功能,例如显式实现,但这些对于此讨论并不重要。)

除此之外,接口几乎可以在任何可以使用任何其他引用类型的地方使用。这包括使用接口类型定义字段,属性或局部变量,或者将它们用作方法的参数类型或返回类型。

诀窍是,如果你将一个属性定义为IEnumerable<int>,并且你想设置它的值,你就不能这样做:

public IEnumerable<int> Numbers { get; set; }
...
this.Numbers = new IEnumerable<int>();

这是一个错误。你不能创建一个新的界面实例,因为它只是一个“模板” - 它实际上什么都没做“落后”。但是,可以执行此操作:

public IEnumerable<int> Numbers { get; set; }
...
this.Numbers = new List<int>();

因为List<T>实现了IEnumerable<T>,编译器会自动进行类型转换以使赋值工作。可以将任何实现IEnumerable<>的具体类分配给类型为IEnumerable<>的属性,这就是您经常看到接口属性类型的原因。它允许您更改基础具体类型(可能您希望将List<T>更改为ObservableCollection<T>,但您的班级用户既不知道也不关心您。

对于具有接口返回类型的方法也是如此,除了这里有一个额外的选项,C#作为奖励投入:

public IEnumerable<string> GetName()
{
    // this fails.
    return new IEnumerable<string>();

    // this works.
    return new List<String>();

    // this also works because magic!~
    yield return "hello";
    yield return "there";
    yield return "!";
}

最后一种情况是C#提供的一种特殊形式的“语法糖”,因为它是如此常见的要求。正如其他人所提到的,编译器专门在返回yield returnIEnumerable(通用和非通用版本)的方法上查找IEnumerator语句,并对代码进行了大量的重写。

在幕后,C#正在创建一个隐藏类,它实现IEnumerable<string>,并实现它的GetEnumerator方法,以返回提供这三个字符串值的IEnumerator<string>对象。这将是你写的很多样板代码,尽管你自己可以自己编写。在以前的C#版本中,没有yield,您必须自己编写。

如果您真的想知道,您可以在其他地方找到C#等效here。本质上,它采用包含yield语句的方法,并从中创建IEnumerator<>.MoveNext方法,但将其转换为状态机。每次消费者在同一个实例上调用MoveNext时,它都会使用等效的标签和goto语句跳回到正确的位置。另外,据我所知,它可以做你在C#中实际无法做的事情(它跳入和跳出循环),但这在IL代码中是合法的,所以它的实现比你自己编写的更有效。 / p>

但是一旦你超越yield关键字的秘密原因,你仍然在做同样的事情。您仍在创建一个实现IEnumerable<>的类,并将其用作方法的返回值。

答案 3 :(得分:0)

对于您的特定示例,即使方法返回类型为IEnumerable<int>,实际返回类型也将是实现IEnumerable<int>的类型。正如其他人所提到的,'yield return'会产生一种实现IEnumerable<int>的类型。在这种特定情况下,您不知道该类型是什么。当我通过调试器运行此操作并执行result.GetType()(从result方法返回Fibs时)时,我发现result的类型为<Fibs>d__0 ,这听起来有点奇怪。因此,我们可以将它视为IEnumerable<int>,而不是担心这种奇怪的类型,因为我们真正希望能够做的就是迭代它,这是IEnumerable<int>的行为暴露。

这是你的具体例子,但其他地方的想法是一样的。通过使用接口,您可以说您并不关心使用/返回的确切类型,只要它暴露某些行为或属性即可。例如,如果我有一个公开方法IFoo的接口DoSomething(),并且我有一个返回IFoo的方法,那么我可以返回任何实现IFoo的方法,但是没有无论我返回什么,该方法的调用者确信它可以DoSomething()与对象。同样,如果我的方法采用IFoo,那么该方法可以确保它能够DoSomething()使用该参数。

对我而言,界面也可以帮助我设计我的课程。例如,我首先编写接口以确定该类能够执行和拥有的重要内容,然后创建实现该接口的具体类。当我编写使用该类的代码时,我会询问接口而不是具体类型。

使用接口还有各种其他原因,例如创建用于测试的模拟,用于依赖注入,用于流畅的API设计,以及可能还有其他一百个原因。