介绍
C#语言集成了Java和C++的优点,在全面的基础上发展了面向对象的概念,比如委托和范型,是从C++语言的函数指针和模版概念进化而来的,而单根的面向对象结构,即所有的对象都是继承于object类,这在很大程度上来自Java语言的思想。
主要内容:C#创建类和实例、虚拟类、抽象类、封装类、静态类、范型类、类的属性、方法、事件、C#语言委托远离和用法、匿名类、拓展方法
类和实例
1. 类和实例
C#引入类的概念,以全面支持对象编程。使用class关键字定义一个类,可以在类前面添加各种修饰符。
类把实例抽象成一个概念,而实例具有类定义的这些特征。
可以通过"[]“符号对类进行设置,一个比较重要的设置是”[Serializable]",这个设置表示当前的类是可序列化的。序列化的主要作用是类实例数据在传递的时候维持原样
[Serializable]
public class Circle
{
}
C#语言使用new关键字调用类的构造方法创建类的实例。
Circle MyCircle = new Circle();
2. 继承类
所谓继承,就是一个类引入另一个类的内容,包括属性、方法是事件等。引入其他类的,称为子类,提供引入的类,称为父类。在子类中能够不必深;就调用父类的属性和方法。
继承是面向对象机制中很重要的特征,经常作为标准用于判断编程语言是否位真正的面向对象语言。
C#是单继承的面向对象语言,而我们熟悉的Java也是单继承的语言,当C++是多继承的语言。
using System;
namespace ExtendsDemo
{
public class Employee
{
public string Name
{
get;
set;
}
public int Age
{
get;
set;
}
}
public class Manager : Employee
{
public Manager(string ManagerName, int ManagerAge) : base()
{
Name = ManagerName;
Age = ManagerAge;
}
}
/*
子类构造函数中的“:base()”,表示先调用父类的构造方法
*/
}
3. 接口
接口也是面向对象编程中的重要概念之一。许多精通面向对象的开发人员都建议针对接口编程,二不输出针对类编程。
接口在更多的意义上是声明一种规范,继承接口的类都必须遵循这个规范。
C#编程语言的接口,具有以下特征:
- 接口不能被实例化,也不能包含字段
- 接口的索引、属性、方法、事件等内容,只可声明而不可具体实现
- 继承A接口的B接口,具有A接口的特征
- 接口可以被多重继承,即接口或类可以同时继承多个接口
using System;
namespace InterfaceDemo
{
public interface IEntry
{
string EmployeeRecords
{
get;
set;
}
void Train(int EmployeeID);
}
}
using System;
namespace InterfaceDemo
{
public class NewEmployee
{
public int NewEmployeeID
{
get;
set;
}
/// <summary>
/// 保存新员工数据
/// </summary>
/// <param name="employeeToSave">接口</param>
public void SaveNewEmployee(IEntry employeeToSave)
{
// 获取接口中定义的属性
string RecordsData = employeeToSave.EmployeeRecords;
// 调用接口中定义的方法
employeeToSave.Train(NewEmployeeID);
}
}
}
4. 实现接口
在C#语言中,继承接口的类必须实现接口定义的内容,否则就会出现编译错误。
using System;
namespace InterfaceDemo
{
/// <summary>
/// 新员工类,继承就职IEntry接口
/// </summary>
class Employee : IEntry
{
public string TrainRecords
{
get;
set;
}
// 实现员工档案的属性
string IEntry.EmployeeRecords
{
get;
set;
}
// 实现员工档案的方法
void IEntry.Train(int EmployeeID)
{
TrainRecords = "公司制度培训和薪酬制度培训";
}
/*
显式方式实现,在属性和方法名之前添加IEntry前缀,表名当前属性或者方法实现了IEntry接口
不能在属性或者方法前添加public前缀,因为接口声明的属性和方法必须允许外部程序访问,否则这种声明就没有意义
*/
}
/// <summary>
/// 老员工类,继承就职IEntry接口
/// </summary>
public class Record : IEntry
{
public string TrainRecords
{
get;
set;
}
// 实现员工档案的属性
public string EmployeeRecords
{
get;
set;
}
/// <summary>
/// 实现员工档案培训的方法
/// </summary>
/// <param name="EmployeeID"></param>
public void Train(int EmployeeID)
{
TrainRecords = "薪酬制度培训";
}
/*
隐式方法实现,不在属性或方法前添加"IEntry"前缀,必须将属性或方法的访问控制符定义为"public",非e编译程序不认为这个类实现了接口
*/
}
}
注意:接口的显式实现如果不声明访问权限修饰符,那么默认为public,如果添加访问权限修饰符,那么必须是public
5. 继承接口
继承接口有两个含义,类继承接口,以及接口继承接口,类继承接口,必须要实现接口的声明。
在一个系统中,客户的需求可能随时在改变,原有接口的修改意味着继承这个接口的所有类,都必须修改以实现接口,为了避免这种牵一发而动全身的灾难出现,我们可以借助于接口的继承来适应规范的改变。
using System;
namespace InterfaceDemo
{
public interface IEntryExamine : IEntry
{
void Examine(int EmployeeID);
}
/// <summary>
/// 新员工类,继承就职IEntry接口
/// </summary>
class Employee : IEntryExamine
{
public string TrainRecords
{
get;
set;
}
// 实现员工档案的属性
string IEntry.EmployeeRecords
{
get;
set;
}
// 实现员工档案的方法
void IEntry.Train(int EmployeeID)
{
TrainRecords = "公司制度培训和薪酬制度培训";
}
void IEntryExamine.Examine(int EmployeeID)
{
TrainRecords += "需要进行考试";
}
}
/// <summary>
/// 老员工类,继承就职IEntry接口
/// </summary>
public class Record : IEntry
{
public string TrainRecords
{
get;
set;
}
// 实现员工档案的属性
public string EmployeeRecords
{
get;
set;
}
/// <summary>
/// 实现员工档案培训的方法
/// </summary>
/// <param name="EmployeeID"></param>
public void Train(int EmployeeID)
{
TrainRecords = "薪酬制度培训";
}
}
}
可以使用接口的多继承,来完善员工入职的模型。
接口的继承和多重继承,不仅为C#编程语言中类的继承增加了灵活性,更重要的是能以可扩展的方式,规范了类的设计和编码实现。
虚拟类、抽象类和封装类
1. 虚拟类
虚拟类并不完全是虚拟的,致所有被称为“虚拟”,是因为C#编程语言借鉴了C++的“虚拟函数”的概念。在C++编程语言中,只有纯虚函数是没有实现代码的,所以“虚拟”的说法并不能确切表示虚拟类的特点,更多上是为了使用C++开发人员的习惯。
实际上,完全可以在C#编程语言的虚拟方法中编写实现方法的代码。
在C#编程语言中,虚拟类是一种可以在子类中覆盖其方法或者属性的类。
C#程序在类的内部,使用virtual声明一个方法或者属性,飙车在子类中可以覆盖这个方法或者属性。在子类的属性或方法声明之前,使用override关键字,表示当前方法或属性将覆盖父类定义的方法或属性。
覆盖具有以下的原则:
- 不能覆盖非虚方法、非虚属性或静态方法、静态属性
- 可以覆盖的方法或属性必须是virtual、abstract或override的。因为子类继承了父类的可覆盖特征,所以override的方法或属性还可以在子类中覆盖
- 覆盖的方法或属性,不能更改原方法或属性的访问控制符、参数列表、返回数据类型和名称
- 不能使用修饰符new、static、virtual或abstract修改覆盖的方法或属性
重写、重载、覆盖的概念
项目 | 重写 | 重载 | 覆盖 |
---|---|---|---|
关键字 | 无 | new | override |
位置 | 同一个类内部 | 子类 | 子类 |
访问控制 | 可以不一致 | 可以不一致 | 必须一致 |
名称 | 必须一致 | 必须一致 | 必须一致 |
参数列表 | 必须不一致 | 可以不一致 | 必须一致 |
返回类型 | 可以不一致 | 可以不一致 | 必须一致 |
using System;
namespace DifferentClassDemo
{
public class MyWebPage
{
/// <summary>
/// 显示类名称的方法,声明为virtual,表示可以在子类中覆盖该方法
/// </summary>
/// <param name="Language"></param>
/// <returns></returns>
protected virtual string ShowName(string Language)
{
switch (Language)
{
case "中文":
return "我的页面";
case "English":
return "MyWebPage";
default:
return "";
}
}
/// <summary>
/// 方法重载,参数类型不一致
/// </summary>
/// <returns></returns>
protected string ShowName()
{
return "我的页面";
}
}
/// <summary>
/// 错误界面,继承自MyWebPage
/// </summary>
public class ErrorPage : MyWebPage
{
/// <summary>
/// 方法重写,访问控制不一致
/// </summary>
/// <returns></returns>
public new string ShowName()
{
return "错误页面";
}
}
/// <summary>
/// 登陆界面,继承自MyWebPage
/// </summary>
public class LoginPage : MyWebPage
{
/// <summary>
/// 覆盖,与父类的方法一致
/// </summary>
/// <param name="Language"></param>
/// <returns></returns>
protected override string ShowName(string Language)
{
switch (Language)
{
case "中文":
return "登陆页面";
case "English":
return "LoginPage";
default:
return "";
}
}
}
}
事实证明,虚拟类的虚拟方法具有很灵活的实现方式,这里的“虚拟”是从C++语言参考而来的概念,并不意味着虚拟方法不能在类中实现,只是在父类实现的方法,很有机会被子类覆盖得面目全非,失去了原来的功能而已。
2. 抽象类
抽象的本意是抽取某些事物具有代表性的特征,作为一个概念来描述这些事物的共性。
抽象的含义在这里表现为不能创建实例,抽象类只声明了需要实现的内容,具体的功能代码在继承它的子类中编写。
C#语言中的抽象类,仅仅提供了子类所必须包含的内容,而不能够创建实例。抽象类的作用和接口有些类似,当并不完全一样。在C#编程语言中,使用abstract关键字声明一个抽象类,抽象类有以下的特点:
- 抽象类不能使用new关键字创建类实例
- 抽象类允许簇拥非抽象成员
- 抽象类不能同时又是封装的
- 继承抽象类的非抽象类,必须实现所有的抽象成员
using System;
namespace DifferentClassDemo
{
/// <summary>
/// 表示形状的类
/// </summary>
public abstract class Shape
{
public int BasePointX
{
get;
set;
}
public int BasePointY
{
get;
set;
}
public abstract int MaxLength
{
get;
set;
}
public abstract int MaxWidth
{
get;
set;
}
public abstract decimal GetArea();
}
public class Rectangle : Shape
{
public override int MaxLength
{
get;
set;
}
public override int MaxWidth
{
get;
set;
}
public override decimal GetArea()
{
return MaxLength * MaxWidth;
}
}
}
3. 封装类
抽象类不能声明为封装类,对于抽象类而言,抽象类可以被继承,实际上必须被继承,是不能创建实例的,封装类刚好相反,可以创建实例而不能被继承。
C#中不允许将一个类同时设置为抽象和封装,因为这样做是没有任何意义的。
C#编程语言使用关键字“sealed”表示一个封装类,封装类不能被继承。同样,“sealed”可以用于属性和方法,表示不能够在子类中覆盖。
using System;
namespace DifferentClassDemo
{
/// <summary>
/// 只能创建一个实例的类
/// </summary>
public sealed class Singleton
{
static Singleton instance = null;
private Singleton()
{
}
public static Singleton Instance
{
get
{
if(instance == null)
{
instance = new Singleton();
}
return instance;
}
}
}
}
这个案例是典型的Singleton模式的实现方式之一,为了禁止外部程序创建重复的类实例,Singleton类需要屏蔽外部程序访问构造函数,同时只允许通过只读的Instance静态属性获取唯一的Singleton类实例。
如果不把类声明成封装类,仍然可以通过继承的途径,重复创建实例。所以,必须堵上所有继承来创建Singleton类实例的途径,而使Singleton类不能被继承,就应该使用sealed关键字声明Singleton,也就是说,Singleton是一个封装类。
因为封装在运行的时候不考虑继承、重载等因素,所以执行效率不比封装的类更高,但是封装类不能被继承,所以封装性能不足,在程序中编写封装类的时候,应当充分考虑系统设计的要求。
静态类和范型类
静态类和范型类更多意义上是为了设计与编程上的方便而引入的,所谓静态类,就是不能创建实例的类,所有的属性和方法都通过类名称来调用。相对于静态类而言,范型类对于提高代码重用性的作用更为明显,范型的愿望其实很简单,就是在声明的时候不确定数据类型,在调用时才确定数据类型,范型的引入显著提高了代码的重用性。
静态类和范型类注意作用是提高程序效率,并且使代码具有较高的重用性。虽然静态类看起来很死板,当Virtual Studio的LINQ和拓展方法等新技术都是借助静态方法来实现的,所以静态方法间接地提高了代码的重用性,仅仅把静态类当作全局变量来调用,还不能充分发挥它的作用。
1. 静态类
在C#编程语言中,有一种用static关键字来声明的静态类,使用静态类或者Singleton模式可以满足全局变量和全局方法的要求。静态类并不能创建实例,所有的调用都是使用类名称进行,这相当于无论何时,静态类总能提供一个所有类都能调用的变量或者方法。
在一个类中对静态的静态属性赋值,然后在另一个类中调用这个静态属性,因为没有创建类实例的过程,所有静态属性都能吧值是一个类原原本本地传递到另一个类中。静态类和设计模式中的Singleton模式有异曲同工之妙。
静态类不是为了实现全局变量而设计的,静态类的真正作用,并不仅仅为了实现全局变量,C#语言的静态类有以下的特点:
- 静态类的所有成员都是静态的,无论是属性还是方法,都使用static关键字声明
- 静态类的构造方法也是静态的,静态构造方法不能在子类中继承,并且不能带有private、public等控制访问符,也不能带有参数
- 不能直接调用静态类的构造函数,在调用静态类成员时会自动执行而且仅执行一次
using System;
namespace DifferentClassDemo
{
/// <summary>
/// 将数字字符转换成十六进制字符串的静态类
/// </summary>
public static class StrringToHexCode
{
/// <summary>
/// 静态方法,将字符串转换成十六进制字符串
/// </summary>
/// <param name="value">需要转换的字符串</param>
/// <returns>已经转换了的字符串</returns>
public static string ConvertToHexCode(string value)
{
return Int32.Parse(value).ToString("x");
}
/// <summary>
/// 拓展静态方法,将字符串转换成十六进制字符串
/// </summary>
/// <param name="value">需要转换的字符串</param>
/// <returns>已经转换了的字符串</returns>
public static string ToHexCode(this string value)
{
return Int32.Parse(value).ToString("x");
}
/*
拓展方法的使用:
string HexCode = "5000".ToHexCode();
*/
}
}
可以在静态类中写拓展方法,使用静态类的静态方法,使各种数据类型具有极强的拓展性。
静态类这种特殊的类,大量用于基础架构的实现。
2. 范型类
范型编程历来就是比较高级的编程理念,实现起来也是非常困难。
C++编程语言使用了模块的概念,使C++编程语言能够支持范型编程,C#编程语言的范型和C++编程语言的模版在实现的机制上稍有不同,但在使用上几乎没有区别。
.NET Framework从2.0就开始支持范型数据类型,这是范型对传统的面向对象编程有相当的冲击力。首先,一切对象基于object类的结构,被认为是“弱类型”的,相当于“强类型”的类,弱类型主要的劣势在于编译时不尽兴严格的数据类型检查,点a的执行速度也相对较慢。
因为强类型编程提出了类型参数话的要求,简单的说,就是声明一个方法的参数时,一般情况下都是以“数据类型 参数名”的形式来声明的,因为强类型编程语言要严格检查参数的数据类型,所以这种代码在强类型的编程语言中重用性不高。
这时,提供了另一种方法,生;参数的时候,暂时不指定参数的数据类型,用一个“T”符号先把位置占了,以后再把特定的数据类型替换上去。这个“T”符号已经被业界用了很久。
范型编程带来的好处有以下三个方面:
- 降低强制转换时装箱操作带来的成本和风险
- 为C#编程语言的强类型编程提供了支持
- 提高C#程序代码的重用性
using System;
using System.Collections;
namespace DifferentClassDemo
{
/*
在范型类声明时使用where语句限定范型的数据类型,称为范型约束,代码中的“where T:class”语句,限定EmployeeList的元素只能是引用类型的数据类型
*/
public class EmployeeList<T> : CollectioinBase
where T : class
{
public T this[int index]
{
get
{
return (T)List[index];
}
set
{
List[index] = value;
}
}
public int Add(T value)
{
return List.Add(value);
}
public void Remove(T value)
{
List.Remove(value);
}
}
}
using System;
using System.Collections;
namespace DifferentClassDemo
{
public class Employee
{
public string Name
{
get;
set;
}
public int Age
{
get;
set;
}
}
public class Manager
{
public string Position
{
get;
set;
}
public string Department
{
get;
set;
}
}
public class Test
{
static void Main(string[] args)
{
EmployeeList<Employee> CurrentEmployees = new EmployeeList<Employee>();
CurrentEmployees.Add(new Employee() { Name="Jack", Age = 54});
EmployeeList<Manager> CurrentManagers = new EmployeeList<Manager>();
CurrentManagers.Add(new Manager() { Department="技术部", Position="程序员"});
}
}
}