65.9K
CodeProject 正在变化。 阅读更多。
Home

C# 中的接口(初学者指南)

starIconstarIconstarIconstarIcon
emptyStarIcon
starIcon

4.60/5 (379投票s)

2007年5月10日

8分钟阅读

viewsIcon

1616107

C# 接口教程

引言

C# 中的接口提供了一种实现运行时多态性的方法。使用接口,我们可以通过相同的接口引用调用不同类中的函数,而使用虚函数,我们可以通过相同的引用调用相同继承层次结构中不同类中的函数。在事情变得复杂之前,我将使用简单明了的示例来解释接口的概念。这是一个简短的示例,展示了接口的外观。

P1.cs

class Demo
{
  public static void Main()
  {
    System.Console.WriteLine("Hello Interfaces");
  }
}

interface abc
{  
}

输出

Hello Interfaces

上面的程序可以成功编译并运行,产生预期的输出。上面的程序包含一个名为 Demo 的类,其中包含一个入口点函数 Main(),它打印 "Hello Interfaces"。上面的程序还定义了一个名为 abc 的接口。接口 abc 目前是空的。让我们向这个接口添加一些元素。

P2.cs

class Demo
{
  public static void Main()
  {
    System.Console.WriteLine("Hello Interfaces");
  }
}

interface abc
{
  int x;  
}

输出

P2.cs(11,3): error CS0525: Interfaces cannot contain fields

错误!C# 中的接口不能包含字段,即变量。上面的程序在接口 abc 中声明了一个名为 x 的整数变量。这就是 C# 编译器无法处理的地方。

P3.cs

class Demo
{
  public static void Main()
  {
    System.Console.WriteLine("Hello Interfaces");
  }
}

interface abc
{
  void xyz()
  {
     System.Console.WriteLine("In xyz");
  }  
}

输出

P3.cs(11,8): error CS0531: 'abc.xyz()': interface members cannot have a 
    definition

这次我们尝试在接口中包含一个名为 xyz() 的函数,发现这同样会惹恼 C# 编译器。它响亮地告诉我们接口成员不能有定义。这是否意味着如果我们只在接口 abc 中有一个函数声明,C# 编译器就能接受呢?让我们来找出答案。

P4.cs

class Demo
{
  public static void Main()
  {
    System.Console.WriteLine("Hello Interfaces");
  }
}

interface abc
{
  void xyz();
}

输出

Hello Interfaces

上面的程序可以成功编译并运行,产生预期的输出。最后我们让编译器满意了。C# 中的接口只能包含函数声明。现在让我们看看接口的实际应用。

接口是类以自己的方式实现的契约。这意味着接口将包含函数原型,而实现了该接口的类将必须负责定义由实现的接口声明了原型的函数。

所以是时候让我们的“新郎”类 Demo 和“新娘”接口 abc 进行“婚礼”了。

P4.cs

class Demo : abc
{
  public static void Main()
  {
    System.Console.WriteLine("Hello Interfaces");
  }
}

interface abc
{
  void xyz();
}

输出

P4.cs(1,7): error CS0535: 'Demo' does not implement interface member 
    'abc.xyz()'
P4.cs(11,8): (Location of symbol related to previous error)

嗯,在上面的程序中,类 Demo 通过 class demo : abc 这行实现了接口 abc,但一如既往,新婚夫妇之间存在一些小误会。类 Demo 需要负责定义由实现的接口 abc 声明了原型的函数。由于上面的程序中类 Demo 没有实现(即定义)由实现的接口 abc 声明了原型的函数 xyz,我们遇到了一个错误。要解决此问题,类 Demo 必须负责定义由实现的接口 abc 声明了原型的函数 xyz。这正是您在以下程序中看到的内容。

P5.cs

class Demo : abc
{
  public static void Main()
  {
    System.Console.WriteLine("Hello Interfaces");
  }

  void xyz()
  {
     System.Console.WriteLine("In xyz");
  }  
}

interface abc
{
  void xyz();
}

输出

a.cs(1,7): error CS0536: 'Demo' does not implement interface member 
    'abc.xyz()'.'Demo.xyz()' is either static, not public, 
    or has  the wrong return type.
a.cs(16,8): (Location of symbol related to previous error)
a.cs(7,8): (Location of symbol related to previous error)

又出错了!类 Demo 实现函数 xyz 并不足够。它必须通过将 xyz 的实现声明为 public 来打动新娘接口 abc。这正是以下程序所做的。

P6.cs

class Demo : abc
{
  public static void Main()
  {
    System.Console.WriteLine("Hello Interfaces");
    xyz();
  }

  public void xyz()
  {
     System.Console.WriteLine("In xyz");
  }  
}

interface abc
{
  void xyz();
}

输出

Hello Interfaces
In xyz

对了!上面的程序可以成功编译并运行,产生预期的输出。如前所述,使用接口,我们可以通过相同的接口引用调用不同类中的函数。为此,我们需要有不同的类来实现相同的接口。在上面的程序中,我们的类 Demo 实现了接口 abc。让我们再创建一个名为 Sample 的类来实现接口 abc

P7.cs

class Demo : abc
{
  public static void Main()
  {
    System.Console.WriteLine("Hello Interfaces");
    Demo refDemo = new Demo();
    refDemo.xyz();
    Sample refSample = new Sample();
    refSample.xyz();    
  }

  public void xyz()
  {
     System.Console.WriteLine("In Demo :: xyz");
  }  
}

interface abc
{
  void xyz();
}

class Sample : abc
{
  public void xyz()
  {
     System.Console.WriteLine("In Sample :: xyz");
  }  
}

输出

In Demo :: xyz
In Sample :: xyz

上面的程序可以成功编译并运行,产生预期的输出。refDemo 是类 Demo 对象的一个引用。refSample 是类 Sample 对象的一个引用。这两个类都实现了接口 abc,因此定义了它们各自对函数 xyz() 的实现。在入口点函数 Main() 中,通过引用 refDemorefSample 调用了各自类 DemoSamplexyz()

现在我们有了两个实现相同接口的不同类,是时候向您展示如何使用相同的接口引用来调用不同类中的函数了。

P8.cs

class Demo : abc
{
  public static void Main()
  {
    System.Console.WriteLine("Hello Interfaces");
    abc refabc = new Demo();
    refabc.xyz();
    abc refabc = new Sample();
    refabc.xyz();    
  }

  public void xyz()
  {
     System.Console.WriteLine("In Demo :: xyz");
  }  
}

interface abc
{
  void xyz();
}

class Sample : abc
{
  public void xyz()
  {
     System.Console.WriteLine("In Sample :: xyz");
  }  
}

输出

In Demo :: xyz
In Sample :: xyz

上面的程序可以成功编译并运行,产生预期的输出。在 Main() 中,我们有一个类型为接口 abc 的接口引用 refabc。类 Demo 对象的引用存储在 refabc 中,并通过 refabc 调用了类 Demoxyz()。接下来,类 Sample 对象的引用存储在 refabc 中,并通过 refabc 调用了类 Samplexyz()。因此,我们能够通过通用接口引用 refabc 调用属于不同类 DemoSamplexyz()

下面的程序使用 for 循环,通过一个与类实现的接口“interface abc”匹配的通用接口引用 refabc,来调用实现相同接口“interface abc”的类 DemoSample 的函数。

P9.cs

class Demo : abc
{
  public static void Main()
  {
    abc [] refabc = {new Demo(), new Sample()} ;
    for (int i = 0; i<= 1; i++)
      refabc[i].xyz();
  }

  public void xyz()
  {
     System.Console.WriteLine("In Demo :: xyz");
  }  
}

interface abc
{
  void xyz();
}

class Sample : abc
{
  public void xyz()
  {
     System.Console.WriteLine("In Sample :: xyz");
  }  
}

输出

In Demo :: xyz
In Sample :: xyz

上面的程序可以成功编译并运行,产生预期的输出。refabc 是一个类型为接口 abc 的数组。它存储了类 DemoSample 对象的引用。在 for 循环中,我们使用数组 refabc 调用了类 DemoSample 的函数 xyz()。一个类可以实现任意数量的接口。请看下面的程序。

P10.cs

class Demo : abc, def
{
  public static void Main()
  {
    System.Console.WriteLine("Hello Interfaces");
    abc refabc = new Demo();
    refabc.xyz();
  }

  public void xyz()
  {
     System.Console.WriteLine("In xyz");
  }  

  public void pqr()
  {
     System.Console.WriteLine("In xyz");
  }
}

interface abc
{
  void xyz();
}

interface def
{
  void pqr();
}

输出

Hello Interfaces
In xyz

上面的程序可以成功编译并运行,产生预期的输出。类 Demo 实现了接口 abc,从而实现了函数 xyz()。类 Demo 还实现了接口 def,从而实现了函数 pqr()。类型为 Interface abc 的变量 ref abc 指向类 Demo 的对象。接下来,通过 refabc 调用 Demoxyz(),因为 refabc 是 Interface abc 类型的变量,它包含了函数 xyz() 的原型。

P11.cs

class Demo : abc, def
{
  public static void Main()
  {
    System.Console.WriteLine("Hello Interfaces");
    abc refabc = new Demo();
    refabc.xyz();
    refabc.pqr();
  }

  public void xyz()
  {
     System.Console.WriteLine("In xyz");
  }  

  public void pqr()
  {
     System.Console.WriteLine("In xyz");
  }
}

interface abc
{
  void xyz();
}

interface def
{
  void pqr();
}

输出

P11.cs(9,5): error CS0117: 'abc' does not contain a definition for 'pqr'

错误!尝试通过 refabc 调用 Demopqr() 会失败,因为 refabc 是 Interface abc 类型的变量,它包含函数 xyz() 的原型,而不是 pqr()。可以通过类型为 Interface def 的引用变量来调用 Demopqr(),因为接口 def 包含了函数 pqr() 的原型。这正是以下程序所做的。

P12.cs

class Demo : abc, def
{
  public static void Main()
  {
    System.Console.WriteLine("Hello Interfaces");
    Demo refDemo = new Demo();
    abc refabc = refDemo;
    refabc.xyz();
    def refdef = refDemo;
    refdef.pqr();
  }

  public void xyz()
  {
     System.Console.WriteLine("In xyz");
  }  

  public void pqr()
  {
     System.Console.WriteLine("In pqr");
  }
}

interface abc
{
  void xyz();
}

interface def
{
  void pqr();
}

输出

Hello Interfaces
In xyz 
In pqr

上面的程序可以成功编译并运行,产生预期的输出。类 Demo 实现了接口 abcdef。创建了一个类 Demo 的对象,并将其引用存储在 refDemo 中。类型为 Interface abc 的变量 refabc 指向类 Demo 的对象。接下来,通过 refabc 调用 Demoxyz(),因为 refabc 是 Interface abc 类型的变量,它包含了函数 xyz() 的原型。同样,类型为 Interface def 的变量 refdef 指向类 Demo 的对象。接下来,通过 refdef 调用 Demopqr(),因为 refdef 是 Interface def 类型的变量,它包含了函数 pqr() 的原型。

P13.cs

class Demo : abc, def
{
  public static void Main()
  {
    System.Console.WriteLine("Hello Interfaces");
    Demo refDemo = new Demo();
    abc refabc = refDemo;
    refabc.xyz();
    def refdef = refDemo;
    refdef.xyz();
  }

  public void xyz()
  {
     System.Console.WriteLine("In xyz");
  }  
}

interface abc
{
  void xyz();
}

interface def
{
  void xyz();
}

输出

Hello Interfaces
In xyz
In xyz

上面的程序可以成功编译并运行,产生预期的输出。接口 abcdef 都声明了函数 xyz() 的原型。类 Demo 同时实现了接口 abcdef,并定义了函数 xyz()。因此,在将类 Demo 的对象引用存储在 refabcrefdef 中后,我们可以通过任一接口引用变量(refabcrefdef)调用函数 xyz()。这就产生了一个问题:我们如何在类 Demo 中拥有一个特定于接口 abcxyz 实现,以及一个特定于 defxyz 实现?嗯,为此我们需要使用如下面程序中的完全限定名。

P14.cs

class Demo : abc, def
{
  public static void Main()
  {
    System.Console.WriteLine("Hello Interfaces");
    Demo refDemo = new Demo();
    abc refabc = refDemo;
    refabc.xyz();
    def refdef = refDemo;
    refdef.xyz();
  }

  public void abc.xyz()
  {
     System.Console.WriteLine("In abc.xyz");
  }  

  public void def.xyz()
  {
     System.Console.WriteLine("In def.xyz");
  }  

}

interface abc
{
  void xyz();
}

interface def
{
  void xyz();
}

输出

a.cs(13,15): error CS0106: The modifier 'public' is not valid for this item
a.cs(18,15): error CS0106: The modifier 'public' is not valid for this item

糟糕!我们使用了完全限定名,结果却出现了错误。这是因为当我们对作为接口一部分的函数使用完全限定名时,编译器不需要像 public 这样的修饰符。所以我们决定从上面的程序中删除访问修饰符 public

P15.cs

class Demo : abc, def
{
  public static void Main()
  {
    System.Console.WriteLine("Hello Interfaces");
    Demo refDemo = new Demo();
    abc refabc = refDemo;
    refabc.xyz();
    def refdef = refDemo;
    refdef.xyz();
  }

  void abc.xyz()
  {
     System.Console.WriteLine("In abc.xyz");
  }  

  void def.xyz()
  {
     System.Console.WriteLine("In def.xyz");
  }  

}

interface abc
{
  void xyz();
}

interface def
{
  void xyz();
}

输出

Hello Interfaces
In abc.xyz
In def.xyz

上面的程序可以成功编译并运行,产生预期的输出。完全限定命名系统允许我们定义具有相同函数原型的接口。在上面的例子中,接口 abcdef 包含函数 xyz() 的相同函数原型。类 Demo 实现了这两个接口。使用完全限定名,它定义了特定于接口 abcxyz 实现,以及特定于接口 defxyz 实现。

P16.cs

class Demo : def
{
  public static void Main()
  {
    System.Console.WriteLine("Hello Interfaces");
    Demo refDemo = new Demo();
    def refdef = refDemo;
    refdef.xyz();
    refdef.pqr();
  }

  public void xyz()
  {
     System.Console.WriteLine("In xyz");
  }  

  public void pqr()
  {
     System.Console.WriteLine("In pqr");
  }  

}

interface abc
{
  void xyz();
}

interface def : abc
{
  void pqr();
}

输出

Hello Interfaces
In xyz
In pqr

上面的程序可以成功编译并运行,产生预期的输出。接口支持继承。接口 def 继承了接口 abc 的原型。类 Demo 实现了接口 def。接口变量 refdef 存储了类 Demo 对象的引用。通过接口引用变量 refdef 调用了类 Demo 的函数 xyz()pqr()

在接口 def 继承自接口 abc 之后,您认为 xyz()pqr() 的完全限定名会是什么?

P17.cs

class Demo : def
{
  public static void Main()
  {
    System.Console.WriteLine("Hello Interfaces");
    Demo refDemo = new Demo();
    def refdef = refDemo;
    refdef.xyz();
    refdef.pqr();
  }

  void def.xyz()
  {
     System.Console.WriteLine("In xyz");
  }  

  void def.pqr()
  {
     System.Console.WriteLine("In pqr");
  }  

}

interface abc
{
  void xyz();
}

interface def : abc
{
  void pqr();
}

输出

P17.cs(12,8): error CS0539: 'def.xyz' in explicit interface declaration is 
    not a member of interface
P17.cs(29,11): (Location of symbol related to previous error)
P17.cs(1,7): error CS0535: 'Demo' does not implement interface member 
    'abc.xyz()'
P17.cs(26,8): (Location of symbol related to previous error)

糟糕!函数 xyz 的原型是接口 abc 的原始成员。因此,即使接口 def 继承自接口 abc,函数 xyz() 的完全限定名仍然是 abc.xyz,而不是像上面程序中那样是 def.xyz。事实上,我们遇到了编译器错误。这可以通过使用函数 xyz() 的正确完全限定名来修复,如下面的程序所示。

P18.cs

class Demo : def
{
  public static void Main()
  {
    System.Console.WriteLine("Hello Interfaces");
    Demo refDemo = new Demo();
    def refdef = refDemo;
    refdef.xyz();
    refdef.pqr();
  }

  void abc.xyz()
  {
     System.Console.WriteLine("In xyz");
  }  

  void def.pqr()
  {
     System.Console.WriteLine("In pqr");
  }  

}

interface abc
{
  void xyz();
}

interface def : abc
{
  void pqr();
}

输出

Hello Interfaces
In xyz
In pqr

上面的程序可以成功编译并运行,产生预期的输出。但是在使用完全限定函数名时有一些陷阱。

P19.cs

class Demo : def
{
  public static void Main()
  {
    System.Console.WriteLine("Hello Interfaces");
    Demo refDemo = new Demo();
    refDemo.xyz();
    refDemo.pqr();
  }

  void abc.xyz()
  {
     System.Console.WriteLine("In xyz");
  }  

  void def.pqr()
  {
     System.Console.WriteLine("In pqr");
  }  

}

interface abc
{
  void xyz();
}

interface def : abc
{
  void pqr();
}

输出

P19.cs(7,5): error CS0117: 'Demo' does not contain a definition for 'xyz'
P19.cs(8,5): error CS0117: 'Demo' does not contain a definition for 'pqr'

上面的程序未能通过编译。refDemo 指向类 Demo 的一个对象。我们正在使用 refDemo() 调用 xyz()。对于 C# 来说,xyz()ddd.xyz() 是两回事。它知道 abc.xyz() 存在。它也知道类中根本没有单独的 xyz()。这就是为什么我们会收到一个错误,说类 Demo 没有定义 xyz()。对于 C# 来说,只有类型为 abc 的接口引用才能调用 abc.xyz()。同样,只有类型为 def 的接口引用才能调用 def.pqr()

© . All rights reserved.