.NET的战略目标是在任何时候、任何地方、使用任何工具都能通过.NET的服务获得网络上的任何信息,享受网络带给人们的便捷与欢乐。.NET框架随后发布,它是开发.NET应用程序的核心基础。
.NET框架具有两个主要组件:CLR和FCL。CLR是.NET框架的基础。FCL是一个综合性的面向对象的可重用类型集合,利用它不仅可以开发传统命令行应用程序,而且可以开发WinForms应用程序及基于ASP.NET的应用程序。
公共语言运行库 (common language runtime,CLR) 是托管代码执行核心中的引擎。运行库为托管代码提供各种服务,如跨语言集成、代码访问安全性、对象生存期管理、调试和分析支持。它是整个.NET框架的核心,它为.NET应用程序提供了一个托管的代码执行环境。它实际上是驻留在内存里的一段代理代码,负责应用程序在整个执行期间的代码管理工作。CLR包含两个组成部分:CLS(公共语言规范)和CTS(通用类型系统)。
为了实现跨语言开发和跨平台的战略目标,.NET所以编写的应用都不编译成本地代码,而是编译成微软中间代码(MSIL)。它将由JIT编译器转换成机器代码。
.NET框架另外一个重要部分是FCL,即框架类库。这些类库使我们进行软件开发的利器。FCL提供了对系统功能的调用,是建立.NET应用程序、组件和控件的基础。通过灵活运用这些类库,开发工作会更加便利。
System:此命名控件包含所有其他的命名空间。
System.Collections.Generic:支持泛型操作。
System.Io:支持对文件的操作。
System,Net:支持对网络协议的编程。
System.Data:提供对表示ADO.NET结构的类的访问。
System.Windows.Forms:用于开发Windows应用程序。
System.Drawing:支持GDI+基本图形操作。
封装就是将抽象得到的数据和行为(或功能)相结合,形成一个有机的整体,也就是将数据与操作数据的源代码进行有机的结合,形成“类”,其中数据和函数都是类的成员。
封装的目的是增强安全性和简化编程,使用者不必了解具体的实现细节,而只是要通过外部接口,以特定的访问权限来使用类的成员。
封装在网络编程里面的意思, 当应用程序用TCP传送数据时,数据被送入协议栈中,然后逐个通过每一层直到被当作一串比特流送入网络,其中每一层对收到的数据都要增加一些首部。
封装主要给我们带来了如下好处
将字段封装为属性是封装的一种方式,类的私有方法也是一种封装。
类图(Class diagram)由许多(静态)说明性的模型元素(例如类、包和它们之间的关系,这些元素和它们的内容互相连接)组成。类图可以组织在(并且属于)包中,仅显示特定包中的相关内容。类图(Class diagram)是最常用的UML图,显示出类、接口以及它们之间的静态结构和关系;它用于描述系统的结构化设计。类图(Class diagram)最基本的元素是类或者接口。
类图主要用在面向对象软件开发的分析和设计阶段,描述系统的静态结构。类图图示了所构建系统的所有实体、实体的内部结构以及实体之间的关系。即.类图中包含从用户的客观世界模型中抽象出来的类、类的内部结构和类与类之间的关系。它是构建其他设计模型的基础,没有类图,就没有对象图、状态图、协作图等其他UMI.动态模型图.也就无法表示系统的动态行为。类图也是面向对象编程的起点和依据。
类图用于描述系统中所包含的类以及它们之间的相互关系,帮助人们简化对系统的理解,它是系统分析和设计阶段的重要产物,也是系统编码和测试的重要模型依据。
C#为了提高开发效率,与Java提高的数据类型非常相似
常用数据类型 | Java | C# | 举例 |
---|---|---|---|
整型 | int | int | 年龄 |
浮点型 | float | float | 成绩 |
双精度型 | double | double | 圆周率 |
字符串 | String | string | 家庭住址 |
布尔型 | boolean | bool | 是否为少数名族 |
枚举类型 | enum | enum | 颜色 |
在表中可以看到数据类型按存储方式可分为两类:值类型和引用类型。
值类型源于System.ValueType家族,每个值类型的对象的对象都有一个独立的内存区域用于保存自己的值,值类型数据所在的内存区域称为栈(Stack)。只要在代码中修改它,就会在它的内存区域内保存这个值。值类型主要包括基本数据类型和枚举类型等。
static void Main(string[] args)
{
int heightZhang = 170;
int heightLi = heightZhang;
Console.WriteLine("去年--张浩是身高是:" + heightZhang + ",李明的身高是:" + heightLi);
heightLi = 180;
Console.WriteLine("今年--张浩是身高是:" + heightZhang + ",李明的身高是:" + heightLi);
}
对值类型,不同的变量会分配不同的储存空间,并且存储空间中存储的是该变量的值。赋值操作传递的是变量的值,改变一个变量的值不会影响另一个变量的值。
引用类型源于System.Object家族,在C#中引用类型主要包括数组、类和接口等。
static void Main(string[] args)
{
int[] infoZhang = new int[] { 170, 60 };
int[] infoLi = infoZhang;
Console.WriteLine("去年--张浩是身高是:" + infoZhang[0] + "体重是:"+ infoZhang[1] + ",李明的身高是:" + infoLi[0]+"体重是:"+ infoLi[1]);
infoLi[0] = 180;
infoLi[1] = 70;
Console.WriteLine("去年--张浩是身高是:" + infoZhang[0] + "体重是:" + infoZhang[1] + ",李明的身高是:" + infoLi[0] + "体重是:" + infoLi[1]);
}
可以看到,标识符infoZhang只是一个引用,它只向了内存中创建的这个数组。如果把存储的对象(即数组)比作气球,那么我们的引用变量(即infoZhang)就是一根线。对引用类型,赋值就是把原对象的引用传递给另一个引用。对数组而言,当一个数组引用赋值给另一个数组引用后,这两个引用指向同一个数组,也就是指向同一块储存空间。
static void Main(string[] args)
{
int[] infoZhang = new int[] { 170, 60 };
int[] infoLi = new int[2];
Console.WriteLine( "复制前--李明的身高是:" + infoLi[0] + "体重是:" + infoLi[1]);
//数组复制
for (int i = 0; i < infoZhang.Length; i++)
{
infoLi[i] = infoZhang[i];
}
Console.WriteLine("去年--张浩是身高是:" + infoZhang[0] + "体重是:" + infoZhang[1] + ",李明的身高是:" + infoLi[0] + "体重是:" + infoLi[1]);
infoLi[0] = 180;
infoLi[1] = 70;
Console.WriteLine("去年--张浩是身高是:" + infoZhang[0] + "体重是:" + infoZhang[1] + ",李明的身高是:" + infoLi[0] + "体重是:" + infoLi[1]);
}
我们用到的int类型是值类型,数组是引用类型。C#中包含的值类型和引用类型如下表所示
类别 | 基本数据类型 | 描述 |
---|---|---|
值类型 | 整型:int | |
长整型:long | ||
浮点型:flo | ||
双精度型:double | ||
字符型:char | ||
布尔型:bool | ||
枚举类型 | 枚举:enum | |
结构类型 | 结构:struck | |
引用类型 | 类 | 基类:System.Object |
字符串:string | ||
自定义类:clas | ||
接口 | 接口:interface | |
数组 | 数组:int[],string[] |
访问修饰符 struck 结构名
{
//结构体
}
结构的定义有以下特点
结构的构成和类相似。在使用结构时,要注意以下几个方面。
public class Student
{
public int _id;
public int _age;
public void Show()
{
Console.WriteLine("ID:{0}\n年龄:{1}",_id,_age);
}
static void Main(string[] args)
{
Student stu = new Student();
stu._id = 1001;
stu._age = 20;
stu.Show();
}
}
结构是值类型,声明结构变量就存储一个结构的新副本,即系统要开辟一块新的储存空间,因此结构用的多所消耗的存储空间也就越多。所以当对象需要用较少的字段来表示时,就可以用结构来实现。
我们说数据类型按照存储方式可以分为值类型和引用类型,两者仍然可以相互转换。将值类型转换为引用类型的过程称为装箱,反之称为拆箱。
static void Main(string[] args)
{
int i = 123;
object o = i;
i = 456;
Console.WriteLine("值类型的值为{0}",i);
Console.WriteLine("引用类型的值为{0}",o);
}
static void Main(string[] args)
{
int i = 123;
object o = i;
int j = (int)o;
}
在实际的开发中,应尽量减少不必要的装箱和拆箱,因为二者的存储方式不同,转换时性能损失较大。
ArrayList非常类似于数组,也有人称它为数组列表,ArrayList可以动态维护。我们知道,数组的容量是固定的,而ArrayList的容量可以根据需要扩充,它的索引会根据程序的扩展而重新进行分配和调整。ArrayList提供了一系列方法对其中的元素进行访问、增加和删除操作。
Areyist类属于System.Coleliens命名空间,这个命名空间包含接口和类,这些接口和类定义各种对象的集合。ArrayList就属于集合的一种,因此,在使用ArrayList类之前一定要引入System.Coleliens命名空间。
ArrayList常用属性和方法入下:
属性名称 | 说明 | |
---|---|---|
Count | 获取ArrayList中实际包含的元素数 | |
返回值类型 | 方法名称 | 说明 |
Int | Add(Object value) | 将对象添加到ArrayList的结尾处 |
void | RemoveAt(int index) | 移除ArrayList指定索引处的元素 |
void | Remove(Object value) | 从ArrayList中移除特定对象 |
void | Clear() | 从ArrayList中移除所有元素 |
static void Main(string[] args)
{
ArrayList list = new ArrayList();
list.Add(16);
list.Add("董世豪");
}
返回值:值是一个int类型,用于返回所添加的元素的索引。
参数:如果向ArrayList中添加的元素是值类型,这些元素就都会装箱处理转换为Object引用类型,然后保存。因此,ArrayList中的所有元素都是对象的引用。
static void Main(string[] args)
{
ArrayList list = new ArrayList();
list.Add(16);
list.Add("董世豪");
Console.WriteLine(list[0].ToString());
}
ArrayList获取一个元素的方法和数组是一样的,也是通过索引来访问,ArrayList中第一个元素的索引是0.需要注意的是,由于给ArrayList都会被转换成Object类型,所以在访问这些元素的时候必须把他们转换回原来的数据类型。
static void Main(string[] args)
{
ArrayList list = new ArrayList();
list.Add(16);
list.Add("董世豪");
for (int i = 0; i < list.Count; i++)
{
Console.WriteLine("for循环输出:"+list[i]);
}
foreach(object obj in list)
{
Console.WriteLine("foreach循环输出:"+obj);
}
Console.WriteLine("长度:"+list.Count);
Console.WriteLine("名字:"+list[1]);
}
static void Main(string[] args)
{
ArrayList list = new ArrayList();
list.Add(16);
list.Add("董世豪");
for (int i = 0; i < list.Count; i++)
{
Console.WriteLine("for循环输出:"+list[i]);
}
foreach(object obj in list)
{
Console.WriteLine("foreach循环输出:"+obj);
}
list.RemoveAt(0);
list.Remove("董世豪");
list.Clear();
Console.WriteLine("长度:"+list.Count);
}
哈希表(HashTable)又叫做散列表,是根据关键码值(即键值对)而直接访问的数据结构。也就是说,它通过把关键码映射到表中一个位置来访问记录,以加快查找速度。看到这里你可能比较疑惑,它是怎么加快查找速度的?下一节就有说明!这个映射函数就叫做散列(哈希)函数,存放记录的数组叫做散列表。
Hashtable也属于System.Collections命名空间,它的每个元素都是一个键/值对。
属性名称 | 说明 | |
---|---|---|
Count | 获取Hashtable中实键/值对的数目 | |
Keys | 获取包含在Hashtable中键的集合 | |
Values | 获取包含在Hashtable中值的集合 | |
返回值类型 | 方法名称 | 说明 |
Void | Add(Object key,Object value) | 将带有指定键和值的元素添加到Hashtable中 |
void | RemoveAt(Object key) | 移除Hashtable指定带有特定键的元素 |
void | Clear() | 从Hashtable中移除所有元素 |
static void Main(string[] args)
{
Hashtable ha = new Hashtable();
ha.Add(1, "张三");
ha.Add(2, "王雪");
}
static void Main(string[] args)
{
Hashtable ha = new Hashtable();
ha.Add(1, "张三");
ha.Add(2, "王雪");
Console.WriteLine(ha[1]);
}
static void Main(string[] args)
{
Hashtable ha = new Hashtable();
ha.Add(1, "张三");
ha.Add(2, "王雪");
Console.WriteLine(ha[1]);
foreach(Object obj in ha.Values)
{
Console.WriteLine((string)obj);
}
foreach(DictionaryEntry entry in ha)
{
Console.WriteLine(ha.Keys);
Console.WriteLine(ha.Values);
}
}
static void Main(string[] args)
{
Hashtable ha = new Hashtable();
ha.Add(1, "张三");
ha.Add(2, "王雪");
Console.WriteLine(ha[1]);
foreach(Object obj in ha.Values)
{
Console.WriteLine((string)obj);
}
foreach(DictionaryEntry entry in ha)
{
Console.WriteLine(ha.Keys);
Console.WriteLine(ha.Values);
}
ha.Remove(1);
ha.Clear();
}
在System.Collections.Generic命名空间中定义了许多泛型集合类,可以用来替代之前的ArrayList和Hashtable。
List 对象名 = new List();
“
异同点 | List |
ArrayList |
---|---|---|
不同点 | 对所保存元素进行类型约束 | 可以增加任何类型 |
添加/读取值类型元素不需要装箱、拆箱 | 添加/读取值类型元素需要装箱、拆箱 | |
共同点 | 通过索引访问集合中的元素 | |
添加元素方法相同 | ||
删除元素方法相同 |
在C#中海油一种泛型集合Dictionary
Dictionary 对象名 = new Dictionary();
static void Main(string[] args)
{
Dictionary va = new Dictionary();
va.Add("16", "董世豪");
va.Add("66", "张三");
foreach(Object sa in va.Keys)
{
Console.WriteLine(sa);
}
foreach(Object str in va.Values)
{
Console.WriteLine(str);
}
va.Add(null, "123");//运行错误
}
异同点 | Dictionary |
Hashtable |
---|---|---|
不同点 | 对所保存元素进行类型约束 | 可以增加任何类型 |
添加/读取值类型元素不需要装箱、拆箱 | 添加/读取值类型元素需要装箱、拆箱 | |
共同点 | 通过索引访问集合中的元素 | |
添加元素方法相同 | ||
删除元素方法相同 |
public class 类名
{
//........
}
T指类型参数,代表具体的数据类型,可以使类类型,也可以是基本数据类型。
class ComboBoxItem
{
private string _itemText; //显示的文字
public string ItemText
{
get { return _itemText; }
set { _itemText = value; }
}
private T _itemValue; //实际的对象
public T ItemValue
{
get { return _itemValue; }
set { _itemValue = value; }
}
}
//创建ComboBox项,运行时确定泛型类支持的数据类型
ComboBoxItem itemJack = new ComboBoxItem();
itemJack.ItemText = jack.Name;
itemJack.ItemValue = jack;
ComboBoxItem itemJoe = new ComboBoxItem();
itemJack.ItemText = joe.Name;
itemJack.ItemValue = joe;
//创建Items
List> items = new List>();
items.Add(itemJack);
items.Add(itemJoe);
//绑定Items
this.cmbEngineers.DataSource = items;
this.cmbEngineers.DisplayMember = "ItemText";
this.cmbEngineers.ValueMember = "ItemValue";
泛型的优点
类的构造函数时类中的一种特殊方法,它具有以下特点:
在默认的情况下,系统将会给类分配一个无参构造函数,并且没有方法体。我们也可以自定义一个无参构造函数,在无参构造函数的方法体中对类的属性进行赋值。
访问修饰符 类名()
{
//方法体
}
class Program
{
public class SE
{
private string name;
public string Name
{
get { return name; }
set { name = value; }
}
private int age;
public int Age
{
get { return age; }
set { age = value; }
}
private string gender;
public string Gender
{
get { return gender; }
set { gender = value; }
}
private string id;
public string ID
{
get { return id; }
set { id = value; }
}
private int popularity;
public int Popularity
{
get { return popularity; }
set { popularity = value; }
}
internal string SayHi()
{
throw new NotImplementedException();
}
public SE()
{
this.ID = "000";
this.Age = 20;
this.Name = "无名氏";
this.Gender = "男";
this.Popularity = 0;
}
}
static void Main(string[] args)
{
SE se = new SE();
Console.WriteLine(se.SayHi());
}
}
通过上述示例我们可以发现,在无参构造函数中给属性赋予默认值有一个明显的缺点,就是对象实例化后的属性值是固定的,为满足对象多样化的需求,不得不修改代码重新给属性赋值。因此就出现了带参构造函数。
访问修饰符 类名(参数列表)
{
//方法体
}
class Program
{
public class SE
{
private string name;
public string Name
{
get { return name; }
set { name = value; }
}
private int age;
public int Age
{
get { return age; }
set { age = value; }
}
private string gender;
public string Gender
{
get { return gender; }
set { gender = value; }
}
private string id;
public string ID
{
get { return id; }
set { id = value; }
}
private int popularity;
public int Popularity
{
get { return popularity; }
set { popularity = value; }
}
internal string SayHi()
{
throw new NotImplementedException();
}
public SE(string id,string name,int age,string gender,int popularity)
{
this.ID = id;
this.Name = name;
this.Age = age;
this.Gender = gender;
this.Popularity = popularity;
}
}
static void Main(string[] args)
{
SE se = new SE("112", "艾边成", 25, "男", 100);
Console.WriteLine(se.SayHi());
}
}
很显然,带参构造函数的灵活性更好,它通过参数来动态控制对象的特征。
当不给类编写构造函数时,系统将自动给类分配一个无参构造函数,称为隐式构造函数。C#有一个规定,一旦类有了构造函数,就不再自动分配构造函数。
方法重载是指在同一个类中方法同名,参数不同,调用时根据实参的形式,选择与他匹配的方法执行操作的一种技术。
这里所说的参数不同是指以下几种情况:
参数的类型不同
参数的个数不同
参数的个数相同时他们的先后顺序不同
注意:系统会认为是同一个方法的两种情况,这样的两个方法不可以在同一个类里,否则系统会报错。
返回类型不同,方法名和参数个数、顺序、类型都相同的两个方法
返回类型相同,方法名和参数的个数、顺序、类型都相同的两个方法,但是参数的名字不同
class CompSalary
{
public static void Pay(PM pm)
{
float money = pm.BasePay + pm.MgrPrize + pm.Bonus;
Console.WriteLine("项目经理的薪水:"+money);
}
public static void Pay(SE se)
{
float money = se.BasePay + se.MeritPay;
Console.WriteLine("程序员的薪水:" + money);
}
static void Main(string[] args)
{
SE se = new SE("112", "艾边成", 25, "男", 100);
se.BasePay = 4000;
se.MeritPay = 3000;
PM joe = new PM("890", "乔布斯", 50, "男", 10);
joe.BasePay = 8000;
joe.MgrPrize = 4000;
joe.Bonus = 2000;
CompSalary.Pay(se);
CompSalary.Pay(joe);
Console.ReadLine();
}
}
重载最大的作用就是省了给方法取名的功夫,方法重载补技能避免命名的麻烦,还能使调用者不必判断方法名就可直接调用。
每个类都有自己的特性和功能,我们把它们封装为属性和方法。对象之间通过属性和方法进行交互。可以认为方法的参数及方法的返回值都是对象间相互传递的消息。
public class Television
{
private Boolean isOn = false;
public void Open()
{
if (isOn)
{
Console.WriteLine("电视机已打开!");
}
else
{
Console.WriteLine("成功打开电视机!");
isOn = true;
}
}
public void TurnOff()
{
if (isOn)
{
Console.WriteLine("正在关机.....");
isOn = false;
}
else
{
Console.WriteLine("电视机已关闭!");
}
}
public void Change(string channelNo)
{
if (isOn)
{
Console.WriteLine("正在切换到{0}台", channelNo);
}
}
}
public class RemoteControl
{
public void TurnOn(Television tv)
{
tv.Open();
}
public void TurnOff(Television tv)
{
tv.TurnOff();
}
public void ChangeChannel(Television tv)
{
Console.WriteLine("请输入频道号:");
string channelNo = Console.ReadLine();
tv.Change(channelNo);
}
}
class Program
{
static void Main(string[] args)
{
RemoteControl control = new RemoteControl();
Television tv = new Television();
control.TurnOn(tv);
control.ChangeChannel(tv);
control.TurnOff(tv);
Console.ReadLine();
}
}
上述示例给我们演示了遥控器对象和电视机对象交互的过程。从其中可以看出,对象间交互主要通过参数传递、方法调用及属性操作等来实现。在面向对象的程序中,对象通过公开方法和属性完成与其他对象的交互。可以认为方法的参数及方法的返回值都是对象间相互传递的消息。
在C#中,一个类可以继承另一个类。被继承的类称为父类或者基类,继承其他类的类称为子类或者派生类,继承是使用已存在的类的定义作为基础建立新类的技术,新类的定义可以增加新的数据或新的功能,也可以用已存在的类的功能。
public class B:A
{
A:父类,基类
B:子类,派生类
}
internal string SayHi()
{
string message = string.Format("大家好,我是{0},今年{1}岁,工号是{2},我的人气值高达{3}!", base.Name, base.Age, base.ID, this.Popularity);
return message;
}
在子类中可以用base调用父类的属性。实际上,还可以用base关键字调用父类的方法及父类的构造函数。父类中的成员如果用private修饰,它将作为私有变量,其他任何类都无法访问。如果设置为公有(public)成员,任何类都可以访问该成员,这不符合我们的要求。C#中提供了另一种访问修饰符protected,被这个访问修饰符修饰的成员允许被其子类访问,而其他类就无法访问了。
private string id;
private int age;
private string name;
private string gender;
protected string Name { get => name; set => name = value; }
protected string ID { get => id; set => id = value; }
protected int Age { get => age; set => age = value; }
protected string Gender { get => gender; set => gender = value; }
修饰符 | 类内部 | 子类 | 其他类 |
---|---|---|---|
Public | 可以 | 可以 | 可以 |
Protected | 可以 | 可以 | 不可以 |
Private | 可以 | 不可以 | 不可以 |
1.隐式调用父类构造函数
public class Employee
{
public Employee()
{
Console.WriteLine("父类无参对象构造执行!");
}
public Employee(string id, string name, int age, string gender)
{
this.ID = id;
this.Name = name;
this.Age = age;
this.Gender = gender;
}
private string id;
private int age;
private string name;
private string gender;
public string Name { get => name; set => name = value; }
public string ID { get => id; set => id = value; }
public int Age { get => age; set => age = value; }
public string Gender { get => gender; set => gender = value; }
}
创建子类对象时会首先调用父类的构造函数,然后才会调用子类本身的构造函数。由于没有指明要调用父类的哪一个构造函数,所以系统隐式地调用了父类的无参构造函数。
2.显示调用父类构造函数
在之前的知识点有提到,C#可以用base关键字调用父类的构造函数。只要在子类的构造函数后添加“:base(参数列表)”,就可以指定该子类的构造函数调用父类的哪一个构造函数了。这样便可以实现继承属性的初始化,然后再子类本身的构造函数中完成对子类特有属性的初始化即可。
public class Employee
{
private string id;
private int age;
private string name;
private string gender;
public string Name { get => name; set => name = value; }
public string ID { get => id; set => id = value; }
public int Age { get => age; set => age = value; }
public string Gender { get => gender; set => gender = value; }
public Employee(string id, string name, int age, string gender)
{
this.ID = id;
this.Name = name;
this.Age = age;
this.Gender = gender;
}
}
1.继承的传递性
举个例子,小型卡车和重型卡车都具有卡车的特征,如载重量、拉货、卸货等,同时它也具有汽车的各种特征。继承需要符合is a的关系,“小型卡车is a卡车,卡车is a汽车,小型卡车is a汽车”,这就是继承的传递性。
public class SmallTruck:Truck
{
public SmallTruck(string type, string place) : base(type, place) { }
public SmallTruck() { }
public void SmallTruckRun()
{
Console.WriteLine("小型卡车在行驶!");
}
}
static void Main(string[] args)
{
SmallTruck smallTruck = new SmallTruck();
smallTruck.VehicleRun();
smallTruck.TruckRun();
smallTruck.SmallTruckRun();
Console.ReadLine();
}
2.继承的单根性
在C#中明确规定,一个子类不能同时继承多个父类。
优:
缺:
多态指同一个实体同时具有多种形式。它是面向对象程序设计(OOP)的一个重要特征。如果一个语言只支持类而不支持多态,只能说明它是基于对象的,而不是面向对象的。同一操作作用于不同的对象,可以有不同的解释,产生不同的执行结果。在运行时,可以通过指向基类的指针,来调用实现派生类中的方法。
举个例子,我们在遍历员工集合实现问好功能时,需要用is关键字判断每个员工的类型。这会带来一个问题,如果员工越多,问好的方法各不相同,那程序在面对如此多的对象就要编写非常多的if判断,使程序变得庞大,扩展困难。
public virtual string SayHi()
{
string message = string.Format("大家好!");
return message;
}
public override string SayHi()
{
string message = string.Format("大家好,我是{0},今年{1}岁,工号是{2},我的人气值高达{3}!", base.Name, base.Age, base.ID, this.Popularity);
return message;
}
static void Main(string[] args)
{
SE ai = new SE("112", "艾边成", 25, "男", 100);
SE joe = new SE("113", "joe", 30, "男", 200);
PM pm = new PM("890", "盖茨", 50, "男", 30);
List empls = new List();
empls.Add(ai);
empls.Add(joe);
empls.Add(pm);
foreach (Employee empl in empls)
{
Console.WriteLine(empl.SayHi());
}
Employee ema = new SE("210", "Ema", 33, "男", 100);
Console.WriteLine(ema.SayHi());
Console.ReadLine();
}
观察上述示例,我们在父类中定义了下面的方法
public virtual string SayHi()
{
//....
}
像这种使用virtual关键字修饰的方法,称为虚方法,虚方法是有方法体的。而子类中,我们定义了这样的方法
public override string SayHi()
{
//....
}
像这种通过override关键字来修饰的方法,称为方法的重写。虚方法可以被重写。
注意:父类中定义的虚方法并非必须被子类重写。在父类中可以给出虚方法的默认实现。如果子类不重写父类的虚方法,依然执行父类的默认实现。如果子类重写了父类的虚方法,执行子类重写后的方法。
is操作符用于检查对象和指定的类型是否兼容,而as操作符主要用于两个对象之间的类型转换。
static void Main(string[] args)
{
SE ai = new SE("112", "艾边成", 25, "男", 100);
SE joe = new SE("113", "Joe", 30, "男", 200);
PM gates = new PM("890", "盖茨", 50, "男", 30);
List empls = new List();
empls.Add(ai);
empls.Add(joe);
empls.Add(gates);
for (int i = 0; i < empls.Count; i++)
{
if(empls[i] is SE)
{
SE se = empls[i] as SE;
Console.WriteLine(se.SayHi());
}
if (empls[i] is PM)
{
PM pm = empls[i] as PM;
Console.WriteLine(pm.SayHi());
}
}
Console.ReadLine();
}
示例在遍历时,先用is关键字来判断对象是属于SE还是PM,判断完毕,用as关键字来将Employee对象转换为对应的SE或PM对象。也可以用强制类型转换来代替as关键字,不同的是,强制类型转换如果不成功将会报告异常,而as关键字如果转换失败会返回null,不会产生异常。
访问修饰符 abstract 返回类型 方法名();
注意,抽象方法没有闭合的大括号,而是直接跟了一个分号,也就是说,抽象方法是没有方法体的,它只提供方法的定义。
访问修饰符 abstract class 类名{}
抽象类提供抽象方法,这些方法只有定义,如何实现都由抽象类的非抽象子类完成。
当从一个抽象父类派生子类时,子类将继承父类的所有特征,包括它未实现的抽象方法。抽象方法必须在其子类中实现,除非它的子类也是抽象类。与子类重写父类的虚方法一样,在子类中也是使用override关键字来重写抽象方法的。
public class Tube: TrafficTool
{
public override void Run()
{
Console.WriteLine("地铁运行中!");
}
}
抽象类特点:
虚方法 | 抽象方法 |
---|---|
用virtual修饰 | 用abstract修饰 |
要有方法体,即使是一个分号 | 不允许有方法体 |
可以被子类override | 必须被子类override |
除了密封类外都可以定义 | 只能在抽象类中定义 |
单一职责(SRP):不要存在多于一个导致类变更的原因。例如某个类有了两个方法,每个方法负责一个职能,假如方法一的职责需要发生变化,那么就需要修改这个类文件,这种改变就可能导致原本运行正常的方法二发生故障。对于这种两个职责经常发生变化的方法,需要分别建立相应的 java 文件分别负责相应的职能,避免一方暂时改变另一方不变的情况下,相互影响。通常情况下我们对类单一职责要求较低,但是接口和方法尽量是保证单一职责。根据你的业务要求所写的类或者接口方法最好能满足需求即可,不需要对其进行可能会用到的扩展,如果真的有,那就再写一个方法、类、接口。
里氏替换原则(LSP):如果对每一个类型为 T1 的对象 o1,都有类型为 T2 的对象 o2,使得以 T1 定义的所有程序 P 在所有的对象 o1 都替换成 o2 时,程序 P 的行为没有发生变化,那么类型 T2 是类型 T1 的子类型。通过这个定义我们可以引申出来一个定义继承,T1 是 T2 的父类。里式替换原则是继承复用的基石,当子类可以替换父类,并且软件功能不受影响时,父类才能真正的被复用,子类也可以增加自己新的功能,里式替换原则是对开闭原则的一个补充,它是对实现抽象化的具体步骤规范。里式替换原则所表达的含义就是反对子类重写父类方法的这一含义。
依赖倒置原则(DIP):高层模块不应该依赖低层模块,二者都应该依赖其抽象。抽象不应该依赖细节;细节应该依赖抽象。针对接口编程,不要针对实现编程。通过抽象包括使用接口或者抽象类,可以使个各类或者模块的实现彼此独立,互不影响,从而实现模块间的耦合性。简单来说就是程序应该依赖于接口而不是实现类。
接口隔离原则(ISP):用多个专门的接口,而不使用单一的总接口,客户端不应该依赖它不需要的接口。一个类对一个类的依赖应该建立在最小的接口上。建立单一的接口,不要建立庞大臃肿的接口。尽量细化接口,接口中的方法尽量少。注意适度原则。
开闭原则(OCP):一个软件实体如类、模块函数应该对扩展开放,对修改关闭。强调的是用抽象构建框架,用实现扩展细节。以提高软件系统的可复用性及可维护性帮助我们实现稳定灵活的系统架构。生活中的实例(弹性工作制,每天必须工作满八小时这个是不能修改的,但是对于什么时候来什么时候走没有规定)。实现开闭原则的核心思想是面向抽象编程。
迪米特原则(LOD):一个对象应该对其他对象保持最少的了解,又叫最少知道原则。尽量降低类与类之间的耦合。高内聚低耦合的体现。大概意思就是能自己解决的就不要去求别人。强调只和朋友交流,不和陌生人说话。保持神秘
合成复用原则(CRP):尽量使用对象组合/聚合,而不是继承关系达到软件复用的目的。