第16章 接口 笔记

第16章 接口 笔记 25.06.22

16.1 什么是接口

接口是声明一组函数成员,而不进行实现的引用类型,只能用类和结构来实现接口。

一个函数内不能传入多个类的实例做为参数,可以通过接口来实现。

csharp 复制代码
// 声明接口
interface IInfo
{
    string GetName();
    string GetAge();
}

// 声明实现接口的CA类
class CA:IInfo
{
    public string Name;
    public int Age;
    public string GetName() {return Name;}
    public string GetAge() {return Age.ToString();}
}

class CB:IInfo
{
    public string First;
    public string Last;
    public double PersonsAge;
    public string GetName() {return First + " " + Last;}
    public string GetAge() {return PersonAge.ToString();}
}

class Programe
{
    // 传入接口的引用
    static void PrintInfo(IInfo item)
    {
        Console.WriteLine("Name: {0}, Age {1}", item.GetName(), item.GetAge());
    }
    
    static void Main()
    {
        CA a = new CA() {Name = "John Doe", Age = 35};
        CB b = new CB() { First = "Jane", Last = "Doe", PersonAge = 33};
   
        // 对象的引用自动转化为 它们实现的接口的引用
        PrintInfo(a);
        printInfo(b);
    }
}

// output
 Name: John Doe, Age 35
 Name: Jane Doe, Age 33

使用 IComparable 接口的示例

整数数组排序

c# 复制代码
var myInt = new [] { 20, 4, 16, 9, 2 };    
Array.Sort(myInt);                         
foreach (var i in myInt)                   
    Console.Write($"{ i } ");

// This code produces the following output
2 4 9 16 20

Array类的Sort方法无法对对象数组进行排序,它不知道如何比较对象以及如何进行排序。

Array 类的 Sort 方法依赖于 IComparable 接口,其声明在 BCL 中,只包含唯一的 CompareTo 方法,该方法目前未实现。

IComparable接口 示例

c# 复制代码
public interface IComparable
{
    int CompareTo( object obj );
}

调用 CompareTo 方法时,应该返回如下值:

  • 负数:当前对象 < 参数对象。

  • 正数:当前对象 > 参数对象。

  • 0:两个对象相等。

为自定义类实现 IComparable 接口后,就可以使用 Array.Sort 方法进行排序

要实现一个接口,类或结构必须做两件事:

在基类类别中列出接口的名称

为接口的每一个成员提供实现

c# 复制代码
// 类实现接口
class MyClass : IComparable
{
    public int TheValue;
    public int CompareTo(object obj)   // Implementation of interface method
    {
        MyClass mc = (MyClass)obj;
        if (this.TheValue < mc.TheValue) return -1;
        if (this.TheValue > mc.TheValue) return  1;
        return 0;
    }
}

class Program {
    static void PrintOut(string s, MyClass[] mc)
    {
        Console.Write(s);
        foreach (var m in mc)
            Console.Write($"{ m.TheValue } ");
        Console.WriteLine("");
    }
    static void Main()
    {
        var myInt = new [] { 20, 4, 16, 9, 2 };
        
        // Create array of MyClass objs.
        MyClass[] mcArr = new MyClass[5];
        // Initialize the array.
        for (int i = 0; i < 5; i++)           
        {
            mcArr[i] = new MyClass();
            mcArr[i].TheValue = myInt[i];
        }
        PrintOut("Initial Order:  ", mcArr);  // Print the initial array.
        // Sort the array.
        Array.Sort(mcArr);                    
        
        PrintOut("Sorted Order:   ", mcArr);  // Print the sorted array.
    }
}

// output
Initial Order:  20 4 16 9 2
Sorted Order:   2 4 9 16 20

16.2 声明接口

接口声明不能包含以下成员:

  • 数据成员。

  • 静态成员。

接口声明只能包含如下类型的非静态成员:

  • 方法。

  • 属性。

  • 事件。

  • 索引器。

函数声明不能包含任何实现代码,使用分号代替函数主体。

接口名称必须从大写的 I 开始。

可以声明分部接口。

c# 复制代码
interface IMyInterface1
{   
    //Semicolon in place of body
    int    DoStuff ( int nVar1, long lVar2 );
    double DoOtherStuff( string s, long x );
}  

接口和接口成员的访问性有一些不同:

接口声明可以有任何的访问修饰符。

接口成员是隐式 public,不能有任何访问修饰符。

c# 复制代码
// 访问修饰符 可以用:public、protected、private、internal
public interface IMyInterface2
{
    // 错误
    private int Method1( int nVar1, long lVar2 );          
} 

16.3 实现接口

要实现接口,类和结构必须:

  1. 在基类列表中包含接口名称。
  2. 为每一个接口成员提供实现。
c# 复制代码
class MyClass: IMyInterface1
{
    int    DoStuff     ( int nVar1, long lVar2 )
    { 
        // ... 
    }                                       
    
    double DoOtherStuff( string s, long x )
    { 
        // ... 
    }                                     
}

重要事项如下:

  1. 必须实现接口的所有成员。
  2. 基类名称必须放在所有接口之前。
c# 复制代码
// 基类名、接口名
class Derived : MyBaseClass, IIfc1, IEnumerable, IComparable
{
    ...
}

简单接口的示例

c# 复制代码
// Declare interface.
interface IIfc1   
{                        
    // Semicolon in place of body
    void PrintOut(string s);
}

//Implement interface
class MyClass : IIfc1 // Declare class.
{

    public void PrintOut(string s)                  // Implementation
    {
        Console.WriteLine($"Calling through:  {s }");
    }
}

class Program
{
    static void Main()
    {
        MyClass mc = new MyClass();                  // Create instance.
        mc.PrintOut("object");                       // Call method.
    }
}

16.4 接口是引用类型

接口是引用类型

不能通过类对象的成员访问接口,只能通过将类对象引用强制转换为接口类型来获取接口引用。

有了接口引用,可以使用点语法来调用接口的成员。

注意:接口引用不能调用类中不属于接口的其他成员。

c# 复制代码
interface IIfc1
{
    void PrintOut(string s);
}

class MyClass : IIfc1
{
    public void PrintOut(string s)
    {
        Console.WriteLine($"Calling through:  { s }");
    }
}

class Program
{
    static void Main()
    {
        MyClass mc = new MyClass();  // Create class object.
        // Call class object implementation method.
        mc.PrintOut("object");       

        // Cast class object ref to interface ref.        
        IIfc1 ifc = (IIfc1)mc;       

        ifc.PrintOut("interface");   // Call interface method.
    }
}

// output
 Calling through:  object
 Calling through:  interface

16.5 接口和 as 运算符

将类对象引用强制转换为类未实现的接口引用,则会抛出异常。

使用 as 运算符可以避免抛出异常:

  • 如果实现了类接口,则 as 返回指向接口的引用。
  • 如果未实现接口,则 as 返回 null,而不抛出异常。
c# 复制代码
// Acts like cast: (ILiveBirth)a
ILiveBirth b = a as ILiveBirth;        

if (b != null)
    
    Console.WriteLine($"Baby is called: {b.BabyCalled() }");

16.6 实现多个接口

  1. 类或结构可以实现任意数量的接口
  2. 所有实现的接口必须列在基类列表中,以逗号分隔(在基类名称之后,如果有的话)。
c# 复制代码
// Declare interface.
interface IDataRetrieve { int GetData(); }           

// Declare interface.
interface IDataStore    { void SetData( int x ); }

// Interface Interface
class MyData: IDataRetrieve, IDataStore              
{

    int Mem1;                                         
    public int  GetData()        { return Mem1; }
    public void SetData( int x ) { Mem1 = x; }   
}

class Program
{
    static void Main()                         
    {
        MyData data = new MyData();
        data.SetData( 5 );
        Console.WriteLine($"Value = {data.GetData() }");
    }
}

// output
Value = 5

16.7 实现具有重复成员的接口

由于类可以实现任意数量的接口,有可能两个或多个接口成员具有相同的签名和返回类型,

如果一个类实现了多个接口,并且其中一些接口具有相同的成员,则类可以实现单个成员来同时匹配所有重复成员的接口。

类中用一个成员来匹配多个接口中的重复成员

c# 复制代码
interface IIfc1
{
    void PrintOut(string s);
}
interface IIfc2
{
    void PrintOut(string t);
}

// Implement both interfaces.
class MyClass : IIfc1, IIfc2             
{
    // Single implementation for both
    public void PrintOut(string s)        
    {
        Console.WriteLine($"Calling through:  { s }");
    }
}

class Program
{
    static void Main()
    {
        MyClass mc = new MyClass();
        mc.PrintOut("object");
    }
}

// output
 Calling through:  object

16.8 多个接口的引用

如果类实现了多个接口,可以获取每个接口的独立引用

c# 复制代码
interface IIfc1
{
    void PrintOut(string s);
}
interface IIfc2
{
    void PrintOut(string t);
}

// Implement both interfaces.
class MyClass : IIfc1, IIfc2             
{
    // Single implementation for both
    public void PrintOut(string s)        
    {
        Console.WriteLine($"Calling through:  { s }");
    }
}

class Program
{
    static void Main()
    {
        MyClass mc = new MyClass();
        // 获取接口的引用
        IIfc1 ifc1 = (IIfc1) mc;                 
        IIfc2 ifc2 = (IIfc2) mc;                 
        
        // 从类对象调用
        mc.PrintOut("object");     
        // 从接口调用
        ifc1.PrintOut("interface 1");           
        ifc2.PrintOut("interface 2");           
    }
}

// output
 Calling through:  object
 Calling through:  interface 1
 Calling through:  interface 2

16.9 派生成员作为实现

实现接口的类可以从基类继承实现的代码。

c# 复制代码
interface IIfc1 { void PrintOut(string s); }

class MyBaseClass                                   
{

    public void PrintOut(string s)                   
    {
        Console.WriteLine($"Calling through:  { s }");
    }

}

class Derived : MyBaseClass, IIfc1                 
{
}

class Program {
    static void Main()
    {
        Derived d = new Derived();                    
        d.PrintOut("object.");                        
    }
}

16.10 显示接口成员实现

可以创建显示接口成员以实现相同接口的分离,

显示接口成员实现,主要特性:

位于实现了接口的类或结构中,

使用限定接口名称声明,由接口名称、成员函数、点分隔符组成

限定接口名称的声明如下:

c# 复制代码
// 声明接口
interface IIfc1 { void PrintOut(string s); } 
interface IIfc2 { void PrintOut(string t); }

class MyClass : IIfc1, IIfc2
{
    // 限定接口名称
    void IIfc1.PrintOut(string s)            
    {  
        Console.WriteLine($"IIfc1:  {s }");
    }

    // Qualified interface name
    void IIfc2.PrintOut(string s)            
    {      
        Console.WriteLine($"IIfc2:  {s }");
    }
}

class Program
{
    static void Main()
    {
        MyClass mc = new MyClass();
        
        IIfc1 ifc1 = (IIfc1) mc;              
        ifc1.PrintOut("interface 1");
        
        IIfc2 ifc2 = (IIfc2) mc;              
        ifc2.PrintOut("interface 2");         
    }
}

// output
IIfc1:  interface 1
IIfc2:  interface 2

如果有显式接口成员实现,类级别实现是允许的,不是必须的,

因此,对于成员函数而言,有如下三种实现方法:

1.类级别实现(不依赖接口,专门提供给类对象调用)。

2.显示接口成员实现(依赖接口,专门提供给接口调用)。

3.类级别和显示接口成员实现(类和接口都能调用)。

显示接口成员实现只能通过接口来访问:

c# 复制代码
class MyClass : IIfc1
{
    // 显式接口实现
    void IIfc1.PrintOut(string s)      
    {
        Console.WriteLine("IIfc1");
    }

    public void Method1()
    {
        // 编译错误
        PrintOut("...");  
        // 编译错误
        this.PrintOut("...");

        // 转换为接口引用
        ((IIfc1)this).PrintOut("...");  // OK, call method.
    }     
} 

16.11 接口可以继承接口

类在基类列表中只能有一个类名;而接口可以有任意多个接口。

c# 复制代码
interface IDataI0 : IDataRetrieve, IDataStore
{
	// ...
}
  • 列表中的接口本身也可以继承其他接口。
  • 继承后的接口包含自己的成员和所有继承接口的成员。
c# 复制代码
using System;

// 定义数据检索接口
interface IDataRetrieve {
    int GetData();
}

// 定义数据存储接口
interface IDataStore {
    void SetData(int value);
}

// 组合接口
interface IDataIO : IDataRetrieve, IDataStore {}

// 具体实现类
class MyData : IDataIO {
    private int nPrivateData;

    public int GetData() {
        return nPrivateData;
    }

    public void SetData(int value) {
        nPrivateData = value;
    }
}

class Program {
    static void Main(string[] args) {
        MyData data = new MyData();
        data.SetData(5);
        Console.WriteLine("当前数据:{0}" + data.GetData());
    }
}

16.12 不同类实现一个接口的示例

c# 复制代码
interface ILiveBirth                           
{
    string BabyCalled();
}

class Animal { }     

class Cat : Animal, ILiveBirth                 
{
    string ILiveBirth.BabyCalled()
    { return "kitten"; }
}

class Dog : Animal, ILiveBirth                 
{
    string ILiveBirth.BabyCalled()
    { return "puppy"; }
}

class Bird : Animal                            
{
}

class Program
{
    static void Main()
    {
        Animal[] animalArray = new Animal[3];   // Create Animal array.
        animalArray[0] = new Cat();             // Insert Cat class object.
        // Insert Bird class object.
        animalArray[1] = new Bird();            
        // Insert Dog class object.
        animalArray[2] = new Dog();  
        
        // Cycle through array.
        foreach( Animal a in animalArray )      
        {
            ILiveBirth b = a as ILiveBirth;      
            
            
            
            // if implements ILiveBirth...
            if (b != null)
                Console.WriteLine($"Baby is called: { b.BabyCalled() }");
        }
    }
}

// output 
Baby is called: kitten
Baby is called: puppy
相关推荐
孞㐑¥1 小时前
Linux之Socket 编程 UDP
linux·服务器·c++·经验分享·笔记·网络协议·udp
sealaugh324 小时前
aws(学习笔记第四十八课) appsync-graphql-dynamodb
笔记·学习·aws
lijingguang5 小时前
在C#中根据URL下载文件并保存到本地,可以使用以下方法(推荐使用现代异步方式)
开发语言·c#
¥-oriented5 小时前
【C#中路径相关的概念】
开发语言·c#
ArabySide6 小时前
【WCF】通过AOP实现基于JWT的授权与鉴权的实践
c#·jwt·aop·wcf
xiaowu0806 小时前
C# Task异步的常用方法
c#
阿蒙Amon6 小时前
C# Linq to Objects 详解:集合处理的终极方案
c#·solr·linq
钢铁男儿6 小时前
C# 委托(调用带引用参数的委托)
java·mysql·c#
freexyn6 小时前
Matlab自学笔记六十一:快速上手解方程
数据结构·笔记·matlab
番茄小能手7 小时前
【全网唯一】C# 纯本地离线文字识别Windows版dll插件
开发语言·c#