Web开发之C#:(6)C#引用类型
1.类
类是 .NET Framework 中的常规类型系统的一种基本构造,封装着一组整体作为一个逻辑单位的数据和行为。 数据和行为是该类“成员”,它们包含方法、属性和事件等。
类是一种“引用类型”。 创建类的对象时,对象赋值到的变量只保存对该内存的引用。 将对象引用赋给新变量时,新变量引用的是原始对象。通过一个变量做出的更改将反映在另一个变量中,因为两者引为用同一数据。
类通常用于对较为复杂的行为建模,或对要在创建类对象后进行修改的数据建模( 结构最适合一些小型数据结构,这些数据结构包含的数据以创建结构后不修改的数据为主)。
静态类
不需要实例化的类,直接从System.Object派生。只能定义静态成员,不能定义实例成员,不能实现接口,不能做为其他类型的基类,不能做为字段、方法参数、或局部变量使用。
1.1 类的定义
从已有类派生自己的类,若没有指定父类,默认的直接继承自Object类。
值类型(结构类型)定义public struct Char{}public struct Int32{}用struct关键字
引用类型(类类型)定义public sealed class String{}用class关键字
(A)用class关键字定义整个类
[访问修饰符] [abstract] class 类名
{
// 成员(包括字段、属性、方法、构造方法、索引器、事件、委托、结构等)
}
(B)用partial class关键字定义分部类,如:
public partial class MyClass
{
string firstName;
string lastName
}
public partial class MyClass
{
int age;
int Num;
}
1.2 创建类的实例
遵循先声明,后赋值,再使用的步骤。
声明MyClass类型的变量
创建MyClass类的新实例(对象)(使用new关键字)
将创建的实例(对象)赋给变量,完成了实例化。
MyClass mc;
mc = new Myclass();
以上两步可以合在一起完成:MyClass mc= new Myclass();
完成对象实例化后,才是创建好了一个可用的实例,才可以访问实例中的成员。
1.3 类的封装性
1.3.1 封装的理解
属性是对字段的封装,方法是对过程的封装,类是对对象的封装(特征与功能)。
程序集是对类库的封装(*)
(1)封装的应用实例:建一个dll文件夹,将dll文件放进来,添加引用即可调用
注意:生成.dll类库文件,需要在新建项目的时候选择“类库”。
(2)封装的应用实例:在一个项目调用另一个项目中的方法需要注意:
(A)添加引用,导入命名空间
(B)被调用的方法所在的类需是用public修饰的。
(C)被调用的方法本身需是用public static修饰的,否则需创建类的实例调用。
(D)两个项目的类不要重名,否则就需要写全命名空间才能调用。
1.3.2 类的封装性
1.4 类的继承性
1.4.1 继承之里氏转换原则
第1条:子类对象可以直接赋值给类对象(子类可以隐式转换为父类)
第2条:指向子类的父类对象,可以强制转化为该子类对象
is运算符:if(对象 is 类型)与 第2条配合使用
示例:造人(Teacher、Student继承自Person类)
static void Main(string[] args)
{
Teacher t = new Teacher("胡安明",'男',23);
Student s = new Student("皓皓",'女',27,"瑜伽");
Teacher tt = new Teacher("黄小明",'男',32);
Person[] p = {t,s,tt}; //第1条 子类隐式转换为父类
for (int i = 0; i < p.Length; i )
{
if(p[i] is Student)
{
Student s1 = (Student)p[i];
s1.SayHello();
}
else
{
Teacher t1 = (Teacher)p[i];
t1.SayHello();
}
}
}
附:is运算符和as运算符
对象 is 类型;//返回bool值
子类类名 变量 = 父类变量 as 子类;//转换成功完成赋值,不成功赋null。
1.4..2 继承之构造方法的执行
new的时候调用构造方法,(其他时候不能用),执行时先去调用父类的构造方法,然后再执行子类的构造方法的方法体
默认地调用父类的(无参)构造方法,如果父类中添加了有参的构造方法,无参构造方法就没有了。会报错!处理:给父类添加无参构造方法;
如果要调用父类的有参构造方法,让子类指定调用父类的构造方法
//父类构造方法
public Father()
{
}
public Father(int n, string nm, char s)
{
num = n;
name = nm;
sex = s;
}
public class Son : Father
{
public static int count;
public Son(): base(11, "jam",'男') //子类构造方法,用base()调用父类的方法
{
}
}
1.5 类的多态性
非多态的情况:隐藏基类方法(隐藏看类型)。
隐藏:基类子类写同名方法,子类方法名前加new(可以省略),这样子类定义的是子类自己的方法,与基类没有关系,不是继承关系。
特点:不同类型调用,不同实现。对象的类型是子类则调用子类方法;对象的类型是基类则调用基类方法(即使对象本身是子类的实例)。
class MyBase
{
public void Func()
{
Console.WriteLine("MyBase");
}
}
class MySub:MyBase
{
public void Func()
{
Console.WriteLine("MySub");
}
}
class MySub2:MyBase
{
public void Func()
{
Console.WriteLine("MySub2");
}
}
class Program
{
static void Main(string[] args)
{
MySub ms = new MySub();
ms.Func();//输出 MySub
MyBase mb = ms;
mb.Func();//输出 MyBase
mb = new MySub2();
mb.Func();//输出 MyBase
}
}
多态的特点:一种调用,多种实现。由基类或接口统一调用,由不同的子类负责不同的实现。
1.5.1继承关系实现多态
重写基类虚方法实现多态。
override重写,重写只管新(只要对象是子类的,调用子类重写的方法)
条件:继承关系、里氏转换原则,且父类和子类有同名的方法。父类方法前加virtual,子类方法前加override
特点:由赋值时所创建的实例决定调用。
class MyBase
{
public virtual void Func()
{
Console.WriteLine("MyBase");
}
}
class MySub:MyBase
{
public override void Func()
{
Console.WriteLine("MySub");
}
}
class MySub2:MyBase
{
public override void Func()
{
Console.WriteLine("MySub2");
}
}
class Program
{
static void Main(string[] args)
{
MySub ms = new MySub();
ms.Func();//输出 MySub
MyBase mb = ms;
mb.Func();//输出 MySub
mb =new MySub2();
mb.Func();//输出 MySub2
}
}
示例:
Object中的虚方法ToString:
public virtual string ToString();
子类可以重写,也可以不重写,直接继承。
Object中的虚方法Equals :
public virtual bool Equals(object obj);
Object提供的Equals方法,比较两个对象的地址是否相同,实现了同一性比较。
子类可以重写以实现相等性比较,也可以不重写,直接继承。
1.5.2抽象方法实现多态
(1) 抽象类和抽象方法
抽象类
是对一类事物或对象的抽象,统一了一类对象。
抽象类的成员
抽象成员:方法、属性、索引、事件声明
非抽象成员:如Shape类中name
抽象类的使用
抽像类就是为了实现多态的,在使用多态的地方几乎都可以使用抽象
抽象类是不能被实例化的,抽象类有构造方法(给非抽象成员赋初值)
抽象成员的使用
(1)抽象方法的使用
父类:[]abstract 返回类型 方法名() {}
子类:[]override 返回类型 方法名() {}
(2)抽象属性的使用 快捷键Shift Alt F10
(3)抽象属性
抽象属性,因为没有方法体,所以get,set中什么都没有
抽象属性是可以有:只读、只写、可读可写之分的
抽象方法
[public] abstract 返回类型 方法名( );
抽象方法必须在抽象类中,方法和类前面都有abstract。
抽象方法是为了实现多态,子类必须重写父类中的抽象方法(除非子类也是抽象类)
public override 父类提供的方法名(){}
应用场景:不知道怎么实现,如计算形状的面积;不需要实现。
(2) 抽象方法实现多态
一种调用(都是父类类型),多种实现(根据赋值时new的实例所属子类)。
实例:形状类(抽象父类),圆形、长方形两个子类,实现父类类型调用子类方法求面积
namespace AbstractClass
{
abstract class Shape
{
//属性
public string Name;
public double Pi= 3.14;
//构造方法
public Shape(string name)
{
this.Name= name;
}
//抽象父类方法
public abstract double Area();
}
class Circle : Shape
{
//属性
public double R;
//构造方法
public Circle(double r)
{
R= r;
}
//圆形子类方法
public override double Area()
{
return Pi * R * R;
}
}
class Rectangle : Shape
{
public double Length;
public double Width;
public Rectangle(double len,double wid)
{
Length= len;
Width= wid;
}
public override double Area()
{
return length *width;
}
}
1.5.3接口实现多态
对象类型都是接口,但赋值时new的实例属于不同的实现类,则调用具体实现的子类的方法。
接口的使用原则:单一功能,组合使用。
1.5.4多态的应用原则
多态实现方式:接口实现多态(对外)、抽象实现多态、父类实现多态(对内)
开发过程:定义接口 --定义抽象--父类--子类,最终使用的是子类,给用户的是接口。
开发以抽象为主,能够抽象,尽量不要使用具体。
1.6 序列化实现深拷
1.6.1 类的可序列化标记
[Serializable]
class Person
{
private Car mycar;
internal Car Mycar
{
get { return mycar; }
set { mycar = value; }
}
public Person()
{
mycar = new Car();
}
}
[Serializable]
class Car
{
int carNo;
public int CarNo
{
get { return carNo; }
set { carNo = value; }
}
string carName;
public string CarName
{
get { return carName; }
set { carName = value; }
}
}
1.6.2 类的序列化实现深拷
class Program
{
static void Main(string[] args)
{
//1 浅拷 深拷
Person p1 = new Person();
p1.Mycar = new Car() {CarNo = 123,CarName = "BMW" };
Person p2 = p1; //浅拷整个p2
Person p3 = new Person(); //深拷p3.Mycar
p3.Mycar.CarName = p1.Mycar.CarName; //浅拷p3.Mycar.CarName
// 监视结果
*p1.Mycar 0x00dd9578 Serialize.Car&
*p2.Mycar 0x00dd9578 Serialize.Car& 浅拷 整个p2指向的是p1的实例
*p3.Mycar 0x00dd959c Serialize.Car& 深拷 new Person()分配新内存空间
*p1.Mycar.CarName 0x00dd9540 string&
*p3.Mycar.CarName 0x00dd9540 string& 浅拷(字符串 引用传递)
p1.Mycar.CarNo 0x00dd9528 123
p3.Mycar.CarNo 0x00dd9528 123 深拷(int 值传递(复制传递))
//2 序列化实现深拷
Person p4 = new Person();
using (MemoryStream ms = new MemoryStream())
{
BinaryFormatter binaryFormatter = new BinaryFormatter();
binaryFormatter.Serialize(ms, p1);//序列化成二进制流
ms.Position = 0;
p4 =(Person)binaryFormatter.Deserialize(ms);// 二进制流反序列化得到新的对象
}
// 监视结果
*p1.Mycar 0x00dd7560 Serialize.Car&
*p4.Mycar 0x00ddbc60 Serialize.Car& 深拷
*p1.Mycar.CarName 0x00dd7528 string&
*p4.Mycar.CarName 0x00ddbcb4 string& 深拷
p1.Mycar.CarName "BMW" string
p4.Mycar.CarName "BMW" string
//以上两个"BMW"是存在堆中的不同字符串对象,地址为分别0x00dd7528和0x00ddbcb4
}
}
2.数组
数组是引用类型,是以class声明的,隐式派生自System.Array,继承自Object。
数组做为实参传递给方法时,实际传递是引用。被调用的方法能修改数组中的元素。
若需要阻止修改,可以创建拷贝传递,或使用AsReadOnly进行包装。
2.1数组的声明、赋值、使用
Int32[] arr;
arr= new Int32[100];
arr[3]=7;
int x=arr[9];
2.2二维、多维数组
每一个元素的类型相同,还是字符串。
String[,] arr =new String[3,5];
arr[0][0]=”abcd”;
arr[1][4]=”efgh”;
取第一个维度的某一项,则得到一维数组。
String[] sub =arr[1];
2.3数组的初始化
类似与类的对象初始化器,数组也有初始化器。
String[] names = new String[]{“A”,”123”,”def”};
由于已经初始化的具体的值,因此不用在中括号中再指定长度。
var kids = new[]{
new {Name=”Jim”,Age=17},
new {Name=”Lily”,Age=15},
new {Name=”Tom”,Age=20}
};
2.4 System.Array的属性、方法
Length
数组长度
GetValue
获取一维 System.Array 中指定位置的值。
public object GetValue(int index);
获取一维 System.Array 中指定位置的值。索引指定为 64 位整数。
public object GetValue(long index);
Clone
创建 System.Array 的浅表副本。
// 返回结果:System.Array 的浅表副本。
public object Clone();
CopyTo
将当前一维 数组的所有元素复制到指定的一维数组中(从指定的目标数组索引开始)。
// array:一维 System.Array,它是从当前 System.Array 复制的元素的目标位置。
// index:一个 32 位整数,它表示 array 中复制开始处的索引。
public void CopyTo(Array array, int index);
AsReadOnly
返回指定数组的只读包装。
// 参数array:要包装在只读 System.Collections.ObjectModel.ReadOnlyCollection<T> 包装中的从零开始的一维数组。
// 类型参数T:数组元素的类型。
public static ReadOnlyCollection<T> AsReadOnly<T>(T[] array);
Clear
将 System.Array 中的一系列元素设置为零、false 或 null,具体取决于元素类型。
// 参数:array:System.Array,需要清除其元素。
// index:要清除的一系列元素的起始索引。
// length:要清除的元素数。
public static void Clear(Array array, int index, int length);
Copy
Array.Copy执行浅拷贝,即对值类型元素创建副本拷贝,对引用类型元素还是传递引用。
// sourceArray:System.Array,它包含要复制的数据。
// destinationArray:System.Array,它接收数据。
// length:一个 32 位整数,它表示要复制的元素数目。
public static void Copy(Array sourceArray, Array destinationArray, int length);
Exist
确定指定数组包含的元素是否与指定谓词定义的条件匹配。
// array:要搜索的从零开始的一维 System.Array。
// match:System.Predicate<T>,定义要搜索的元素的条件。
// 类型参数T:数组元素的类型。
public static bool Exists<T>(T[] array, Predicate<T> match);
示例:
string[] array = { "beijing", "shanghai", "shenzhen" };
bool a = Array.Exists(array, element => element == "shanghai");//true
bool b = Array.Exists(array, element => element == "tianjin");//false
Find
搜索与指定谓词定义的条件匹配的元素,然后返回整个数组中的第一个匹配项。
public static T Find<T>(T[] array, Predicate<T> match);
IndexOf
搜索指定的对象,并返回整个一维 System.Array 中第一个匹配项的索引。如果没有搜索到指定对象,则返回该数组的下限减 1。
// array:要搜索的一维 System.Array。
// value:要在 array 中查找的对象。
[ReliabilityContract(Consistency.WillNotCorruptState, Cer.MayFail)]
public static int IndexOf(Array array, object value);
搜索指定的对象,并返回第一个匹配项的从零开始的索引,如果没有搜索到指定对象,则返回-1。
// 类型参数T:数组元素的类型。
// 返回结果:如果在整个 array 中找到 value 的匹配项,则为第一个匹配项的从零开始的索引;否则为 -1。
public static int IndexOf<T>(T[] array, T value);
ForEach
对指定数组的每个元素执行指定操作。
// array:从零开始的一维 System.Array,要对其元素执行操作。
// action:要对 array 的每个元素执行的 System.Action<T>。
// 类型参数T:数组元素的类型。
public static void ForEach<T>(T[] array, Action<T> action);
3.接口
接口是对能力和方法的抽象。(抽象类是对一类事物的抽象,接口是对能力和方法的抽象).
[public] interface 接口名
{
// 接口成员
}
接口成员
仅仅抽象能力,所以只有方法、属性、索引、事件声明
接口中方法定义
定义接口方法不用写访问修饰符,默认public
实现接口的类,实现接口中的方法时,写一个正常方法 (没有override)
接口的多继承性
(1)显式实现接口:
通过显式实现接口的方式为特定的接口重写方法,调用方法时根据变量所属的接口类型来选择相应的方法。
(2)调用接口:返回类型 接口名.方法名()显式实现的方法只能由接口调用
接口类型的对象优先调用显式实现接口的方法,缺少时,再调用普通实现的方法。
非接口类型的对象,只能调用非显式实现的方法,不能调用显式实现接口的方法。
4.委托
4.1 委托
通过委托提供了一种回调函数的机制,是类型安全的机制。
可以登记回调方法来获得多种多样的通知。
委托使用delegate关键字声明。
可用来实现调用另一个类中私有方法。
4.2 委托的声明
4.2.1 delegate
delegate我们常用到的一种声明。
Delegate至少0个参数,至多32个参数,可以无返回值,也可以指定返回值类型。
例:public delegate stringt MyDelegate(int x, int y);表示有两个参数,并返回string型。
4.2.2 Action
Action是无返回值的泛型委托。
Action 表示无参,无返回值的委托
Action<int,string> 表示有传入参数int,string无返回值的委托
Action<int,string,bool> 表示有传入参数int,string,bool无返回值的委托
Action<int,int,int,int> 表示有传入4个int型参数,无返回值的委托
Action至少0个参数,至多16个参数,无返回值。
例:
public void ActionTest<T>(Action<T> action,T t)
{
action(t);
}
4.2.3 Func
Func是有返回值的泛型委托
Func<int> 表示无参,返回值为int的委托
Func<object,string,int> 表示传入参数为object, string 返回值为int的委托
Func<object,string,int> 表示传入参数为object, string 返回值为int的委托
Func<T1,T2,,T3,int> 表示传入参数为T1,T2,,T3(泛型)返回值为int的委托
Func至少0个参数,至多16个参数,根据返回值泛型返回。必须有返回值,不可void
例:
public int FuncTest<T1,T2>(Func<T1,T2,int>func,T1 a,T2 b)
{
return func(a, b);
}
4.2.4 predicate
predicate 是返回bool型的泛型委托。
predicate<T> 表示传入参数为T 返回bool的委托
Predicate有且只有一个参数,返回值固定为bool
例:public delegate bool Predicate<T> (T obj)
4.3 委托的使用
4.3.1 使用步骤
(1)声明委托,指定回调方法返回值和签名。
其中方法签名由方法名称和一个参数列表(方法的参数的顺序和类型)组成。
声明委托是通常返回值为使用void。如果定义了返回值,那么多个订阅者的方法都会向发布者返回数值,结果就是后面一个返回的方法值将前面的返回值覆盖掉了,因此,实际上只能获得最后一个方法调用的返回值。
(2)定义一个或多个与委托返回值和参数列表相同的方法。
(3)构造委托实例(使用相同签名的方法),将方法绑定到委托实例。
(4)做为参数传递,在需要的地方对委托实例进行调用。
4.3.2 协变性和逆变性
协变性:方法返回值可以是委托的返回值的派生类(方便变量灵活接收更多种类型返回值)。
逆变性:方法获取的参数可以是委托参数类型的基类。
协变性和逆变性只用于引用类型,不能用于值类型或void的情况。
4.3.3 简化语法
简化语法1:直接使用方法名代替委托实例。
Button btn= new Button;
btn.Click =new EventHandler(btn_click);
可以简化为
btn.Click =btn_click;
简化语法2:使用lambda表达式。