关于c#:左侧接口


Interface on left side

我知道我们不能创建Interface的实例。但是为什么我们可以在左边写Interface?这些只是接口的引用吗(对于实现该类的类)而不是实例?

如果我们不能创建接口实例,那么这个例子中的P是什么类型?

1
2
string[] names = {"John","Bob","Mark"};
IEnumerable<string> P = names.Where(n => n.Contains('o'));


PIEnumerable型。

这里声明局部变量P的类型。

P持有IEnumerable的类型,当您不直接实例化接口时,您可以实例化一个具体的类,但只持有对接口的引用。

Where()调用返回的实例只需要遵守IEnumerable定义的契约。


不能创建接口的实例,但可以创建实现接口的某个实例。

您在示例中所做的只是获取一些(P的内容),这些内容是已知的,可以遵守IEnumerable定义的合同。

如果用稍微简单的术语来思考这个概念,就更容易理解了:

1
IBeast mrJuggles = new Tiger();

胡格斯先生现在是一只老虎,但同时也是一只江户(15岁)。我们没有明确地创建一个IBeast,我们只是指定我们所创建的东西将表现为一个IBeast,因此我们可以将它视为一个IBeast


我将试着从概念上解释这一点。

but why we can write Interface on left side?

我们可以,因为它的意思是:"这个对象是实现这个接口的东西"。

接口本身不是"某物"——这就是为什么你不能直接实例化它——它只是一个契约。

接口是无用的,除非您定义了一些保证实现它所表示的契约的对象(并公开此类方法等)。这就是你对接口的使用。没有这个,他们就没有意义了。

合同本身是一个抽象概念。它需要一些东西来体现它——一个充满它的对象。

请看下面的示例:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
using System.Collections.Generic;

namespace App
{
    class Program
    {
        class Example
        {
            List<string> list = new List<string> {"a","b","c" };

            public IEnumerable<string> AsEnumerable()
            {
                return list;
            }
        }

        static void Main(string[] args)
        {
            IEnumerable<string> foo = new Example().AsEnumerable();
            List<string> bar = (List<string>)foo;
        }
    }
}

你知道它不会坠毁吗?

该行中的IEnumerable

1
IEnumerable<string> foo = new Example().AsEnumerable();

实际上意味着:"foo是我们知道实现IEnumerable的东西"。

但仍然是这样。它不仅可以是IEnumerable,而且只能是。IEnumerable只是我们碰巧知道的一些事情。

否则我们就不能把它扔回List,对吗?(这实际上是C中的一个常见警告,因为调用者可以进行这种转换,因此可以访问AddRemove方法,并干扰我们列表的内容,即使我们不打算这样做。这是封装泄漏)。

换言之:我们看待这个对象的方式是IEnumerable

编辑:

正如@kjartan建议的那样,您可以这样验证这一切:

1
2
3
4
bool isFooIEnumerable = foo is IEnumerable<string>; // it's true
bool isBarIEnumerable = bar is IEnumerable<string>; // true again
bool isFooList = foo is List<string>; // yup. true
bool isBarList = bar is List<string>; // true all the way


在左侧声明变量的类型。

IEnumerable stringEnumerator

您告诉编译器,从现在起,在值stringEnumerator下,可以提供对字符串枚举器对象的空引用或引用。

所以当你有这样的表情

IEnumerable P = names.where(n => n.Contains('o'));

你说,创建一个变量P,它将代表IEnumerable,并将names.where(n => n.Contains('o'));的结果赋给它。


确切地说,如果赋值中的左侧部分被类型化为接口类型,则表示左侧部分(变量或类似变量)是对实现所述接口的类的任何实例的引用。

在本例中,P是对某个实现IEnumerable的内部框架类实例的引用。因为它是一些内部类(假定隐藏在可枚举的实现中),所以我们甚至没有访问类名的权限(因此无法声明该类型的任何变量)。事实上,微软可能正在改变由Where返回的每个.NET版本的实际类。然而,我们也不会注意到,也不必担心,因为我们需要知道的是,实现IEnumerable的某些内容将被返回,因此我们也只声明变量P引用实现IEnumerable的某些内容,而不是作为具体类。