C#面向对象总结

本文详细介绍了C#中的面向对象编程特性,包括抽象与接口、继承、多态性、方法重载、消息和事件。讲解了抽象类、接口、继承的规则,以及多态在方法覆盖和重载中的应用。同时,提到了C#中的构造函数、析构函数、委托和事件的定义及使用,以及事件处理的标准设计模式。文章通过实例展示了如何定义和使用这些概念,帮助读者深入理解C#面向对象编程的核心概念。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

1.  抽象与接口


抽象化是为了要降低程序版本更新后,在维护方面的负担,使得功能的提供者和功能的用户分开,各自独立,彼此不受影响。 


为了达到抽象化的目的,需要在功能提供者与功能使用者之间提供一个共同的规范,功能提供者与功能使用者都要按照这个规范来提供、使用这些功能。这个共用的规范就是接口,接口定义了功能数量、函数名称、函数参数、参数顺序等。它是一个能声明属性、字段和方法的编程构造。它不为这些成员实现,只提供定义。接口定义了功能提供者与功能使用者之间的准则,因此只要接口不变,功能提供者就可以任意更改实现的程序代码,而不影响到使用者。


一旦定义了接口,就可以在类中实现它。这样,类就可以支持接口所指定的所有属件和成员。注意,不能实例化接口,执行过程必须在实现接口的类中实现。






 

2.继承


继承是OOP(面向对象编程(Object Oriented Programming,OOP,面向对象程序设计))最重要的特性之—。任何类都可以从另—个类继承,这就是说,这个类拥有它继承的类的所有成员。在00P中,被继承(也称为派生)的类称为父类(也称为基类)。注意C#中的对象仅能派生于一个基类。


公共汽车、出租车、货车等都是汽车,但它们是不同的汽车,除了具有汽车的共性外,它们还具有自己的特点,如不同的操作方法,不同的用途等。这时我们可以把它们作为汽车的子类来实现,它们继承父类(汽车)的所有状态和行为,同时增加自己的状态和行为。通过父类和子类,我们实现了类的层次,可以从最一般的类开始,逐步特殊化,定义一系列的子类。同时,通过继承也实现了代码的复用,使程序的复杂性线性地增长,而不是呈几何级数增长。


    在继承一个基类时,成员的可访问性就成为一个重要的问题。派生类不能访问基类的私有成员,但可以访问其公共成员。不过,派生类和外部的代码都可以访问公共成员。这就是说,只使用这两个可访问性,不仅可以让一个成员被基类和派生类访问,而且也能够被外部的代码访问。为了解决这个问题,C#提供了第三种可访问性:protected,只有派生类才能访问protected成员。


    除了成员的保护级别外,我们还可以为成员定义其继承行为。基类的成员可以足虚拟的,也就是说,成员可以由继承它的类重写。派生类可以提供成员的其他执行代码。这种执行代码不会删除原来的代码,仍可以在类中访问原来的代码,但外部代码不能访问它们。如果没有提供其他执行方式,外部代码就访问基类中成员的执行代码。虚拟成员不能是私有成员。


    基类还可以定义为抽象类。抽象类不能直接实例化。要使用抽象类,必须继承这个类,抽象类可以有抽象成员,这些成员在基类中没有代码实现,所以这些执行代码必须在派生类中提供。


    最后,类可以是密封的。密封的类不能用作基类,所以也没有派生类。


    在C#中,所有的对象都有—个共同的基类object


3.多态性


    多态是面向对象程序设计的又一个特性。在面向过程的程序设计中,主要工作是编写一个个的过程或函数,这些过程和函数不能重名。例如在一个应用中,需要对数值型数据进行排序,还需要对字符型数据进行排序,虽然使用的排序方法相同,但要定义两个不同的过程(过程的名称也不同)来实现。


    在面向对象程序设计中,可以利用“重名”来提高程序的抽象度和简洁性。首先我们来理解实际的现象,例如,“启动”是所有交通工具都具有的操作,但是不同的具体交通工具,其“启动”操作的具体实现是不同的,如汽车的启动是“发动机点火——启动引擎”、“启动”轮船时要“起锚”、气球飞艇的“启动”是“充气——解缆”。如果不允许这些功能使用相同的名字,就必须分别定义“汽车启动”、“轮船启动”、“气球飞艇启动”多个方法。这样一来,用户在使用时需要记忆很多名字,继承的优势就荡然无存了。为了解决这个问题,在面向对象的程序设计中引入了多态的机制。


  多态是指一个程序中同名的不同方法共存的情况。主要通过子类对父类方法的覆盖来实现多态。这样一来,不同类的对象可以响应同名的方法来完成特定的功能,但其具体的实现方法却可以不同。例如同样的加法,把两个时间加在一起和把两个整数加在一起肯定完全不同。


  通过方法覆盖,子类可以重新实现父类的某些方法,使其具有自己的特征。例如对于车类的加速方法,其子类(如赛车)中可能增加了一些新的部件来改善提高加速性能,这时可以在赛车类中覆盖父类的加速方法。覆盖隐藏了父类的方法,使子类拥有自己的具体实现,更进一步表明了与父类相比,子类所具有的特殊性。


  多态性使语言具有灵活、抽象、行为共享的优势,很好地解决了应用程序函数同名问题。


    注意*并不是只有共享同一个父类的类才能利用多态性。只要子类和孙子类在继承层次结构


中有一个相同的类,它们就可以用相同的方式利用多态性。


4.重载


    方法重载是实现多态的另一个方法。通过方法重载,一个类中可以有多个具有相同名字的方法,由传递给它们的不同个数的参数来决定使用哪种方法。例如,对于一个作图的类,它有一个draw()方法用来画图或输出文字,我们可以传递给它一个字符串、一个矩形、一个圆形,甚至还可以再制定作图的初始位置、图形的颜色等。对于每一种实现,只需实现一个新的draw()方法即可,而不需要新起一个名字,这样大大简化了方法的实现和调用,程序员和用户不需要记住很多的方法名,只需要传入相应的参数即可。


因为类可以包含运算符如何运算的指令,所以可以把运算符用于从类实例化而来的对象。    我们为重载运算符编写代码,把它们用作类定义的一部分,而该运算符作用于这个类。也可以重载运算符,以相同的方式处理不同的类,其中一个(或两个)类定义包含达到这一目的的代码。


    注意只能用这种方式重载现有的C#运算符,不能创建新的运算符。


5.消息和事件


    对象之间必须要进行交互来实现复杂的行为。例如,要汽车加速,必须发给它一个消息,告诉它进行何种动作(这里是加速)以及实现这种动作所需要的参数(这里是需要达到的速度等)。一个消息包含三个方面的内容:消息的接收者、接收对象应采用的方法、方法所需要的参数。同时,接收消息的对象在执行相应的方法后,可能会给发送消息的对象返回一些信息。如上例中,汽车的仪表上会出现已达到的速度等。


在C#中,消息处理称为事件。对象可以激活事件,作为它们处理的一部分。为此,需要给代码添加事件处理程序,这是一种特殊类型的函数,在事件发生时调用。还需要配置这个处理程序,以监听我们感兴趣的事件。


    使用事件可以创建事件驱动的应用程序,这类应用程序很多。例如,许多基于Windows的应用程序完全依赖于事件。每个按钮单击或滚动条拖动操作都是通过事件处理实现的,其中事件是通过鼠标或键盘触发的。本章的后面将介绍事件是如何工作的。


6. 定义类
本节将重点讨论如何定义类本身。首先介绍基本的类定义语法、用于确定类可访问性的关键字、指定继承的方式以及接口的定义。


6.1 C#中的类定义


6.2类的定义


C#使用class关键字来定义类。其基本结构如下:


  Class MyClass


  {


    // class members


}


这段代码定义了一个类MyClass。定义了一个类后,就可以对该类进行实例化。在默认情况下,类声明为内部的,即只有当前代码才能访问,可以用intemal访问修饰符关键字显式指定,如下所示(但这是不必要的):


  internal class MyClass


  {


    // class members


}


另外,还可以制定类是公共的,可以由其它任意代码访问。为此,需要使用关键字public:


  public class MyClass


{


  // class members


}


    除了这两个访问修饰符关键字外,还可以指定类是抽象的(不能实例化,只能继承,可以有抽象成员)或密封的(sesled,不能继承)。为此,可以使用两个互斥的关键字abstract或sealed。所以,抽象类必须用下述方式声明:


      public abstract class MyClass


      {


        // class members, may be abstract


      }


密封类的声明如下所示:


      public sealed class MyClass


      {


        //class members


      }


    还可以在类定义中指定继承。C#支持类的单一继承,即只能有一个基类,语法如下:


  class MyClass : MyBaseClass


  {


    // class members


}


在C#的类定义中,如果继承了一个抽象类,就必须执行所继承的所有抽象成员(除非派生类也是抽象的)。


    编译器不允许派生类的可访问性比其基类更高。也就是说,内部类可以继承于一个公共类,但公共类不能继承于一个内部类。因此,下述代码就是不合法的:


      internal class MyBaseClass


      {


        // class members


       }


 


public class MyClass : MyBaseClass


{


  // class members


}


在C#中,类必须派生于另一个类。如果没有指定基类,则被定义的类就继承于基类System.Object。


    除了以这种方式指定基类外,还可以指定支持的接口。如果指定了基类,它必须紧跟在冒号的后面,之后才是指定的接口。必须使用逗号分隔基类名(如果有基类)和接口名。


    例如,给MyClass添加一接口,如下所示:


      class MyClass : IMyInterface


      {


        // class memebrs


       }


  


可以指定多个接口,所以下面的代码是有效的:


      public class MyClass : MyBaseClass, ImyInterface, ImySecondInterface


      {


        // class members


     }


   


 


 访问修饰符


  修饰符


 含义


none或internal类只能在当前程序中被访问


public类可以在任何地方访问


abstract或internal abstract类只能在当前程序中被访问,不能实例化,只能继承


public abstract类可以在任何地方访问,不能实例化,只能继承


sealed或internal sealed类只能在当前程序中被访问,不能派生,只能实例化


public sealed类可以在任何地方访问,不能派生,只能实例化


 


6.3接口的定义


接口声明的方式与声明类的方式相似,但使用的是关键字interface,例如:


  interface ImyInterface


  {


    // interface members


  }


访问修饰符关键字public和internal的使用方式是相同的,所以要使接口的访问是公共的,就必须使用public关键字:


      public interface ImyInterface


      {


        // interface members


     }


    关键字abstract和sealed不能在接口中使用,因为这两个修饰符在接口定义中是没有意义的(接口不包含执行代码,所以不能直接实例化,且必须是可以继承的)。


    接口的继承也可以用与类继承的类似方式来指定。主要的区别是可以使用多个基接口,例如:


      public interface IMyInterface : IMyBaseInterface, ImyBaseInterface2


      {


        // interface members


     }








//实例
using System;


public abstract class MyBaseClass


         {


        }


class MyClass:MyBaseClass


         {


         }


public interface IMyBaseInterface


         {


         }


interface IMyBaseInterface2


         {


         }


         interface ImyInterface : IMyBaseInterface, IMyBaseInterface2


         {


         }


    sealed class MyComplexClass:MyClass,IMyInterface


         {


         }


   class Class1


         {


              static void Main(string[] args)


              {


                     MyComplexClass myObj = new MyComplexClass();


                     Console.WriteLine(myObj.ToString());              


              }


        }


这里的Clsss1不是主要类层次结构中的一部分,而是处理Main()方法的应用程序的入口点。MyBaseClass和IMyBaseInterface被定义为公共的,其他类和接口都是内部的。其中MyComplexClass继承MyClass和IMyInterface,MyClass继承MyBassClass,IMyInterface继承IMyBaseInterface和IMyInterface2,而MyBaseClass和IMyBaseInterface、IMyBaseInterface2的共同的基类为object。Main()中的代码调用MyComplexClass的一个实例myObj的ToString()方法。这是继承System.ObJect的一种方法,功能是把对象的类名作为一个字符串返回,该类名用所有相关的命名空间来限定。


6.4 Object类


前面提到所有的.NET类都派生于System.Object。实际上,如果在定义类时没有指定基类,编译器就会自动假定这个类派生于object。其重要性在于,自己定义的所有类除了自己定义的方法和属性外,还可以访问为Object定义的许多公共或受保护的成员方法。


 






方    法


访问修饰符        作    用


string ToString()


public virtual           返回对象的字符串表示。在默认情况下,这是一个类类型的限定名,但它可以被重写,以便给类类型提供合适的实现方式






int GetHashTable()


public virtual                 在实现散列表时使用






bool Equals(object obj)


public virtual       把调用该方法的对象与另一个对象相比较,如果它们相等,就返回true。以默认的执行方式进行检查,以查看对象的参数是否引用了同一对象。如果想以不同的方式来比较对象,可以重写该方法。


bool Equals(object objA,object objB)


public static          这个方法比较传递给它的两个对象是否相等。如果两个对象都是空引用,这个方法会返回true。








bool ReferenceEquals(object objA, object objB)


public static            比较两个引用是否指向同一个对象


Type GetType()
public                     返回对象类型的详细信息


object MemberwiseClone()


protected              通过创建一个新对象实例并复制成员,来复制该对象。成员复制不会得到这些成员的新实例。新对象的任何引用类型成员都将引用与源类相同的对象,这个方法是受保护的,所以只能在类或派生的类中使用。


 


以上方法是.NET Framework中对象类型必须支持的基本方法,但我们可以从不使用它们。下面将简要几个方法的作用。


GetType()方法:这个方法返回从System.Type派生的类的一个实例。在利用多态性时,GetType()是一个有用的方法,它允许根据对象的类型来执行不同的操作。联合使用GetType()和typeof(),就可以进行比较,如下所示:


  if (myObj.GetType() == typeof(MyComplexClass))


  {


     // myObj is an instance of the class MyComplexClass


  }


ToString()方法:是获取对象的字符串表示的一种便捷方式。当只需要快速获取对象的内容,以用于调试时就可以使用这个方法。在数据的格式化方面,它提供的选择非常少:例如,日期在原则上可以表示为许多不同的格式,但DateTime.ToString()没有在这方面提供任何选择。例如:


      int i = -50;


string str = i.ToString();  // returns "–50"


下面是另一个例子:


enum Colors {Red, Orange, Yellow};


// later on in code...


Colors favoriteColor = Colors.Orange;


string str = favoriteColor.ToString();                 // returns "Orange"


Object.ToString()声明为虚类型,在这些例子中,该方法的实现代码都是为C#预定义数据类型重写过的代码,以返回这些类型的正确字符串表示。Colors枚举是一个预定义的数据类型,它实际上实现为一个派生于System.Enum的结构,而System.Enum有一个ToString()重写方法,来处理用户定义的所有枚举。


如果不在自己定义的类中重写ToString(),该类将只继承System.Object执行方式——显示类的名称。如果希望ToString()返回一个字符串,其中包含类中对象的值信息,就需要重写它。下面用一个例子Money来说明这一点。在该例子中,定义一个非常简单的类Money,表示钱数。Money是decimal类的包装器,提供了一个ToString()方法(这个方法必须声明为override,因为它将重写Object提供的ToString()方法)。该例子的完整代码如下所示:






      using System;


  class MainEntryPoint


  {


      static void Main(string[] args)


      {


         Money cash1 = new Money();


         cash1.Amount = 40M;


         Console.WriteLine("cash1.ToString() returns: " + cash1.ToString());        


      }


   }


   class Money


   {


      private decimal amount;


      public decimal Amount


      {


         get


         {


            return amount;


         }


         set


         {


            amount = value;


         }


        }


       public override string ToString()


       {


         return "$" + Amount.ToString();


       }


      }


在Main()方法中,先实例化一个Money对象,在这个实例化过程中调用了ToString(),选择了我们自己的重写方法。运行这段代码,会得到如下结果:


StringRepresentations


cash1.ToString() returns: $40


6.5构造函数和析构函数


在C#中定义类时,常常不需要定义相关的构造函数和析构函数,因为基类System.Object提供了一个默认的实现方式。但是,如果需要,也可以提供我们自己的构造函数和析构函数,以便初始化对象和清理对象。


1.构造函数


    使用下述语法把简单的构造函数添加到一个类中:


      class MyClass


      {


        public MyClass()


        {


          // Constructor code


      }


// rest of class definition


    }


    这个构造函数与包含它的类同名,且没有参数,这是一个公共函数,所以用来实例化类的对象。


    也可以使用私有的默认构造函数,即这个类的对象实例不能用这个构造函数来创建。例如:


      class MyClass


      {


        private MyClass()


        {


          //Constructor code


      }


// rest of class definition


    }


  构造函数也可以重载,即可以为构造函数提供任意多的重载,只要它们的签名有明显的区别,例如:


class MyClass


{


  public MyClass()


  {


    //Default contructor code


}


public MyClass(int number)


{


  //Non-default contructot code


}


//rest of class definition


}


    如果提供了带参数的构造函数,编译器就不会自动提供默认的构造函数,下面的例子中,因为明确定义了一个带一个参数的构造函数,所以编译器会假定这是可以使用的唯一构造函数,不会隐式地提供其他构造函数:


      public class MyNumber


      {


        public MyNumber(int number)


        {


          // Contructor code


    }


// rest of class definition


   }


2.构造函数的执行序列


    在讨论构造函数前,先看看在默认情况下,创建类的实例时会发生什么情况。


为了实例化派生的类,必须实例化它的基类。而要实例化这个基类,又必须实例化这个基类的基类,这样一直到实例化System.Object为止。结果是无论使用什么构造函数实例化一个类,总是要先调用System.ObJect.Object()。


    如果对一个类使用非默认的构造函数,默认的情况是在其基类上使用匹配十这个构造函数签名的构造函数。如果没有找到这样的构造函数,就使用基类的默认构造函数。下面介绍一个例子,说明事件的发生顺序。代码如下:


      public class MyBaseClass


      {


        public MyBaseClass()


        {


}


public MyBaseClass(int i)


{


}


}


public class MyDerivedClass : MyBaseClass


{


  public MyDerivedClass()


  {


}


public MyDerivedClass(int i)


{


}


public MyDerivedClass(int i, int j)


{


}


}


    如果以下面的方式实例化MyDerivedClass:


      MyDrivedClass myObj = new MyDerivedClass();


    则发生下面的一系列事件:


● 执行System.Object.Object()构造函数。


● 执行MyBaseClass. MyBaseClass()构造函数。


● 执行MyDrivedClass. MyDerivedClass()构造函数。


另外,如果使用下面的语句:


      MyDrivedClass myObj = new MyDrivedClass(4);


则发生下面的一系列事件:


● 执行System.Object.Object()构造函数。


● 执行MyBaseClass. MyBaseClass(int i)构造函数。


● 执行MyDrivedClass. MyDerivedClass(int i)构造的数。


最后,如果使用下面的语句;


    MyDeivedClass myObj = new MyDerivcdClass(4, 8);


则发生下面的一系列事件:


● 执行System. Object. Object()构造函数。


    ● 执行MyBaseClass. MyBaseClass()构造函数。


    ● 执行MyDerivedClass. MyDerivedClass(int i,int j)构造函数


    有时需要对发生的事件进行更多的控制。例如,在上面的实例化例子中,需要有下面的事件序列:


    ● 执行System.Object. Object()构造函数。


    ● 执行MyBaseClass. MyBaseClass(int i)构造函数。


    ● 执行MyDerivedClass. MyDerivedClass(int i, int j)构造函数。


    使用这个序列可以编写在MyBaseClass(int i)中使用int i参数的代码,即MyDerivedClass(int i, int j))构造函数要做的工作比较少,只需要处理int j参数(假定int i参数在两种情况下有相同的含义)。为此,只需指定在派生类的构造函数定义中所使用的基类的构造函数即可,如下所示:


      public class MyDerivedClass : MyBaseClass


      {


        …


        public MyDerivedClass(int i, int j) : base(i)


        {


}


}


    其中,base关键字指定.NET实例化过程,以使用基类中匹配指定签名的构造函数。这里使用了一个int i参数,所以应使用MyBaseClass(int i)。这么做将不调用MyBaseClass(),而是执行本例前面列出的事件序列。


    也可以使用这个关键字指定基类构造函数的字面值,例如使用MyDerivedClass的默认构造函数调用MyBaseClass非默认的构造函数:


      public class MyDerivedClass : MyBaseClass


      {


        public MyDerivedClass() : base(5)


        {


     }





   }


这段代码将执行下述序列:


● 执行System. Object. Object()构造函数。


● 执行MyBaseClass. MyBaseClass(int i)构造函数。


● 执行MyDerivedClass. MyDerivedClass()构造函数。


除了base关键字外,这里还可以使用另一个关键字this。这个关键字指定在调用指定的构造函数前,.NET实例化过程对当前类使用非默认的构造函数。例如:


  public class MyDerivedClass : MyBaseClass


  {


    public MyDerivedClass() : this(5, 6)


    {


   }





public MyDerivedClass(int i, int j) : base(i)


{


}


}


    这段代码将执行下述序列:


    ● 执行System. Object. Object()构造函数。


    ● 执行MyBaseClass. MyBaseClass(int i)构造函数。


    ● 执行MyDerivedClass. MyDeri
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值