C#从入门到精通(第四版)学习记录-- 第17(抽象类、抽象方法)、18章(迭代器、分布类)
第十七章 面向对象技术高级应用
17.1 抽象类与抽象方法
17.1.1抽象类概述及声明
C#中声明抽象类时需要使用abstract关键字,语法格式:
访问修饰符 abstract class 类名:基类或接口
{
//类成员
}
- 抽象类:使用abstract关键字定义的类
- 抽象方法: 使用abstract关键字定义的方法
- 抽象方法没有方法体,除了被继承外没有任何意义
- 只要类中有一个抽象方法,此类就被标记为抽象类
- 抽象类被继承后需要实现其中所有的抽象方法
- 类不能同时继承多个父类,但可以继承多个接口
public abstract class MyClass
{
public int iValue;
public void Print(){ }
}
17.1.2 抽象方法概述及声明
- 抽象方法必须声明再抽象类中
- 声明抽象方法时,不能使用virtual、static和private修饰符
- 重写抽象方法时要使用override关键字
- 抽象方法的方法体只包含一个分号
public abstract class MyClass
{
public abstract void Print(); //抽象方法
}
17.2 接口
- C#中的类不支持多重继承,但通过接口可以实现多重继承的功能。
- C#中声明接口时需要使用interface关键字,语法格式:
访问修饰符 interface 接口名:接口1,接口2,。。。 //接口列表
{
//接口内容
}
17.2.1接口的概述及声明
接口是抽象类的延伸,可以将它看做是纯粹的抽象类,接口中的方法都没有方法体。
接口可以包含方法、属性、索引器和事件作为成员,但不能设置这些成员的具体值(只能定义不能赋值)
接口的特征:
- 接口类似于抽象基类:继承接口的任何非抽象类型必须实现接口的所有成员
- 不能直接实例化接口
- 接口可以包含方法、属性、索引器和事件
- 接口不包含方法的实现
- 类和结构可从多个接口继承
- 接口自身可从多个接口继承
- 可以使用new、public、protected、internal和private等修饰符声明接口,但接口成员必须是公共的
interface ITest
{
string Name { get; set; }
}
interface ITestU : ITest
{
void Print();
}
public class MyCalss : ITest, ITestU
{
string _name = "";
public string Name { get { return _name; } set { _name = value; } }
public void Print() { Console.WriteLine("Name:{0}", _name); }
}
class Program
{
static void Main(string[] args)
{
MyCalss obj = new MyCalss();
obj.Name = "ZhangSan";
obj.Print(); //Name:ZhangSan
ITestU iTestU = obj;
iTestU.Name = "LiSi";
iTestU.Print(); //Name:LiSi
Console.ReadLine();
}
}
17.2.3 显示接口成员实现
若类实现两个接口,且两个接口具有相同签名的成员,那么在类中实现该成员将导致两个接口都使用该成员作为它们的实现。
若两个成员实现不同的功能,则可能会导致其中一个接口的实现不正确或两个接口的实现都不正确,这是需要显示地实现接口成员。
显示接口成员实现是使用接口名称和一个句号命名该类成员来实现。
注意:
- 显示接口成员实现中不能包含访问修饰符、abstact、virtual、override或static修饰符
- 显示接口成员属于接口的成员,而不是类的成员,因此,不能使用类对象直接访问,只能通过接口对象访问;
interface ITest1
{
void Print();
}
interface ITest2
{
void Print();
}
class MyCalss : ITest1, ITest2
{
void ITest1.Print() { Console.WriteLine("Enter ITest1."); } //不能用public等修饰符修饰
void ITest2.Print() { Console.WriteLine("Enter ITest2."); }
}
class Program
{
static void Main(string[] args)
{
MyCalss obj = new MyCalss();
ITest1 iTest1 = obj;
iTest1.Print(); //Enter ITest1.
ITest2 iTest2 = obj;
iTest2.Print(); //Enter ITest12
Console.ReadLine();
}
}
17.2.4 抽象类与接口
- 都包含可以由派生类继承的成员,它们都不能直接实例化,但可以声明它们的变量
- 它们的派生类只能继承一个基类,即只能直接继承一个抽象类,但可以继承任意多个接口
- 抽象类中可以定义成员的实现,但接口中不可以
- 抽象类中可以包含字段、构造函数、析构函数、静态成员或变量等,接口中不可以
- 抽象类中的成员可以是私有的(只要它们不是抽象的)、受保护的、内部的或受保护的内部成员,DNA接口中的成员必须是公共的
抽象类和接口目的不同:抽象类主要用作对象系列的基类,共享某些主要特性,例如共同的目的和结构;接口主要用于类,这些类在基础水平上有所不同,但仍可以完成某些相同的任务。
17.3 密封类与密封方法
17.3.1 密封类概述及声明
密封类可以用来限制扩展性,若密封了某个类,则其他类不能继承此类;
C#中声明密封类时需要使用sealed关键字,语法格式:
访问修饰符 sealed class 类名:基类或接口
{
//类成员
}
什么样的类建议声明为密封的呢,最好满足以下条件之一,否则不建议密封类:
- 类包含带有安全敏感信息的继承的受保护成员
- 类继承多个虚成员,并且密封每个成员的开发和测试开销明显大于密封整个类
- 类是一个要求使用反射进行快速搜索的属性。密封属性可提高反射在检索属性时的性能
说明:
- 若密封了某个成员,则派生类不能重写该成员的实现;
- 类和方法默认是不密封的;
- 密封类不能作为基类被继承,但它可以继承别的类或接口
- 在密封类中不能声明受保护成员或虚成员,因为受保护成员只能从派生类进行访问,而虚成员只能在派生类中重写
- 由于密封类的不可继承性,因此密封类不能声明为抽象的,即sealed修饰符不能与abstract修饰符同时使用。
17.3.2 密封方法概述及声明
- 密封方法只能用于对基类的虚方法进行实现,并提供具体的实现。
- 所以,声明密封方法时,sealed修饰符总是和override修饰符同时使用。
17.3.3 密封类与密封方法的使用
密封类除了不能被继承外,与非密封类的用法基本一致;
密封方法必须通过重写基类中的虚方法来实现。
public class VClass
{
public virtual void Print() { } //虚方法
}
class MyCalss : VClass
{
public string Name;
public sealed override void Print() //密封并重写基类中的虚方法
{
Console.WriteLine("Name:{0}",Name);
}
}
class Program
{
static void Main(string[] args)
{
MyCalss obj = new MyCalss();
obj.Name = "ZhangSan";
obj.Print(); //Name:ZhangSan
Console.ReadLine();
}
}
第十八章 迭代器和分部类
18.1 迭代器
18.1.1 迭代器概述
- 迭代器是可以返回相同类型的值的有序序列的一段代码,可用作方法、运算符或get访问器的代码体。
- 迭代器代码使用yield return语句一次返回每个元素,yield break语句将终止迭代。
- 类中可以实现多个迭代器,每个迭代器都必须像任何类成员一样有唯一的名称,并且可以再foreach语句中被客户端代码调用。
- 迭代器的返回类型必须为IEnumerable或IEnumerator中的任意一种。
18.1.2 迭代器的使用
- 创建迭代器最常用的方法是对IEnumerator接口实现GetEnumerator方法。
class MyCalss : System.Collections.IEnumerable
{
string[] Name = { "ZhangSan", "LiSi", "WangWu" };
public System.Collections.IEnumerator GetEnumerator() //创建迭代器
{
for(int i = 0;i< Name.Length;i++)
{
yield return Name[i]; //返回每个元素
}
}
}
class Program
{
static void Main(string[] args)
{
MyCalss obj = new MyCalss();
foreach(var item in obj)
{
Console.WriteLine("item: {0}",item);
}
Console.ReadLine();
}
}
18.2 分部类
18.2.1 分部类概述
- 分部类可以将类、结构或接口的定义拆分到两个或多个源文件中;
- 每个源文件包含类定义的一部分,编译应用时,编译器(VS)会把分部类组合起来;
18.2.2 分部类的使用
- 定义分部类时需要使用partial关键字,分部类的每个部分都必须包含一个partial关键字,且声明必须与其他部分位于统一命名空间。
- 开发分部类时,要成为同一类型的哥哥部分的所有分部类型定义都必须在同一程序集或同一模块(.exe或.dll)中定义,分部类定义不能跨越多个模块。
- 在设置分部类时,各个部分必须具有相同的可访问性,如public、private等。
partial class Alg
{
public int Add(int ia, int ib) { return ia + ib; }
}
partial class Alg
{
public int Sub(int ia, int ib) { return ia - ib; }
}
class Program
{
static void Main(string[] args)
{
Alg obj = new Alg();
Console.WriteLine("Add: {0}", obj.Add(1, 2));
Console.WriteLine("Add: {0}", obj.Sub(1, 2)); Console.ReadLine();
}
}