3、二、c# 面向对像编程。类,结构、C# 数据类型(引用类型、值 类型、指针类型)、ref参数与out参数、方法的重载、静态类型与静态成员、继承与多态、委托与事件

一、类

定义类使用class关键字。

<access specifier> class  class_name 
{
    // member variables 成员变量
    <access specifier> <data type> variable1;
    <access specifier> <data type> variable2;
    ...
    <access specifier> <data type> variableN;
    // member methods 成员方法
    <access specifier> <return type> method1(parameter_list) 
    {
        // method body 
    }
    <access specifier> <return type> method2(parameter_list) 
    {
        // method body 
    }
    ...
    <access specifier> <return type> methodN(parameter_list) 
    {
        // method body 
    }
}

请注意:

  • 访问标识符 <access specifier> 指定了对类及其成员的访问规则。如果没有指定,则使用默认的访问标识符。类的默认访问标识符是 internal,成员的默认访问标识符是 private
  • 数据类型 <data type> 指定了变量的类型,返回类型 <return type> 指定了返回的方法返回的数据类型。
  • 如果要访问类的成员,你要使用点(.)运算符。
  • 点运算符链接了对象的名称和成员的名称。

类的内部定义不同的成员,这些成员包括

1)字段:内部定义的一种变量 

2)属性:用于描述对象的特征

C#字段和属性

通常属性带有get和set访问器,get访问器用来获取属性的值 ,set访问器则用来设置属性的值 。如果 希望让属性只读,即只能获取其值而不允许对其进行赋值,直接去掉set访问器即可,仅保留get访问器。

如果属性不需要特殊验证可以简化写成

publie string Name{get;set;}

不简化

publie string Name
{
    get{return this.name;}
    set{this.name=value;}
}

  

设置默认值 

publie string Name{get;set;}=700

只读属性

1)publie string Name{get;}

2)//还可以使用类似Lambda表达式来声明

publie int MaxTaskNum=>500;
当使用”=>“操作符来声明只读属性时,不需要写get语句 ,也不需要也return关键字,”=>“后面直接 写上返回值 即可。

字段是真正存储数据值 的变量,而属性只是一个对外公开的窗口,数据通过属性来传递。当获取属性的值 时,通过retrun关键字直接把字段中存放的值 返回。当要设置属性的值 时,调用set访问器把外部传进来的数据存放到value中,再以value作为纽带把数据赋值给字段。

 3)方法:对象的行为

带有参数的方法

int Add(int a ,int b )
{
    return a+b;
}

不按顺序参数调用方法:命名参数

Add(b:5,a:6)

定义可选参数,就是在调用方法时,可以忽略的参数,就是可选 参数要赋值默认值。如下P1是可选参数,P2由于已赋了默认值,就成了可选参数

void DoWor(string p1,string p2="abc")
{
}

这要调用

DoWork("123");

因为P2是可选参数,这样是对的,但如下就是不对的,p1是可选参数,如果仍然采取上面的调用方法,就会提示错误 ,由此可见可选 参数要放在参数列表最后一列,因为放在前面 无法一一对应。

void DoWor(string p1="abc",string p2)
{
}

也可以Lamebda表达式的形式来声明

public string PickName()=>"jack";//PickName方法没有参数,返回一个字符串实例 。


 public string PickName(int a ,int b)=>a+b;//带参数的方法

写一个例子

用属性来封装name字段,是为了安全性。不让直接该变量字段,也可以进行判断一些规则,不让值 随便修改
class Program
{
    static void Main(string[] args)
    {
        Person ps =new Person();//类在使用的时候,必须使用new实例化。
        ps.Name="小红";
        ps.Age=20;
     ps.Play();//调用方法
    console.Read();//让代码执行到这边可以停 } }
public class person {
  // 字段
string name;//字段默认为private,可以不写private,代表只能在类里面使用 public string Name;//公共的字段,可以在类外面使用该字段
  //属性
public string Name { get {return name;}//返回name的值 set { name=value;}//给name值 赋值 } int age public int Age { get {return age;} set { if (value<0) { throw new ArgumentException(“属性值不能小于0”);//抛出异常 } age=value; } }

//方法
  public void play()
  {
    Console.WriteLine("一起去玩");
  } }

 私有方法 private,只能在当前类的调用

4)事件:在特定条件下触发的行为

5)构造器,构造函数,构造方法。它是一种特殊的方法,在创建对象实例时调用,用来进行一些初始化工作。

  在类被实例化的时候 (创建类的对象实例时)调用,它也是类的成员,具有以下特点:

  • 构造函数的名称必须与类名相同
  • 构造函数没有返回值 
  • 默认构造函数没有参数,但也可以定义参数。

即使开发人员不为类编写构造函数,它默认就有一个不带参数的构造函数,

如果 希望在类型初始化的过程中加入自己的处理的代码,或者使用带参数的构造函数,就有必要息来定义构造函数

定义一个有参数,有自己编写代码的程序,如下

class Toy
{
    Public Toy(string name)
    {
        Console.WriteLine("正在创建{0}玩具",name);
    }
}  
我们为Toy类定义了一个带字符串类型参数的构造函数。但是,如果创建Toy类的实例时无法再使用无参数的默认构造函数了。如Toy myToy=new Toy()
会报不包含采用“0”个参数的构造函数。
这代表默认的构造函数会被覆盖,如果要用无参数的构造函数,就必须把无参构造函数写上。  

class Toy
{
    Public Toy()    {}
    Public Toy(string name)
    {
        Console.WriteLine("正在创建{0}玩具",name);
    }
} 

使用带参数的构造函数调用来创建类的实例,需要传递数据给对应的参数
Toy tbetoy=new Toy("小汽车");

类是通过构造函数来创建,通过调用析构函数来销毁。析构函数通过“~”开头,没有返回值,后紧跟类名,无参数。

只能在类只使用,只有一个析构函数,不能在代码中调用 析构函数,它是在资源被销毁时由运行时库调用。

class Toy
{
//无参数的构造函数
    Public Toy() {}
//析构函数
~Toy(){}
   
} 

 一般进行调试点击“F5”进行调试,应用程序退出。查看“输出”,如下

构造函数被调用。
析构函数被调用。
程序“[6040] MyApp.exe”已退出,返回值为 0 (0x0)。
程序“[6040] MyApp.exe: 程序跟踪”已退出,返回值为 0 (0x0)。

二、结构

结构与类比较相似,有字段、属性、方法等成员,但与类相比,有许多 限制 。因此,如果要定义属性、方法、事件等成员,应当优先考虑类,结构一般用来定义一些比较简单的类型,比如 包含几个公共字段的结构就比较合理,当然 最大的区别是,类是引用类型,结构是值 类型。

限制:

1)只能声明带参数的构造函数,不能声明默认构造函数

2)不能进行继承和派生,但可以实现 接口。结构默认是System.ValueType派生,类是System.Object派生。所以类是引用类型,结构是值 类型。

3)结构在实例化是可以忽略new运算符,而类不可以

struct Pet

{

  public string Name;

  publie int Age;

}

实例化

//声明变量,可不需要new来实例化
Pet pet;
//给Pet实例的成员赋值
Pet.Name="Jack";
Pet.Age=3;




//声明变量,当然 也可用new来实例化
Pet pet2=new Pet;
//给Pet实例的成员赋值
Pet.Name="Tom";
Pet.Age=2;

4)在结构中声明 的字段,不能进行初始化。如

struct Pet
{
    public string Name="";
    public int Age=1;
}
就会发生错误“结构中不能有实例字段初始值 设定项”

5)没有使用new关键字来实例化结构的前提,只能调用 字段,不能调用属性、方法等成员。如下

struct Book
{
    public string Name{get;set;}  
    public string ISBN{get;set}
    public void Read()
    {
        console.WriteLine("this book is reading.....");
    }
}    


Book theBook;
theBook.Name="书名";
theBook.ISBM="XX-XX-X-XX";
theBood.Read();
这时报错“使用未赋值的局部变量theBook”

改为
Book theBook= new Book;
theBook.Name="书名";
theBook.ISBM="XX-XX-X-XX";
theBood.Read();
这时报错“使用未赋值的局部变量theBook”

 

三、C#数据类型

在 C# 中,变量分为以下几种类型:参考来自己

  • 值类型(Value types)
  • 引用类型(Reference types)
  • 指针类型(Pointer types)

2、引用类型与值 类型

类是引用类型,结构是值 类型

引用类型例子1

using System;

namespace MyApp
{
    #region 类型定义
    public class Person
    {
        public string Name { get; set; }
        public int Age { get; set; }
    }
    #endregion

    class Program
    {
        static void Main(string[] args)
        {
            // 创建两个Person实例
            Person ps1 = new Person { Name = "Time", Age = 22 };
            // 把ps1赋值给ps2
            Person ps2 = ps1;
            // 输出ps2的属性值
            Console.WriteLine("ps1被修改前:
ps2.Name : {0}
ps2.Age : {1}", ps2.Name, ps2.Age);
            // 修改ps1的属性值
            ps1.Name = "Jack";
            ps1.Age = 28;
            // 再次输出ps2的属性值
            Console.WriteLine("
ps1被修改后:
ps2.Name : {0}
ps2.Age : {1}", ps2.Name, ps2.Age);
            Console.Read();
        }
    }
}

结果为

 引用类型例子2、

 

结果为

结构类型例子

using System;

namespace MyApp
{
    struct Person
    {
        public string Name { get; set; }
        public int Age { get; set; }
    }

    class Program
    {
        static void Main(string[] args)
        {
            // 实例化Person结构
            Person ps1 = new Person { Name = "Bob", Age = 21 };
            // 将ps1赋值给ps2
            Person ps2 = ps1;
            // 输出ps2的各个属性的值
            Console.WriteLine("ps1被修改前:
ps2.Name : {0}
ps2.Age : {1}", ps2.Name, ps2.Age);
            // 修改ps1的属性值
            ps1.Name = "Tom";
            ps1.Age = 33;
            // 再次输出ps2的各个属性值
            Console.WriteLine("
ps1被修改后:
ps2.Name : {0}
ps2.Age : {1}", ps2.Name, ps2.Age);
            Console.Read();
        }
    }
}

结果为

 结构例子2

 

结果

  

1)因为对于 引用类型,实例化是在托管中动态分配内存的,变量只是保存该实例的地址,

2)引用类型都是public class Object派生。是所有类型的基类,所是定义的时候 是class

3)结构是值 类型,它所创建的实例 不在拖管堆 中分配内存,而是直接 存储在变量中。

 只要从ValueType派生出来的都是值 类型。如下Byte类型,它的基类就是ValueType

4)ValueType的基类是object

5)引用类型与快捷方式很像,就相当于某个文件创建快捷方式 ,如桌面快捷方式,不管创建多少个快捷方式 都是指向同一个文件,当这个文件被修改补删除 ,会影响的指向该文件的所有快捷方式。

6)值类型和复制文件类似,例如把A从C盘复制到D盘,然后打开 D盘下的A文件进行修改和保存,但是,存放在C盘的A文件不会受影响。

四、ref参数与out参数

 1、类例子

using System;

namespace MyApp
{
    public class Person
    {
        public string Name { get; set; }
        public int Age { get; set; }
    }
    class Program
    {
        static void Main(string[] args)
        {
            Person ps = new Person { Name = "John", Age = 25 };
            // 调用TestMethod1前
            Console.WriteLine("在调用TestMethod1方法前,ps.Name : {0}, ps.Age : {1}", ps.Name, ps.Age);
            TestMethod1(ps);
            // 调用TestMethod1方法后,再次输出ps的属性值
            Console.WriteLine("在调用TestMethod1方法后,ps.Name : {0}, ps.Age : {1}", ps.Name, ps.Age);

            Console.Write("

");
            Person ps2 = new Person();
            ps2.Name = "Xin";
            ps2.Age = 35;
            Console.WriteLine("在调用TestMethod2前,
ps2.Name : {0}
ps2.Age : {1}", ps2.Name, ps2.Age);
            // 调用TestMethod2方法
            TestMethod2(ps2);
            // 再次输出ps2的属性值
            Console.WriteLine("在调用TestMethod2后:
ps2.Name : {0}
ps2.Age : {1}", ps2.Name, ps2.Age);

            Console.Write("

");
            Person ps3 = new Person();
            ps3.Name = "Li";
            ps3.Age = 10;
            Console.WriteLine("在调用TestMethod3方法前:
ps3.Name : {0}
ps3.Age : {1}", ps3.Name, ps3.Age);
            // 调用TestMethod3方法
            TestMethod3(ref ps3);
            // 调用TestMethod3方法后再次输出ps3的属性值
            Console.WriteLine("在调用TestMethod3方法后:
ps3.Name : {0}
ps3.Age : {1}", ps3.Name, ps3.Age);

            Console.Write("

");
            Person ps4;
            // 调用TestMethod4方法
            // 变量在传递给out参数前可以不进行初始化
            TestMethod4(out ps4);
            // 调用TestMethod4方法后输出ps4的属性值
            if (ps4 != null)
            {
                Console.WriteLine("在调用TestMethod4方法后:
ps4.Name : {0}
ps4.Age : {1}", ps4.Name, ps4.Age);
            }

            Console.Read();
        }

        /// <summary>
        /// 参数传递的是引用,因此可以把更改反映到外部变量上
        /// </summary>
        static void TestMethod1(Person p)
        {
            p.Name = "Loo";
            p.Age = 33;
        }

        /// <summary>
        /// 对于引用类型来说,如果在方法中创建了新实例,将不影响外部变量的引用更改
        /// </summary>
        static void TestMethod2(Person p)
        {
            p = new Person { Name = "Chen", Age = 29 };
        }

        static void TestMethod3(ref Person p)
        {
            p = new Person();
            p.Name = "Lee";
            p.Age = 12;
        }

        static void TestMethod4(out Person p)
        {
            p = new Person();
            p.Name = "Huang";
            p.Age = 27;
        }
    }
}

结果

值类型变量之间的赋值是把自身进行一次复制。因此值类型的变量传递给方法的参数后,就把自身复制到参数中,而在方法中对参数 的修改是不会影响到方法外部的变量,因为它们是相互独立的,所以,为了让值类型的变量也能按引用传递,以达到修改外部变量的目的,可以在方法的相应参数 上加上ref,out关键字。 

 引用参数(ref)传递前必须初始化

out在参数可以在未初始化的情况下传递。

不管使用ref,out,关键字来修饰参数,在调用方法时也要带上相应的关键字

2、结构例子

using System;

namespace MyApp
{
    public struct Dress
    {
        public string Color;
        public double Size;
        // 构造函数
        public Dress(string color, double size)
        {
            Color = color;
            Size = size;
        }
    }
    class Program
    {
        static void F1(Dress d)
        {
            d.Color = "红色";
            d.Size = 17.213d;
        }

        static void F2(ref Dress d)
        {
            d.Color = "紫色";
            d.Size = 19.5d;
        }

        static void F3(out Dress d)
        {
            d = new Dress("浅灰", 18.37d);
        }
        static void Main(string[] args)
        {
            Dress d1 = new Dress("白色", 3.125d);
            Console.WriteLine("调用F1方法前:
d1.Color:{0}
d1.Size:{1}", d1.Color, d1.Size);
            // 调用F1方法
            F1(d1);
            // 调用F1方法后输出d1的成员的值
            Console.WriteLine("调用F1方法后:
d1.Color:{0}
d1.Size:{1}", d1.Color, d1.Size);

            /*---------------------------------------------*/
            Console.Write("

");
            Dress d2 = new Dress("绿色", 8.5777d);
            Console.WriteLine("调用F2方法前:
d2.Color:{0}
d2.Size:{1}", d2.Color, d2.Size);
            // 调用F2方法
            F2(ref d2);
            // 调用F2方法后再次输出d2的成员的值
            Console.WriteLine("调用F2方法后:
d2.Color:{0}
d2.Size:{1}", d2.Color, d2.Size);

            /*---------------------------------------------*/
            Console.Write("

");
            Dress d3;
            // 调用F3方法
            F3(out d3);
            // 调用F3方法后输出d3的成员的值
            Console.WriteLine("调用F3方法后:
d3.Color:{0}
d3.Size:{1}", d3.Color, d3.Size);

            Console.Read();
        }
    }
}

结果

五、方法重载

方法名相同,但是参数不同,参数的个数不同或者类型不同,满足一个就可以(和返回值无关,和参数的类型和个数有关)

1)具有不同类型的返回值且参数有差异的同名 方法可以重载,如果 参数列表相同,只是返回值 不同的两个同名 方法是不可构成重载的

public string DoWork(int a);
public int DoWork();

2)参数列表的类型及顺序不同,可以构成重载

void OnTest(String a){}
void OnTest(float b ,double a){}

参数类型和顺序 相同,不能构成重载,编译器只关注 参数 个数,类型和顺序,而参数 的名字并不编译

不能构成重载
void onTest(float a ,double b){}

3)带ref 或out修饰符的参数,如果 一个方法的参数 带有ref关键字的参数,而另一个同名方法不带有ref关键字修饰的参数,可以构成重载

void Computer(ref short v){}
void Computer(short v){}

void Computer(short v){}
void Computer(out short v){v=2}

由于编译器不区分ref和out参数,所以如果一个方法使用ref参数,而另一个方法使用out参数,而且 参数的个数,类型和顺序相同,不能构成重载

void Computer(ref short v){}//编译错误 
void Computer(out short v){v=2}//编译错误 

4)构造函数也支持重载

public Class Goods
{
public Goods(){};
public Goods(string goodsName){};
}

第一个是默认构造函数,第二个构造 函数是一个重载,带有一个string类型的参数 。

例子方法参考

六、静态类型与静态成员

使用static关键字即表示 声明静态类型、静态成员。它们不是基于实例 的,所以在使用前不需要实例化,它们是基于类型本身的,直接 就可可以调用静态成员。

public class DataOperator
{
  public static void AddNew(){}
  public static void UpdateNow(){}    
}

在调用时,无需声明 DataOperator类型的变量,也不需要进行实例化,而是直接 调用它的公共方法即可

DataOperator.AddNew();
DataOperator.UpdateNow();

直接写上类型的名字,然后用点号(成员运算符)来访问其公共成员即可。static关键字不仅能修饰方法,也可以修饰字段、属性、事件等成员。

public Class Car
{
    public static string CarName{get ; set;}
    public static double Speed {get; set;}
}

同理也不用实例化,不用声明 变量,直接访问

Car.CarName="高档汽车"
Car.Speed=170d;

如果将static关键字用于修饰类型,就表明整个类型都是静态,在这种条件下,类型只能定义静态成员 。

public static class Test
{
    public void SayHello(){}; //错误 
    public string Message{get;set;}//错误
}

Test已声明为静态类型,因此 它只能定义静态成员,以上代码中SayHello方法,Message属性是不能通过编译的。

public static class Test
{
    public static void SayHello(){}; //正确 
    public static string Message{get;set;}//正确
}

七、继承与多态

1)、可访问性

1. public:访问不受限制; 
2. internal:访问仅限于所属程序集(当前项目中使用); 
3. protected:访问仅限于当前类和其子类; 
4. internal protected:访问仅限于当前程序集或其子类(子类可以不属于当前程序集); 
5. private:访问仅限于包含该成员的类型,只能在当前类中访问

例子

using System;

namespace MyApp
{
    class Program
    {
        static void Main(string[] args)
        {
            A va = new A();
            va.Value = 5;  // 错误,因为A类中value被定义成private,只能在类中内部使用
            B vb = new B();
            vb.Value = 100;  // 正确,B类中value被定义public公共属性没有访问限制

            MyLib.M m = new MyLib.M();  // 错误,MyLib中,M被定义为internal,只能在它所在的程序集中使用,在其他程序集中无法访问。
        }
    }


    public class A
    {
        private int Value { get; set; }
    }

    public class B
    {
        public int Value { get; set; }
    }
}

新建一个项目,类库MyLib,引用到MyApp项目中

using System;

namespace MyLib
{
    internal class M
    {
        public void MakeMessage()
        {
            // 方法内容
        }
    }
}

由于命名空间下类的默认访问方式为internal,所以在命名空间下直接 定义类可以省略internal

2)继承

继承是在类之间建立一种相交的关系,使得新定义的派生类的实例可以继承已有的基类的特征并且还可以添加新的功能。以前对继承的理解仅仅限于定义。

被继承的类叫基类/父类,继承其他类叫派生类/子类。

1.C#继承的特点

(1) 派生类是对基类的扩展,派生类可以添加新的成员,但不能移除已经继承的成员的定义。

(2)继承是可以传递的。如果C从B中派生,B又从A中派生,那么C不仅继承了B中声明的成员,同样也继承了A中声明的成员。

(3)构造函数和析构函数不能被继承,除此之外其他成员能被继承。基类中成员的访问方式只能决定派生类能否访问它们。

(4)派生类如果定义了与继承而来的成员同名的新成员,那么就可以覆盖已继承的成员,但这并不是删除了这些成员,只是不能再访问这些成员。

(5)类可以定义虚方法、虚属性及虚索引指示器,它的派生类能够重载这些成员,从而使类可以展示出多态性。

(6)派生类只能从一个类中继承,可以通过接口来实现多重继承。

上面参考 来源,更多例子参考

using System;

namespace MyApp
{
    public class Person
    {
        /// <summary>
        /// 构造函数
        /// </summary>
        public Person()
        {
            System.Diagnostics.Debug.WriteLine("Person类的构造函数被调用。");
        }
        /// <summary>
        /// 析构函数
        /// </summary>
        ~Person()
        {
            System.Diagnostics.Debug.WriteLine("Person类的析构函数被调用。");
        }

        /// <summary>
        /// 姓名
        /// </summary>
        public string Name { get; set; }
        /// <summary>
        /// 住址
        /// </summary>
        public string Address { get; set; }
        /// <summary>
        /// 年龄
        /// </summary>
        public int Age { get; set; }
    }

    public class Student : Person
    {
        /// <summary>
        /// 构造函数
        /// </summary>
        public Student()
        {
            System.Diagnostics.Debug.WriteLine("Student类的构造函数被调用。");
        }
        /// <summary>
        /// 析构函数
        /// </summary>
        ~Student()
        {
            System.Diagnostics.Debug.WriteLine("Student类的析构函数被调用。");
        }

        /// <summary>
        /// 课程
        /// </summary>
        public string Course { get; set; }
    }
    class Program
    {
        static void Main(string[] args)
        {
            Student st = new Student();
            st.Name = "Tom";
            st.Age = 21;
            st.Address = "test";
            st.Course = "C++编程入门";
            // 输出到屏幕
            Console.WriteLine("学员信息:
姓名:{0}
住址:{1}
年龄:{2}
课程:{3}",
                              st.Name,
                              st.Address,
                              st.Age,
                              st.Course);
            Console.Read();
        }
    }
}

运行结果

由下图可知,实例类时,先调用基类构造函数,再调用派生类的构造函数,在实例化释放时,调用顺序正好相反。

  注意可访问性要一致:派生类的可访问性不应该比基类高。派生类的可访问性可经比基类低。

隐藏基类成员

1)派生类无法访问基类的私有方法。

2)当派生类和基类有同样的方法,调用时还是只调用派生类的方法,这里涉及到隐藏基类成员的问题。两个相同方法会将基类的那个方法隐藏。但会收到警告。

3)如果确实 要隐藏基类的成员,可以明确 的告诉编译器,方法是在方法声明上加一个new关键字,这个不是创建实例,而是隐藏类的成员。

 覆写基类成员

1)用virtual,override关键字,方法是将基类中需要被覆写的成员加上virtual关健字使其“虚化”,接着把派生类的覆写的成员加上override关键字。

2)运行时库会根据变量所引用的实例 类型来判断应该调用谁。

3)override不仅能覆写基类成员,还能实现 对基类成员 的扩展,也可以使用base关键字来调用基类的成员。base关键字与this关键字是相对的。this关键字引用的是当前类的实例 ,而base关键字引用的是基类的实例。

 

using System;

namespace MyApp
{
    #region 第一组类型
    public class D
    {
        public virtual void Work()
        {
            Console.WriteLine("调用了D类的Work方法。");
        }
    }
    public class E : D
    {
        public override void Work()
        {
            Console.WriteLine("调用了E类的Work方法。");
        }
    }
    #endregion

    #region 第二组类型
    public class X
    {
        public virtual void Output()
        {
            Console.WriteLine("调用了X类的Output方法。");
        }
    }
    public class Y : X
    {
        public override void Output()
        {
            base.Output();  // 调用基类成员
            Console.WriteLine("调用了Y类的Output方法。");
        }
    }
    #endregion

    class Program
    {
        static void Main(string[] args)
        {
            // 分别声明两个D类型的变量
            D d, e;
            // 变量d引用D类的实例
            d = new D();
            d.Work();  // 调用D类的Work方法
            // 变量e引用E类的实例
            e = new E();
            e.Work();  // 调用E类的Work方法

            /*------------------------------------------------*/
            Console.Write("

");

            // 变量y声明为X类型,但引用了Y类的实例
            X y = new Y();
            y.Output();

            Console.ReadKey();           
            
        }

        public void Do()
        {
            this.GetType();   
         }

    }
}

 

如何阻止类被继承

1)加上sealed关键字,用sealed关健字声明的类也叫密封类。   

2)如果只是想阻止基类中的虚成员被覆写,而并不打算阻止整个类被继承,那么方法与密封类相同,在定义虚成员时加上sealed关键字即可。

八、抽象类

1、抽象类不能实例化

2、使用abstract关键字表示类或成员是抽象的

3、抽象方法因为不提供具体的实现,所以没有方法体(一对大括号所包裹的内容,语句以分号结束)

4、抽象类仅对成员进行声明,但不提供 实现代码,就等于设计 了“空架子”,描绘一副大致的蓝图。

5、具体如何 实现取决于派生类

6、实现抽象类的抽象成员也是使用override关键字,与成员覆写相似,可以看作是覆写基类的成员。

7、在非抽象类中不能声明抽象成员,在抽象类中定义非抽象成员是可以的

using System;

namespace MyApp
{
    public abstract class Ball
    {
        /// <summary>
        /// 获取球类的名称
        /// </summary>
        public abstract string CateName { get; }
        /// <summary>
        /// 打球
        /// </summary>
        public abstract void Play();

        private decimal _r;
        /// <summary>
        /// 球的半径
        /// </summary>
        public decimal Radius
        {
            get { return _r; }
            set
            {
                if (value <= 0)
                {
                    _r = 1;
                }
                if (value > 15)
                {
                    _r = 15;
                }
            }
        }
    }

    public class FootBall : Ball
    {
        public override string CateName
        {
            get { return "足球"; }
        }

        public override void Play()
        {
            Console.WriteLine("正在踢足球……");
        }
    }

    public class BasketBall : Ball
    {
        public override string CateName
        {
            get { return "篮球"; }
        }

        public override void Play()
        {
            Console.WriteLine("正在打篮球……");
        }
    }

//////////////////////////////////////////////////////////////////////////////////////

    class Program
    {
        static void Main(string[] args)
        {
            FootBall football = new FootBall();
            football.Radius = 2;
            BasketBall basketball = new BasketBall();
            basketball.Radius = 10;
            // 调用PlayBall方法
            PlayBall(football);
            PlayBall(basketball);

            Console.Read();
        }

        static void PlayBall(Ball ball)//声明变量时可以用抽像类的类型,赋值时/实例化时必须有实现类/派生类
        {
            Console.WriteLine("
球类:{0}", ball.CateName);
            ball.Play();
        }
    }
}

 

 

 九、接口

1)不能被实例化

2)自身不提供实现代码

3)抽象类与接口的差别:

  (1)抽象类的成员定义是带有可访问性的关键字(如public),而接口是不带可访问性关健字的,因为接口中所声明的成员是公共,所以没有必要加访问个修饰符

  (2)抽象类可以包含非抽象成员,也包括构造函数,而接口不能包含具备实现代码的成员 ,也不能包含构造函数

4)使用interface关键字

5)命名时,一般前面 加个“I”

6)默认为internal,即只能在同一程序集中访问,如果 要让接口对外公开,应当加public.

7)一个子类不能继承多基类,一个类可以实现 多个接口,实现 多继承的效果,多个接口用英语 逗号分隔。

8)在接口中成员都是公共 的,无论是结构还是类,必须以公共成员来实现 接口的成员,而且 必须实现 接口的所有成员

9)接口也可以继承接口

10)显式实现 接口

  (1)显式接口就是在类中实现的接口成员加上接口的名字

  (2)显式实现接口的成员,在名字前面加上所属接口的名字以及一个成员算符(.)

  (3)显式实现接口的类无法通过类的实例 来调用成员,只能通过对应的接口来调用。

using System;

namespace MyApp
{
    public interface ITest1
    {
        void Run();
    }
    public interface ITest2
    {
        void Run();
    }

    // 显式实现接口
    public class Test : ITest1, ITest2
    {
        void ITest1.Run()
        {
            Console.WriteLine("调用ITest1.Run方法。");
        }
        void ITest2.Run()
        {
            Console.WriteLine("调用ITest2.Run方法。");
        }
    }





    class Program
    {
        static void Main(string[] args)
        {
            Test t = new Test();
            // 调用ITest1.Run方法
            ((ITest1)t).Run();
            // 调用ITest2.Run方法
            ((ITest2)t).Run();
            Console.Read();
        }
    }
}

十一、委托与事件

1、要使用delegate关键字,以告诉编译器这是委托类型

2、与委托匹配,返回类型相同,参数类型和个数相同

3、使用new 关键字来产生委托的新实例

4、委托之间可以进行相加减运算,但这与数学中的加减运算不同,委托的加运算可以是增加所关联的方法,而减法则是从委托所关联的方法列表中移除指定 的方法。

using System;

namespace MyApp
{
    class Program
    {
        static void Main(string[] args)
        {
            // 定义三个委托变量
            MyDelegate d1, d2, d3;
            // d1关联TestMethod1方法
            d1 = TestMethod1;
            // d2关联TestMethod2方法
            d2 = TestMethod2;
            // d3关联TestMethod3方法
            d3 = TestMethod3;
            // 分别调用三个委托实例
            Console.WriteLine("分别调用三个委托实例,输出结果如下:");
            d1("d1");
            d2("d2");
            d3("d3");
/*----------------------------------------------------------------------*/
            // 先与TestMethod1方法关联
            MyDelegate d4 = TestMethod1;
            // 随后再与TestMethod2和TestMethod3方法关联
            d4 += TestMethod2;
            d4 += TestMethod3;
            // 调用d4
            Console.WriteLine("
调用d4可同时调用三个方法,结果如下:");
            d4("d4");
/*-----------------------------------------------------------------------*/
            // 从d4中关联的方法列表中减去TestMethod2方法
            d4 -= TestMethod2;
            // 再次调用d4
            Console.WriteLine("
移除与TestMethod2方法关联后:");
            d4("d4");
            Console.Read();
        }

        #region 定义委托
        public delegate void MyDelegate(string s);
        #endregion

        #region 方法定义
        static void TestMethod1(string str)
        {
            Console.WriteLine("这是方法一。参数:{0}", str);
        }
        static void TestMethod2(string str)
        {
            Console.WriteLine("这是方法二。参数:{0}", str);
        }
        static void TestMethod3(string str)
        {
            Console.WriteLine("这是方法三。参数:{0}", str);
        }
        #endregion
    }
}

 结果 

 5、委托可以让方法作为参数传递给其他方法。

using System;

namespace MyApp
{
    public delegate void MyDelegate();
    class Program
    {
        static void Main(string[] args)
        {
            MyDelegate de = M1;
            Test(de);
            // 执行Test方法后重新调用委托
            de();
            Console.Read();
        }

        static void Test(MyDelegate d)
        {
            // 调用委托
            if (d != null)
            {
                d();
            }
            // 改为与M2方法关联
            d = M2;
        }

        static void M1() { Console.WriteLine("方法一"); }
        static void M2() { Console.WriteLine("方法二"); }
    }
}

结果

原文地址:https://www.cnblogs.com/michellexiaoqi/p/9898471.html