根据我对接口的理解,为了使用它们,你必须通过在冒号后面添加接口的名称来声明一个类正在实现它,然后实现这些方法。
我目前正在学习枚举器,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;}
答案 0 :(得分:6)
使用yield
keyword时会有额外的魔力,即创建迭代器块。编译器会为您创建一个状态机。
所以C#在这里有一个特殊的功能,它只有IEnumerable<>
这个功能。所以它是C#语言是神奇的。
接口IEnumerable<>
本身就是一种无聊的普通类型。没有魔法。
注意:从技术上讲,yield
魔法在“正式”返回类型为IEnumerable<>
,IEnumerator<>
,IEnumerable
或IEnumerator
时有效,但通常你使用第一个。当然,不要使用非通用的。
答案 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 return
或IEnumerable
(通用和非通用版本)的方法上查找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设计,以及可能还有其他一百个原因。