《C#本质论》 第8章 接口

第8章 接口

并非只能通过继承实现多态性,还能通过接口实现。和抽象类不同,接口不包含任何实现,但和抽象类相似,接口也定义了一组成员,调用者可认为这些成员已实现。
类型通过实现接口来定义其功能,接口实现关系是一种“能做==”can do关系==:类型能做接口所规定的事情。在实现接口的类型和使用接口的代码之间,接口订立了契约实现接口的类型必须使用接口要求的签名来定义方法。

8.1 接口概述

interface IFileCompresshion
{
  void Compress(string targetFileName,string[] fileList)
  void Uncompress(string compressedFileName,string expandDirectoryName);
}

初学者主题:为什么需要接口
接口有用是因为和抽象类不同,它能完全隔离实现细节和提供的服务。接口就像电源插座。电如何输送到插座是实现细节;电器不必关心电如何输送到插座,秩序提供兼容的插头。
接口的强大之处在于,调用者可随便切换不同的实现而不需要修改调用代码。
接口的关键之处是不包含实现和数据,注意其中的方法声明用分号取代了大括号。字段不能在接口声明中出现。如接口要求派生类包含特定数据,会声明属性而不是字段。
接口声明的成员描述了在实现该接口的类型中必须能访问的成员。而所有非公共成员的目的都是组织其他代码访问成员,所以,C#不允许为接口成员使用访问修饰符,所有成员都自动公共
设计规范
•接口名称使用Pascal大小写,加“I”前缀。

8.2 使用接口实现多态性

public interface IShape
{
    double GetArea();
}

public class Circle : IShape
{
    public double Radius { get; set; }

    public Circle(double radius)
    {
        Radius = radius;
    }

    public double GetArea()
    {
        return Math.PI * Radius * Radius;
    }
}

public class Rectangle : IShape
{
    public double Width { get; set; }
    public double Height { get; set; }

    public Rectangle(double width, double height)
    {
        Width = width;
        Height = height;
    }

    public double GetArea()
    {
        return Width * Height;
    }
}

// Usage
IShape shape1 = new Circle(5);
IShape shape2 = new Rectangle(4, 6);
double area1 = shape1.GetArea(); // Returns the area of the circle
double area2 = shape2.GetArea(); // Returns the area of the rectangle

8.3 接口实现

声明类来实现接口类似于从基类派生——要实现的接口和基类名称以逗号分隔。类可实现多个接口,但只能从一个基类派生。
实现接口时,接口的所有成员都必须实现,抽象类既可以将接口方法映射为抽象方法,也可以将它实现为非抽象方法并在方法中抛出NotImplementedException异常,但无论如何都要提供接口成员的一个“实现”。

interface IFoo
{
  void Bar();
}

在抽象类中可将接口方法映射成自己的抽象方法,将真正的实现留给子类完成:

abstract class Foo:IFoo
{
    public abstract void Bar();
}

也可拿掉abstract关键字并添加方法主体:

abstract class Foo:IFpp
{
   public void Bar();
   {
     throw new NotImplementedException();
   }
}

接口的重点在于永远不能实例化,即不能用new创建接口。所以接口没有构造函数或终结器。只有实例化实现了接口的类型,才能使用接口实例。此外接口不能包含静态成员。接口为多态性而生,而假如没有实现接口的那个类型的实例,多态性就没什么价值。
每个接口成员的行为和抽象方法相似,都是强迫派生类实现成员,但不能为接口成员显式添加abstract修饰符。
在类型中实现接口成员时有两种方式:显式隐式。之前看到的是隐式实现,是用类型的公共成员实现接口成员。

8.3.1 显式成员实现

// Justification: Only a aartial implmentation provided for elucidation purposes.
#pragma warning disable IDE0059 // Unnecessary assignment of a value

namespace AddisonWesley.Michaelis.EssentialCSharp.Chapter08.Listing08_04
{
    using System;
    using Listing08_02;

    public class Program
    {
        public static void Main()
        {
            string?[] values;
            Contact contact = new Contact("Inigo Montoya");

            // ...

            // ERROR:  Unable to call .CellValues directly
            //         on a contact
            // values = contact.CellValues;

            // First cast to IListable
            values = ((IListable)contact).CellValues;//执行强制转型和调用CellValues

            // ...
        }
    }

    public class Contact : PdaItem, IListable
    {
        // ...
        public Contact(string name)
            : base(name)
        {
        }

        #region IListable Members
        string?[] IListable.CellValues
        {
            get
            {
                return new string?[] 
          {
              FirstName,
              LastName,
              Phone,
              Address
          };
            }
        }
        #endregion

        private string? _LastName;
        protected string LastName
        {
            get => _LastName!;
            set => _LastName = value ?? throw new ArgumentNullException(nameof(value));
        }

        private string? _FirstName;
        protected string FirstName
        {
            get => _FirstName!;
            set => _FirstName = value ?? throw new ArgumentNullException(nameof(value));
        }
        protected string? Phone { get; set; }
        protected string? Address { get; set; }
        static public string GetName(string firstName, string lastName)
            => $"{ firstName } { lastName }";
    }
}

显式成员实现的方法只能通过接口本身调用,最典型的做法是将对象转型为接口。
通过属性名附加IListable前缀来显式实现ColumnValues。

8.3.2 隐式成员实现

result = Contact1.CompareTo(contact2);

8.3.3 显式和隐式接口实现的比较

对于隐式和显式实现的接口成员,关键区别不在于成员声明的语法,而在于通过类型的实例而不是接口访问成员的能力。
建立类层次接口时需要建模真实世界的“属于” is a关系——例如,长颈鹿属于哺乳动物。而接口用于建模机制关系。
一般来说,最好的做法是将一个类的公共层面限制成“全模型”,尽量少地涉及无关的机制;遗憾的是,有的机制在.NET中是不可避免的。不能获得长颈鹿的哈希码,或者将长颈鹿转换成字符串。但可获得长颈鹿类的哈希码,并把它转换为字符串。
设计规范
避免显式实现接口成员,除非有很好的理由。

8.4 在实现类和接口之间转换

类似于派生类和基类的关系,实现类可隐式转换为接口,无须转型操作符。实现类的实例总是包含接口的全部成员,所以总是能成功转换为接口类型。
虽然从实现类型向接口的转换总是成功,但可能有多个类型实现了同一个接口。所以,无法保证从接口向实现类型的向下转型能成功,接口必须显式转型为它的某个实现类型。

8.5 接口继承

一个接口可以从另一个接口派生,派生的接口将继承”基接口“的所有成员。
即使类实现的是从基接口派生的接口,仍可明确声明自己要实现这两个接口。
最后要说的是,虽然”继承“这个词用得没错,但更准确的说法是接口代表契约,一份契约可指定另一份也必须遵守的条款。

8.6 多接口继承

就像类能是实现多个接口一样,接口也能从多个接口继承,而且语法和类的继承与实现语法一致。很少有接口没有成员,但如果要求同时实现两个接口,这种情况就很正常了。

8.7 接口上的扩展方法

扩展方法的重要特点是除了能作用于类,还能作用于接口。语法和作用于类时一样。

8.8 通过接口实现多继承

设计规范
•考虑定义接口获得和多继承相似的效果

8.9 版本控制

创建新版时不要修改接口,如组件或应用程序正在供其他开发者使用。一个接口一旦被发布,就不可以再被修改。
设计规范
不要为已交付的接口添加成员

8.9.1 C#8.0之前和之后的接口版本升级

8.10 比较接口和类

接口引入了另一个类别的数据类型,但和类不同,接口永远不能实例化,只能通过对实现接口的一个对象的引用来访问接口实例。不能使用new操作符创建接口实例,所以接口不能包含任何构造函数或终结器。此外,接口不允许静态成员。
接口类似于抽象类,有一些共同特点,比如都缺少实例化能力。
表8.1 抽象类和接口的比较

抽象类接口
不能直接实例化,只能实例化派生类不能直接实例化,只能实例化一个实现类型
派生类要么自己也是抽象的,要么必须实现所有抽象成员实现类型必须实例化所有接口成员
可添加额外的非抽象成员,由所有派生类继承,不会破坏跨版本的版本兼容性为接口添加额外的成员会破坏版本兼容性
可声明方法、属性和字段以及其他成员类型包括析构函数和终结器可声明方法和属性但不能声明字段、构造函数或终结器。
成员可以是实例、虚、抽象或静态、非抽象成员可提供默认实现供派生类使用所有成员都基于实例,而且自动视为抽象,所以不能包含任何实现
派生类只能从一个基类派生,单继承实现类型可实现任意多的接口

设计规范

•一般要优先选择类而不是接口,用抽象类分离契约(类型做什么)与实现接口(类型怎么做)

•如果需要使已从其他类型派生的类型支持接口定义的功能,考虑定义接口

8.11 比较接口和特性

有时用无任何成员的接口(不管是不是继承)来描述关于类型的信息,一般认为这是对接口机制的滥用:接口应表示类型能执行的功能,而非陈述关于类型的事实。
设计规范
•避免使用无成员的标记接口,改为使用特性。

  • 17
    点赞
  • 23
    收藏
    觉得还不错? 一键收藏
  • 1
    评论
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值