不行硅步无以至千里,学习虽苦,但终究会苦尽甘来,无需羡慕他人,无需成为他人,无需让自己变的所谓“更好”,好好做自己,更好的做自己就好!

        凡所有相,皆是虚妄,若见诸相非相,便是如来!

1. 抽象类与抽象方法

        抽象类和接口都包含可以由派生类继承的成员,它们都不能直接实例化,但是可以声明他们的变量,使用多态性把继承这两种类型的对象指定给他们的变量,通过这些变量来使用这些类型的成员【但不能直接访问派生类中的其他成员】,抽象类和接口的区别有以下几点:

  • 抽象类的派生类只能继承一个基类,但是可以继承任意多个接口
  • 抽象类中可以定义成员的实现,但接口中不可以;
  • 抽象类中可以包含字段、构造函数、析构函数、静态成员或常量等,接口中不可以;
  • 抽象类成员可以是私有的(非抽象)、受保护的、内部的或受保护的内部成员,接口中的成员必须是公共的。

说明:抽象类和接口这两种类型用于完全不同的目的,抽象类主要用作对象系列的基类,共享某些主要特称,例如共同的目的和接口。接口则主要用于类,这些类在基础水平上有所不同,但仍可以完成某些相同的任务。

1.1 抽象类

        类的继承树中,越是在上方的类越抽象,在多态机制中,并不需要将弗雷初始化对象,我们需要的只是子类对象,C#中声明抽象类的的关键字是abstract,语法格式如下

访问修饰符 abstract class 基类:基类或接口
{
    //类成员
}

        使用abstract定义的类称为抽象类,使用这个关键字定义的方法称为抽象方法,抽象方法必须存在于抽象类中,在子类继承后,由子类实现

1.2 抽象方法

        注意两点:

  • 抽象方法必须声明在抽象类中
  • 声明抽象方法是,不能使用virtual(虚拟)、static(静态)、private(私有)

        当从轴向类派生一个非抽象类是,需要在非抽象类中重写抽象方法,以提供具体的实现,重写抽象方法使用override关键字

//声明一个抽象类,该抽象类中声明了一个抽象方法
public abstract class myClass
{
    public abstract void method();    //抽象方法
}

2. 接口

        由于C#不支持多重继承,但是客观世界出现多重继承的情况又比较多,因此提出了接口的概念,因此接口是抽象类的延申,通过接口可以实现多重继承的功能,接口具有如下特征:

  • 接口类似于抽象基类,继承接口的任何非抽象类都必须实现接口的所有成员;
  • 不能直接实例化接口
  • 接口可以包含事件、索引器、方法和属性,但不能包含字段;
  • 接口不包含方法的实现
  • 类和接口可以从多个接口继承
  • 接口自身可以从多个接口继承

        单继承实例

 //修饰符 interface 接口名称:继承的接口列表
 //{
 //    接口内容;
 //}
 //示例:声明一个接口,包含编号和姓名两个属性,还包含了一个自定义方法ShowInfo()
 //接口定义
 interface ImInterface
 {
     string ID
     {
         get;
         set;
     }
     string Name
     {
         get;
         set;
     }
     void ShowInfo();
 }
 //接口的实现与继承
 internal class Program:ImInterface            //从接口继承
 {
     string id = "";
     string name = "";
     public string ID
 {
         get
         {
             return id;
         }
         set
         {
             id = value;
         }
     }
     public string Name
     {
         get
         {
             return name;
         }
         set
         {
             name = value;
         }
     }
     public void ShowInfo()
     {
         Console.WriteLine("编号\t 姓名");
         Console.WriteLine(ID + "\t" + Name);
     }
     static void Main(string[] args)
     {
         Program program = new Program();        //实例化Program类对象
         ImInterface imInterface = program;      //使用派生类对象实例化接口ImInterface
         imInterface.ID = "01";                  //为派生类中的ID属性赋值
         imInterface.Name = "姓名";              //为派生类中Name赋值
         imInterface.ShowInfo();                 //调用派生类中的方法显示定义的属性

     }
 }

        多重继承实例

interface IPeople
{
    string Name { get; set; }
    string Sex { get; set; }
}
interface ITeacher : IPeople        //继承公共接口
{
    void teach();                   //教学方法
}
interface IStudent:IPeople          //继承公共接口
{
    void study();                   //学习方法
}
internal class Program:IPeople, ITeacher, IStudent //多重继承,各接口用逗号隔开
{
    string name = "";
    string sex = "";
    public string Name {  get { return name; } set { name = value; } }
    public string Sex { get { return sex; } set { sex = value; } }
    public void teach()
    {
        Console.WriteLine(Name + " " + Sex + " 教师");    //教学方法
    }
    public void study()
    {
        Console.WriteLine(Name+" "+Sex+" 学生");          //学习方法
    }
    static void Main(string[] args)
    {
        Program program = new Program();    //实例化类对象
        ITeacher iteacher = program;        //使用派生类对象实例化接口iTeacher
        iteacher.Name = "张三";
        iteacher.Sex = "男";
        iteacher.teach();
        IStudent istudent = program;        //使用派生类对象实例化接口iTeacher
        istudent.study();                   //此时调用Name、Sex是Program的Name、Sex即张三、男
        istudent.Name = "李四";
        istudent.Sex = "女";
        istudent.study();
    }
}

        多重继承中,如果类实现了两个相同签名的成员或接口,可以显示的实现接口成员,避免接口实现不正确的情况,还是多重继承的例子,ITeacher、IStudent,定义了相同的成员today(),在派生类中进行显示的实现,需要注意的点:

  • 显示接口成员实现中不能包含访问修饰符、abstract、virtual、override或static修饰符;
  • 显示接口成员属于【接口】成员,因此不能使用类对象直接访问。
interface IPeople
{
    string Name { get; set; }
    string Sex { get; set; }
}
interface ITeacher : IPeople        //继承公共接口
{
    void teach();                   //教学方法
    void today();
}
interface IStudent:IPeople          //继承公共接口
{
    void study();                   //学习方法
    void today();
}
internal class Program:IPeople, ITeacher, IStudent
{
    string name = "";
    string sex = "";
    public string Name {  get { return name; } set { name = value; } }
    public string Sex { get { return sex; } set { sex = value; } }
    public void teach()
    {
        Console.WriteLine(Name + " " + Sex + " 教师");    //教学方法
    }
    public void study()
    {
        Console.WriteLine(Name+" "+Sex+" 学生");          //学习方法
    }
    void ITeacher.today()                                 //显示接口成员实现
    {
        Console.WriteLine("我在上课");
    }
    void IStudent.today()                                 //显示接口成员实现
    {
        Console.WriteLine("我在听课");
    }
    static void Main(string[] args)
    {
        Program program = new Program();    //实例化类对象
        ITeacher iteacher = program;        //使用派生类对象实例化接口iTeacher
        iteacher.Name = "张三";
        iteacher.Sex = "男";
        iteacher.teach();                   //使用接口对象调用派生类实现的方法
        iteacher.today();                   //使用接口对象调用派生类实现的方法
        IStudent istudent = program;        //使用派生类对象实例化接口iTeacher
        istudent.study();                   //此时调用Name、Sex是Program的Name、Sex即张三、男
        istudent.Name = "李四";
        istudent.Sex = "女";
        istudent.study();                   //使用接口对象调用派生类实现的方法
        istudent.today();                   //使用接口对象调用派生类实现的方法
    }
}

3. 密封类和密封方法

3.1 密封类

        继承滥用会导致类的层次结构体系变得十分复杂,这样使得使用任意对类的理解和使用变得十分困难。为了避免滥用,C#提出了密封类的概念,一旦密封,就不能再被继承。

        使用密封类时应当满足的场景

  • 类包含带有安全敏感信息的继承受保护成员
  • 类继承多个虚成员,并且密封每个成员的开发和测试开销明显大于密封整个类
  • 类是一个要求使用反射进行快速搜索的属性。密封属性可以提高反射在检索属性时的性能

        密封类关键字seealed,使用密封类要注意的点

  • 密封类可以基层别的类或接口,但不能被继承
  • 密封类中不能声明受保护成员或虚成员,因为受保护成员只能从派生类访问,虚成员只能在派生类中重写,而密封类不能被派生;
  • 密封类不能声明为抽象的,即sealed修饰符不能与abstract修饰符同时使用,原因同上

        密封类实例

 public sealed class oneClass
 {
     public int i = 0;
     public void method()
     {
         Console.WriteLine("这是一个密封类");
     }
 }

3.2 密封方法

        并非所有方法都可以被声明为密封方法,密封方法只能用于对基类的虚方法进行实现,所以声明密封方法,sealed和override修饰符同时使用

        密封方法实例

public class OneClass
{
    public virtual void Method()
    {
        Console.WriteLine("基类中的虚方法");
    }
}
public class SonClass : OneClass
{
    public sealed override void Method()
    {
        base.Method();                              //调用基类中的Method()方法,
                                                    //base关键字为派生类调用基类成员提供了一种简写的方法
        Console.WriteLine("密封类中重写后的方法");
    }
}
internal class Program
{
    
    static void Main(string[] args)
    {
        SonClass sonClass = new SonClass();
        OneClass oneClass = sonClass;               //用派生类对象实例话基类
        oneClass.Method();
        sonClass.Method();
    }
}

        密封类与密封方法实例

public class OneClass
{
    public virtual void ShowInfo() { }              //虚方法,用来显示信息
}
public sealed class SonClass : OneClass
{
    private string id = "";
    private string name = "";
    public string ID { get { return id; } set { id = value; } }
    public string Name { get { return name; } set { name = value; } }
    public sealed override void ShowInfo()          //密封并重写基类中的ShowInfo()方法
    {
        Console.WriteLine(ID + " " + Name);
    }
}
internal class Program
{
    static void Main(string[] args)
    {
        SonClass sonClass = new SonClass();         //实例化密封类对象
        sonClass.ID = "001";                        //为密封类中的编号属性赋值
        sonClass.Name = "张三";                     //为密封类中的名称属性赋值
        sonClass.ShowInfo();                        //调用密封类中的密封方法
    }
}

4.委托和匿名方法

        委托时一种引用方法的类型,也就是方法的引用,或者可以理解为方法的别名,一旦为委托分配了方法,委托将与该方法具有完全相同的行为

4.1 委托

        C#中的委托(Delegate)是一种引用类型,即在委托的内部包含一个指向某个方法的指针,通过使用委托,把方法的引用封装在委托对象中,然后将委托对象传递给调用引用方法的代码。

        委托实例

public class TestClass
{
    public int Add(int x,int y)
    {
        return x + y;
    }
}
internal class Program
{
    public delegate int OneDelegate(int x, int y);  //定义一个委托类型OneDelegate
    static void Main(string[] args)
    {
        TestClass testClass = new TestClass();
        OneDelegate oneDelegate=testClass.Add;      //创建委托类型的实例oneDelegate,并绑定到Add方法;
        int intSum = oneDelegate.Invoke(2, 3);      //委托的调用,等同于int intSum = oneDelegate(2, 3);
        Console.WriteLine("运算的结果是"+intSum.ToString());
        Console.ReadLine();
    }
}

4.2 匿名方法

        匿名方法是为了简化委托的可操作性,匿名方法允许一个与委托关联的代码被内联地写入使用委托的位置。除了这种便利之外,匿名方法还共享了对本地语句包含的函数成员的访问,匿名方法的语法格式:

delegate(【参数列表】)
{
    【代码块】
}

        匿名方法使用实例

delegate void DelOutPut(string s);              //自定义委托类型
internal class Program
{
    static void OutPutString(string s)
    {
        Console.WriteLine(s);
    }
    static void Main(string[] args)
    {
        //委托的引用指向匿名方法delegate(string j){}
        DelOutPut del = delegate (string s)
        {
            Console.WriteLine(s);
        };
        del.Invoke("匿名方法被调用");          //委托对象del调用匿名方法
        //del("匿名方法被调用");               //委托也可以使用这种方式调用匿名方法
        Console.WriteLine("\n");
        del = OutPutString;                    //委托绑定到命名方法OutPutString
        del("命名方法被调用");                 //委托对象del调用命名方法
        Console.ReadLine();
    }
}

5.事件

        事件是指某个类的对象在运行过程中遇到的一些特定的事情,而这些特定的事情有必要通知给这个对象的使用者。

5.1 委托的发布和订阅

        由于委托能够引用方法,而且能够链接和删除其他委托对象,因而就能够通过委托来实现事件的“发布和订阅”,通过委托实现事件处理的过程,通常需要以下四部

        (1) 定义委托类型,并在发布者类中顶一个一个该类型的共有成员

        (2) 在订阅者类中定义委托处理方法

        (3) 订阅者对象将其事件处理方法链接到发布者对象的委托成员(一个委托类型的引用)上

        (4) 发布者在特定的情况下“激发”委托操作,从而自动调用订阅者对象的委托处理方法,整体流程如下

         委托发布和订阅的实例

public delegate void RingEvent(int ringKind);   //声明一个委托类型

public class SchoolRing                         //定义发布者类
{
    public RingEvent OnBellSound;               //公有委托类型实例
    public void Jow(int ringKind)               //成员方法
    {
        if(ringKind == 1 || ringKind == 2)
        {
            Console.WriteLine(ringKind == 1 ? "上课铃响了," : "下课铃想了,");
            if(OnBellSound != null)         //委托发布不为空,说明他已经订阅了具体的方法
            {
                OnBellSound(ringKind);      //执行委托实例绑定的方法
            }
        }
        else
        {
            Console.WriteLine("这个铃声不正确!");
        }
    }
}
public class Students                                   //定义委托订阅者类
{
    public void SubscribeToRing(SchoolRing schoolRing)  //定义委托事件的方法委托发布者类实例作为入参
    {
        schoolRing.OnBellSound += SchoolJow;            //调用委托发布类实例,绑定事件处理方法
    }
    public void SchoolJow(int ringKind)                 //绑定的事件处理方法
    {
        if(ringKind == 2)                               //打下课铃
        {
            Console.WriteLine("同学们开始课间休息");
        }
        else if(ringKind == 1)
        {
            Console.WriteLine("同学们开始认真学习!");  //打上课铃
        }
    }
}
internal class Program
{
    static void Main(string[] args)
    {
        SchoolRing schoolRing = new SchoolRing();       //创建事件发布者实例
        Students students = new Students();             //创建时间订阅者实例
        students.SubscribeToRing(schoolRing);           //学生订阅学校铃声
        Console.WriteLine("请输入打铃参数(1:表示打上课铃;2:表示打下课铃):");
        schoolRing.Jow(Convert.ToInt32(Console.ReadLine())); //开始打铃动作
        Console.ReadLine();
    }
}

        委托发布和订阅的缺点,外部对象可以通过“=”任意修改已经发布的委托,对委托的安全性造成严重的威胁,所以有了接下来的内容。

5.2 事件的发布和订阅

        为了解决委托发布和订阅的缺点,C#提供了专门的事件处理机制,保障事件订阅的可靠性,作坊很简单,在发布委托的定义中加上event关键字:

 public event RingEvent OnBellSound;               //公有委托类型实例

        通过这个修改,其他类型再使用OnBellSound委托时,就只能将其放在符合赋值运算符“+=”或“-=”的左侧。

5.3 EventHandler类

        由于在事件发布和订阅的过程中,委托类型是一件重复性的工作,因此.NET类库中定义了一个EventHandler委托类型,并建议尽量使用该类型作为事件的委托类型。

         该委托类型定义为:

pbulic delegate void EventHandler(object sender,EventArgs e);

        object类型的参数:表示引发事件的对象,由于事件成员只能由类型本身(委托发布者类)出发,因此在触发时传递给该参数的值通常是this,例如5.1的例子中,可将ShoolRing类的OnBellSound事件定义为EventHandler委托类型,那么触发事件的diamond就是“OnBellSound(this,null);"

        EventHandler类使用实例(修改5.1委托发布和订阅实例)

/// <summary>
/// 定义事件基类的派生类,用于处理委托发布者类发布的事件
/// </summary>
public class RingEventArgs : EventArgs            
{
    private int ringKind;
    public int RingKind                           //返回铃声参数
    {
        get { return ringKind; }
    }
    public RingEventArgs(int ringKind)            //在构造函数中初始化铃声
    {
        this.ringKind = ringKind;
    }
}
/// <summary>
///  //定义发布者类
/// </summary>
public class SchoolRing                        
{
    public EventHandler OnBellSound;               //公有委托类型实例
    //这里注意EventHander的定义是 public delegate void EventHandler(Object sender,EventArgs e)
    //没有返回值,有两个参数
    //参数1:sender表示引发事件的对象,由于事件成员只能有类本身触发,因此,在触发是传递该参数的值通常为this
    //参数2:事件数据的基类,如果有事件数据,可以通过派生类对象传递参数
    public void Jow(int ringKind)               //成员方法
    {
        if (ringKind == 1 || ringKind == 2)
        {
            Console.WriteLine(ringKind == 1 ? "上课铃响了," : "下课铃想了,");
            if (OnBellSound != null)         //委托发布不为空,说明他已经订阅了具体的方法
            {
                OnBellSound(this, new RingEventArgs(ringKind));      //执行委托实例绑定的方法
            }
        }
        else
        {
            Console.WriteLine("这个铃声不正确!");
        }
    }
}

/// <summary>
///  定义委托订阅者类
/// </summary>
public class Students                                   
{
    public void SubscribeToRing(SchoolRing schoolRing)  //定义委托事件的方法委托发布者类实例作为入参
    {
        schoolRing.OnBellSound += SchoolJow;            //调用委托发布类实例,绑定事件处理方法
    }

    public void SchoolJow(object sender,EventArgs e)
    {
        if (((RingEventArgs)e).RingKind == 2)           //将基类EventArgs类型参数还原为派生类RingEventArgs,并返获取RingKind
        {
            Console.WriteLine("同学们开始课间休息!");
        }
        else if(((RingEventArgs)e).RingKind ==1)        //将基类EventArgs类型参数还原为派生类RingEventArgs,并返获取RingKind
        {
            Console.WriteLine("同学们开始认真学习!");
        }
    }
    
}
internal class Program
{
    static void Main(string[] args)
    {
        SchoolRing schoolRing = new SchoolRing();       //创建事件发布者实例
        Students students = new Students();             //创建时间订阅者实例
        students.SubscribeToRing(schoolRing);           //学生订阅学校铃声
        Console.WriteLine("请输入打铃参数(1:表示打上课铃;2:表示打下课铃):");
        schoolRing.Jow(Convert.ToInt32(Console.ReadLine())); //开始打铃动作
        Console.ReadLine();
    }
}

5.4 Windows事件

        事件在Windows这样的图形界面程序中有着非常广泛的应用,事件的响应是程序与用户交互的基础。用户的绝大多数操作,如移动鼠标、改变光标位置、选择惨淡命令等,都可以触发相关的控件的事件,例如Button的Click(单击)就是一个EventHandler类型的事件:

public event EventHandler Click;
public Form1()
{
    InitializeComponent();
    button1.Click += Button1_Click;
}

private void Button1_Click(object sender, EventArgs e)
{
    this.Close();
}

Logo

Agent 垂直技术社区,欢迎活跃、内容共建。

更多推荐