• 【C# Programming】继承、接口


    一、继承

    1、派生

            继承在相似而又不同的概念之间建立了类层次概念。 更一般的类称为基类,更具体的类称为派生类。派生类继承了基类的所有性质。

            定义派生类要在类标识符后面添加一个冒号,接着添加基类名。

    1. public class PdaItem
    2. {
    3. public string Name { get; set; }
    4. public DateTime LastUpdated { get; set; }
    5. }
    6. // Define the Contact class as inheriting the PdaItem class
    7. public class Contact : PdaItem
    8. {
    9. public string Address { get; set; }
    10. public string Phone { get; set; }
    11. }

            除非明确指定了基类,否则所有类都默认从object 派生。

    2、基类和派生类的转型

            由于派生建立了“属于”关系,所以总可以将派生类的值赋给基类型的变量。这称为隐式类型转换。  从基类型转换到派生类型,要求执行显式转型。方法是在原始引用名称前,将要转换成的目标类型放到圆括号中。

    1. public static void Main()
    2. {
    3. // Derived types can be implicitly converted to base types
    4. Contact contact = new Contact();
    5. PdaItem item = contact;//隐式类型转换
    6. // Base types must be cast explicitly to derived types 显示类型转换/强制类型转换
    7. contact = (Contact)item;
    8. // ...
    9. }

    3、自定义转换

            类型之间的转换并不限于单一继承链中的类型,不同类型相互之间也能进行转换,关键是两个类型之间提供转型操作符。 C# 允许显式或隐式转型操作符。 例如:

    1. class GPSCoordinates
    2. {
    3. public static implicit operator UTMCoordinates(GPSCoordinates coordinates)
    4. {
    5. // ...
    6. return null;//return the new UTMCoordinates object
    7. }
    8. }
    9. class UTMCoordinates { /**/}

            如果需要显式转换,可将implict 换成 explict。

    4、private 访问修饰符

            派生类继承了除构造器和析构器之外的所有基类成员,到那时私有成员只能在声明它们的那个类型中才能访问,派生类不能访问基类的私有成员。

    1. public class PdaItem
    2. {
    3. private string _Name;
    4.     // ...
    5. }
    6. public class Contact : PdaItem
    7. {
    8.     // ...
    9. }
    10. public class Program
    11. {
    12.     public static void ChapterMain()
    13.     {
    14. Contact contact = new Contact();
    15.         // ERROR:  ‘PdaItem. _Name’ is inaccessible due to its protection level
    16.         //contact._Name = "Inigo Montoya"//uncomment this line and it will not compile
    17.     }
    18. }

    5、protected 访问修饰符

            基类中受保护的成员只能由基类以及派生链中的其他类访问。

    1. public class Program
    2. {
    3. public static void Main()
    4.     {
    5.     Contact contact = new Contact();
    6.         contact.Name = "Inigo Montoya";
    7.         // ERROR:  ‘PdaItem.ObjectKey’ is inaccessible due to its protection level
    8. //contact.ObjectKey = Guid.NewGuid(); //uncomment this line and it will not compile
    9.      }
    10. }
    11. public class PdaItem { protected Guid ObjectKey { get; set; } }
    12. public class Contact : PdaItem
    13. {
    14.     void Save()
    15.     {
    16. // Instantiate a FileStream using <ObjectKey>.dat for the filename.
    17.         FileStream stream = System.IO.File.OpenWrite(ObjectKey + ".dat");
    18.     }
    19.     void Load(PdaItem pdaItem)
    20.     {
    21.         // ERROR:  ‘pdaItem.ObjectKey’ is inaccessible due to its protection level
    22. //pdaItem.ObjectKey =...;
    23.         Contact contact = pdaItem as Contact;
    24.         if(contact != null)
    25.         contact.ObjectKey = new Guid();//...; 
    26.     }
    27.     public string Name { get; set; }
    28. }

    6、单继承

    • 继承链中的类理论是无限的。但是C#是单继承语言,意味着一个类不能从两个类中直接派生。
    • 在极少数需要多继承类结构时, 一般的解决方法是使用聚合(aggregation)即一个类包含另一个类的实例。

    7、密封类

            密封类要求使用sealed 修饰符,这样做的结果时不能从它们派生其他类。

    1. public sealed class CommandLineParser
    2. {
    3. // ...
    4. }
    5. // ERROR:  Sealed classes cannot be derived from
    6. public sealed class DerivedCommandLineParser
    7. //: CommandLineParser //uncomment this line and it will not compile
    8. {
    9. // ...
    10. }

    8、virtual 修饰符

    • C#支持重写(override)实例方法和属性,但不支持重写字段或者任何静态成员,为了重写,在基类中必须将允许重写的每个成员标记为virtual。
    1. public class PdaItem
    2. {
    3. public virtual string Name { get; set; }
    4. }
    5. public class Contact : PdaItem
    6. {
    7. public override string Name
    8. {
    9. get {
    10. return $"{ FirstName } { LastName }";
    11. }
    12. set {
    13. string[] names = value.Split(' ‘);
    14. FirstName = names[0];
    15. LastName = names[1];
    16. }
    17. }
    18. public string FirstName { get; set; }
    19. public string LastName { get; set; }
    20. }
    • C# 应用于派生类的override 关键字是必须的,C# 不允许隐式重写。为了重写,基类和派生类成员签名必须一致。

    9、new 修饰符

            如果重写方法没有使用override 关键字,则编译器会报告警告消息, 一种方法是添加override关键字,另一种方法是使用new 修饰符。

    warning CS0108: 'Program.DerivedClass.DisplayName()' hides inherited member 'Program.BaseClass.DisplayName()'. Use the new keyword if hiding was intended.

    warning CS0114: 'Program.SuperSubDerivedClass.DisplayName()' hides inherited member 'Program.SubDerivedClass.DisplayName()'. To make the current member override that implementation, add the override keyword. Otherwise add the new keyword.

            new 修饰符在基类面前隐藏了派生类重新声明的成员,这时是搜索继承链,找到使用new 修饰符的那个成员前的成员,然后调用该成员,例如:

    1. public class Program{
    2. public class BaseClass{
    3. public void DisplayName() => Console.WriteLine("BaseClass");
    4. }
    5. public class DerivedClass : BaseClass{
    6. // Compiler WARNING: DisplayName() hides inherited member. Use the new keyword if hiding was intended.
    7. public virtual void DisplayName() => Console.WriteLine("DerivedClass");
    8. }
    9. public class SubDerivedClass : DerivedClass{
    10. public override void DisplayName() =>Console.WriteLine("SubDerivedClass");
    11. }
    12. public class SuperSubDerivedClass : SubDerivedClass{
    13. public new void DisplayName() => Console.WriteLine("SuperSubDerivedClass");
    14. }
    15. public static void Main()
    16. {
    17. SuperSubDerivedClass superSubDerivedClass = new SuperSubDerivedClass();
    18. SubDerivedClass subDerivedClass = superSubDerivedClass;
    19. DerivedClass derivedClass = superSubDerivedClass;
    20. BaseClass baseClass = superSubDerivedClass;
    21. superSubDerivedClass.DisplayName();
    22. subDerivedClass.DisplayName();
    23. derivedClass.DisplayName();
    24. baseClass.DisplayName();
    25. }
    26. }

    10、sealed 修饰符

            虚成员也可以密封,这样做会禁止子类重写继承链中高层基类的虚成员, 例如:

    1. class A
    2. {
    3. public virtual void Method()
    4. {
    5. }
    6. }
    7. class B : A
    8. {
    9. public override sealed void Method()
    10. {
    11. }
    12. }
    13. class C : B
    14. {
    15. // ERROR: Cannot override sealed members
    16. //public override void Method()
    17. //{
    18. //}
    19. }

    11、base 成员

            重写成员时, 可以使用base关键字调用该成员的基类版本。 base 表示当前类的基类。

    1. public class Address
    2. {
    3. public string StreetAddress;
    4. public string City;
    5. public string State;
    6. public string Zip;
    7. public override string ToString()
    8. {
    9. return $"{ StreetAddress + NewLine }"
    10. + $"{ City }, { State } { Zip }";
    11. }
    12. }
    13. public class InternationalAddress : Address {
    14. public string Country;
    15. public override string ToString() => base.ToString() +NewLine + Country;
    16. }

    12、构造器

            当构造派生类的对象时, 基类的构造函数首先被调用。如果基类没有默认构造函数,则需要在派生类的构造函数中显示调用基类的构造函数,例如:

    1. public class PdaItem
    2. {
    3. public PdaItem(string name)
    4. {
    5. Name = name;
    6. }
    7. public string Name { get; set; }
    8. }
    9. public class Contact : PdaItem
    10. {
    11. public Contact(string name) :
    12. base(name)
    13. {
    14. Name = name;
    15. }
    16. public new string Name { get; set; }
    17. // ...
    18. }

    13、抽象类

            抽象类是仅供派生的类,无法实例化抽象类,只能实例化从它派生的类。不抽象,可以实例化的类称为具体类。

            抽象类代表抽象的实体。一个类要从抽象类成功派生,必须为抽象类中的抽象方法提供具体实现. C#要求为类定义添加abstract 修饰符。

    1. public abstract class PdaItem
    2. {
    3. public PdaItem(string name)
    4. {
    5. Name = name;
    6. }
    7. public virtual string Name { get; set; }
    8. }
    9. public class Program
    10. {
    11. public static void Main()
    12. {
    13. PdaItem item;
    14. // ERROR: Cannot create an instance of the abstract class
    15. //item = new PdaItem("Inigo Montoya"); //uncomment this line and it will not compile
    16. }
    17. }

    14、抽象成员

            不可实例化只是抽象类的一个较次要的特征,其主要特征是包含抽象成员。抽象成员是没有实现的方法或属性。其作用是强制所有派生类实现。

    1. public abstract class PdaItem{
    2. public PdaItem(string name)
    3. {
    4. Name = name;
    5. }
    6. public virtual string Name { get; set; }
    7. public abstract string GetSummary();
    8. }
    9. public class Contact : PdaItem{
    10. public Contact(string name) : base(name) {}
    11. public override string Name
    12. {
    13. get{
    14. return $"{ FirstName } { LastName }";
    15. }
    16. set{
    17. string[] names = value.Split(' ‘);
    18. FirstName = names[0];
    19. LastName = names[1];
    20. }
    21. }
    22. public string FirstName { get; set; }
    23. public string LastName { get; set; }
    24. public string Address { get; set; }
    25. public override string GetSummary() =>$"FirstName: { FirstName + NewLine }"
    26. + $"LastName: { LastName + NewLine }" + $"Address: { Address + NewLine }";
    27. }
    28. public class Appointment : PdaItem
    29. {
    30. public Appointment(string name) :
    31. base(name)
    32. {
    33. Name = name;
    34. }
    35. public DateTime StartDateTime { get; set; }
    36. public DateTime EndDateTime { get; set; }
    37. public string Location { get; set; }
    38. // ...
    39. public override string GetSummary()
    40. {
    41. return $"Subject: { Name + NewLine }"
    42. + $"Start: { StartDateTime + NewLine }"
    43. + $"End: { EndDateTime + NewLine }"
    44. + $"Location: { Location }";
    45. }
    46. }

            相同签名的成员在不同类中的不同实现,称为多态性(polymorphism).抽象成员是实现多态的一个手段

    1. public class Program{
    2. public static void Main()
    3. {
    4. PdaItem[] pda = new PdaItem[3];
    5. Contact contact = new Contact("Sherlock Holmes");
    6. contact.Address = "221B Baker Street, London, England";
    7. pda[0] = contact;
    8. Appointment appointment = new Appointment("Soccer tournament");
    9. appointment.StartDateTime = new DateTime(2008, 7, 18);
    10. appointment.EndDateTime = new DateTime(2008, 7, 19);
    11. appointment.Location = "Estádio da Machava";
    12. pda[1] = appointment;
    13. contact = new Contact("Anne Frank");
    14. contact.Address = "Apt 56B, Whitehaven Mansions, Sandhurst Sq, London";
    15. pda[2] = contact;
    16. List(pda);
    17. }
    18. public static void List(PdaItem[] items)
    19. {
    20. // Implemented using polymorphism. The derived type knows the specifics of implementing GetSummary().
    21. foreach(PdaItem item in items)
    22. {
    23. Console.WriteLine("________");
    24. Console.WriteLine(item.GetSummary());
    25. }
    26. }
    27. }

    15、System.Object

            任何对象,不管是自定义类型,还是系统内建类型,都最终从Sytem.Object派生。因此,都定义了如下方法:

    16、使用 is 操作符验证基础类型

            C# 允许在继承链中向下转型。C#提供了is 操作符判断基础类型。它允许验证一个数据项是否是属于特定类型

    1. public class AnObject
    2. {
    3. public static void Save(object data)
    4. {
    5. if(data is string)
    6. {
    7. data = Encrypt((string)data);
    8. }
    9. // ...
    10. }
    11. private static object Encrypt(string data)
    12. {
    13. return null;
    14. }
    15. }

    17、使用 as 操作符进行转换

            as 操作符尝试将对象转换为特定类型,如果对象不能转换,as 操作符返回null. 因而转型失败不会引发异常

    1. class Program
    2. {
    3. static object Print(IDocument document)
    4. {
    5. if(document != null)
    6. {
    7. // Print document...
    8. }
    9. return null;
    10. }
    11. static void Main()
    12. {
    13. object data = new object();
    14. // ...
    15. Print(data as Document);
    16. }
    17. }
    18. internal class Document : IDocument
    19. {
    20. }
    21. internal interface IDocument
    22. {
    23. }

    二、接口

    1、概述

    • 和抽象类不同,接口能将实现细节和提供的服务完全隔离开。接口的关键特点是既不包含实现,也不包含数据。
    • 按照惯例,接口名采用Pascal大小写风格,以大写字母I 作为前缀            
      • interface Ixxxx            {                //...            }  
    • 接口声明的成员描述了在实现该接口的类型中必须能够访问的成员
    1. interface IFileCompression
    2. {
    3. void Compress(string targetFileName, string[] fileList);
    4. void Uncompress(
    5. string compressedFileName, string expandDirectoryName);
    6. }
    • 如果接口要求派生类包含特定数据,会声明属性而不是字段
    1. interface IListable {
    2. string[] ColumnValues // Return the value of each column in the row.
    3. {
    4. get;
    5. }
    6. }
    7. public abstract class PdaItem{
    8. public PdaItem(string name) {
    9. Name = name;
    10. }
    11. public virtual string Name { get; set; }
    12. }
    13. class Contact : PdaItem, Ilistable{
    14. public Contact(string firstName, string lastName, string address, string phone)
    15. : base(null)
    16. {
    17. FirstName = firstName;
    18. LastName = lastName;
    19. Address = address;
    20. Phone = phone;
    21. }
    22. public string FirstName { get; set; }
    23. public string LastName { get; set; }
    24. public string Address { get; set; }
    25. public string Phone { get; set; }
    26. public string[] ColumnValues {
    27. get {
    28. return new string[] { FirstName, LastName, Phone, Address };
    29. }
    30. }
    31. public static string[] Headers {
    32. get { return new string[] {"First Name", "Last Name ", "Phone ", "Address" };
    33. }
    34. }

    2、接口实现

    • 声明类以实现接口, 类似于从基类派生:要实现的接口和基类名称以逗号分隔,基类在前,接口顺序任意。 类可以实现多个接口,但只能从一个基类直接派生。例如:
    1. public class Contact : PdaItem, IListable, IComparable
    2. {
    3. public Contact(string name) : base(name) {}
    4. #region IComparable Members
    5. public int CompareTo(object obj)
    6. {
    7. int result;
    8. Contact contact = obj as Contact;、
    9. if(obj == null)
    10. result = 1; // This instance is greater than obj.
    11. else if(obj.GetType() != typeof(Contact))
    12. throw new ArgumentException($"The parameter is not a of type { nameof(Contact) }", nameof(obj));
    13. else if(Contact.ReferenceEquals(this, obj))
    14. result = 0;
    15. else {
    16. result = LastName.CompareTo(contact.LastName);
    17. if(result == 0)
    18. result = FirstName.CompareTo(contact.FirstName);
    19. }
    20. return result;
    21. }
    22. #endregion
    23. string[] IListable.ColumnValues{
    24. get {
    25. return new string[] {FirstName, LastName, Phone, Address};
    26. }
    27. }
    28. protected string LastName { get; set; }
    29. protected string FirstName { get; set; }
    30. protected string Phone { get; set; }
    31. protected string Address { get; set; }
    32. }
    • 一旦某个类声明自己要实现的接口,接口所有成员都必须实现,抽象类允许提供接口成员的抽象实现。  
    • 接口的另一特征是永远不能实例化。不能用new 创建接口,所以接口没有构造器或者析构器。只有实例化实现接口的类型,才能使用接口实例。接口不能包含静态成员  
    • 在类型中实现接口成员时有两种方式: 显式 和隐式

    3、显示成员实现

            显式声明的接口成员需要在成员名前附加接口前缀, 例如:

    1. public class Contact : PdaItem, IListable, IComparable {
    2. //….
    3. string[] IListable.ColumnValues{
    4. get {
    5. return new string[] {FirstName, LastName, Phone, Address};
    6. }
    7. }
    8. }

            显式实现的接口只能通过接口本身调用,因此需要将对象转型为接口。 显式实现的接口成员直接和接口关联,因此不允许使用virtual, override, 或者public 来修饰他们。 例如:

    1. public class Program
    2. {
    3. public static void Main()
    4. {
    5. string[] values;
    6. Contact contact1, contact2 = null;
    7. // ERROR: Unable to call ColumnValues() directly on a contact.
    8. // values = contact1.ColumnValues;
    9. // First cast to IListable.
    10. values = ((IListable)contact2).ColumnValues;
    11. // ...
    12. }
    13. }

    4、隐式成员实现

            隐式实现只要求成员是公共的且签名与接口成员的签名相符。接口成员实现不需要override 关键字或者其他任何表明该成员与接口关联的指示符。  

            由于成员是像其他类成员那样声明的,所以可以像调用其它类成员那样直接调用隐式实现的成员。        result = contact1.CompareTo(contact2);

            隐式实现上,显式实现不允许的许多修饰符是必须或者可选的。例如: 隐式实现必须式public.而且还可选择virtual.具体取决于派生类是否可以重写。去掉virtual 导致成员密封。

    5、显式接口实现与隐式接口实现的比较

    选择显示实现还是隐式实现的基本原则:

    • 如果成员是类的辅助成员,则必要设计成类的直接可见成员  
    • 假设接口成员的用途在实现类中不明确,就可以考虑显式成员  
    • 显式接口成员不会在类型的声明空间添加具名元素。所以,假如类型有一个潜在的与之相冲突的成员。那么另一个是显式的接口成员, 就可以与之同名

    6、接口继承

    • 一个接口可以从另一个接口派生, 派生的接口将继承“基接口”的所有成员。例如:
    1. interface IReadableSettingsProvider
    2. {
    3. string GetSetting(string name, string defaultValue);
    4. }
    5. interface ISettingsProvider : IReadableSettingsProvider
    6. {
    7.     void SetSetting(string name, string value);
    8. }
    9. class FileSettingsProvider : ISettingsProvider{
    10.     public void SetSetting(string name, string value) { /*.. */ }
    11.     public string GetSetting(string name, string defaultValue)
    12.     {
    13.         return name + defaultValue; // just returning this for the example
    14.     }
    15. }
    • 假如显式实现GetSetting,必须通过IReadableSettingsProvider
    • 即使类实现的接口是从基接口派生的,这个类任然可以可以声明自己要实现这两个接口。例如:
    1. interface IReadableSettingsProvider
    2. {
    3.     string GetSetting(string name, string defaultValue);
    4. }
    5. interface ISettingsProvider : IReadableSettingsProvider
    6. {
    7.     void SetSetting(string name, string value);
    8. }
    9. class FileSettingsProvider : ISettingsProvider, IReadableSettingsProvider{
    10.     public void SetSetting(string name, string value) { /*.. */ }
    11.     public string GetSetting(string name, string defaultValue)
    12.     {
    13.     return name + defaultValue; // just returning this for the example
    14.     }
    15. }

    7、多接口继承

    接口也能从多个接口继承,而且语法与类的继承和实现语法一致 。

    1. interface IReadableSettingsProvider
    2. {
    3. string GetSetting(string name, string defaultValue);
    4. }
    5. interface IWriteableSettingsProvider
    6. {
    7.     void SetSetting(string name, string value);
    8. }
    9. interface ISettingsProvider : IReadableSettingsProvider,
    10.     IWriteableSettingsProvider
    11. {
    12. }

    8、接口上的扩展方法

            扩展方法的一个重要特点是除了能作用于类,还能作用于接口,语法和作用于类一致。方法的第一个参数是要扩展的接口,该参数必须附加this 修饰符。

    1. class Program
    2. {
    3. public static void Main()
    4.     {
    5.     Contact[] contacts = new Contact[6];
    6.         contacts[0] = new Contact("Dick", "Traci", "123 Main St., Spokane, WA 99037", "123-123-1234");
    7.         contacts.List(Contact.Headers); // Classes are implicitly converted totheir supported interfaces.
    8.         Console.WriteLine();
    9. //…..
    10.     }
    11. }
    12. static class Listable
    13. {
    14. public static void List( this IListable[] items, string[] headers)
    15.     {
    16.     //....
    17.     }
    18. }

    9、通过接口实现多继承

    虽然类职能从一个基类派生。但是可以实现任意数量的接口

    1. interface IPerson {
    2. string FirstName {get; set;   }
    3.     string LastName{get; set; }
    4. }
    5. public class Person : IPerson {
    6. public string FirstName { get; set; }
    7.     public string LastName { get; set; }
    8. }
    9. public class Contact : PdaItem, IPerson {
    10. public Contact(string name)
    11.     : base(name) {}
    12. private Person Person
    13. {
    14.     get { return _Person; }
    15.     set { _Person = value; }
    16. }
    17.     private Person _Person;
    18.     public string FirstName
    19.     {
    20.     get { return _Person.FirstName; }
    21.         set { _Person.FirstName = value; }
    22.     }       
    23. public string LastName {
    24.       get { return _Person.LastName; }
    25.       set { _Person.LastName = value; }
    26. }
    27. }

    三、接口和类的比较

  • 相关阅读:
    4S店汽车行业万能通用小程序源码系统 在线预约试驾+购车计算器 源码完全开源可二次开发
    【一起学Rust | 设计模式】习惯语法——默认特质、集合智能指针、析构函数
    监控易在大数据运维中的应用与实践
    Kubernetes---配置 Pod 以使用 PersistentVolume 作为存储
    基于Python实现的文章整合搜索引擎网站(Scrapy+Django+MySQL)
    自动装配(七)
    javascript选择框和选择文本的创建与增加以及设置选中项
    AOP中5种通知的注解
    晋江文学城PHP面试题(!带答案)
    Oracle之ADG与DG的区别?
  • 原文地址:https://blog.csdn.net/weixin_44906102/article/details/132843310