Web开发之C#:(6)C#引用类型

作者:陆金龙    发表时间:2016-07-21 00:18   


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表达式。