我知道接口不能实例化,但可以创建实现接口的对象实例。
但是在这个例子中,我正在使用一个接口变量,它永远不会实例化为类的对象。
public static IEnumerable<Person> GetPeople()
{
IEnumerable<Person> people = AMethod<IEnumerable<Person>>();
return people;
}
var people = MyClass.GetPeople();
// ... operations on the people variable
因此,我绝不是指数组或列表或实现IEnumerable的另一种类型。当我调试此代码时,调试器显示 people 变量的类型为 IEnumerable
AMethod<IEnumerable<Person>>()
这是调用一个看起来像这样的方法:
public T AMethod <T> {
// Do something to create an instance of T
}
IEnumerable<Person>
是传递给AMethod
签名<T>
的内容。这是一种通用方法。
这样做的结果是正在创建一些具体的东西(如Person[]
、List<Person>
等)以适应接口IEnumerable<Person>
。但它是由AMethod
创建的,而不是您发布的任何代码。
如果你对人到底是什么类型感到好奇,不能只看AMethod
,打电话Type concreteType = people.GetType();
会告诉你。
像IEnumerable<T>
这样的抽象的有用之处在于它是由许多类实现的。
例如:
List<T>
是IEnumerable<T>
。
ImmutableArray<T>
就是IEnumerable<T>
。
数组T[]
是一个IEnumerable<T>
(尽管它有点编译器的魔力,请参阅注释)。
说一个方法返回一个IEnumerable<T>
只是意味着这个方法将返回实现IEnumerable
的东西,你不需要关心是哪一个,但你可以依靠接口被实现的事实,从而使用foreach
和linq方法在其上。
如果您所需要的只是能够使用foreach
枚举某些内容或应用 linq 方法,则可以隐藏实际实现(数组或列表或其他任何内容)并改用IEnumerable
。
一般来说,为什么要使用抽象?这是一个简短的开始答案。
想象一下,你依赖于一个特定的实现,比如List<T>
,然后你把它作为List<T>
参数等传递给其他函数......你的代码像这样增长。
有一天,您意识到由于某种原因(为了性能,或者因为您现在使用另一个返回某种其他类型的类的库),List<T>
不是一个好的选择。
现在你需要解开依赖于这个List
假设的一切。