C# 面向对象(封装、继承、多态)


面向对象的基本知识:

C#程序分为面向过程和面向对象

什么是对象:一切皆为对象:Object,生活中常说的“东西”就是程序里面所指的对象;生活中遇到的东西我们都在下意识的归类;归类意味着抽象模型;

类:class,对某类众多对象的共同特点抽象出来的模型。

他们的关系:类是好多对象的抽象,对象是类的实例化。

面向对象是把构成问题事务分解成各个对象,建立对象 的目的不是为了完成一个步骤,而是为了描叙某个事物
在整个解决问题的步骤中的行为。
例如:
两个套在一起的圆,求内圆的周长和内圆与外圆之间的面积,用面向对象的思想做
//首先做了一个名为circle的类,它可以生产任何一个半径不同的圆,在构造函数中,为半径赋值。
class circle
    {
        float r;
        public circle(float a)
        {
            r = a;
        }
        public double zhouchang()
        {
            return 2 * 3.14 * r;
        }
        public double mianji()
        {
            return 3.14 * r * r;
        }
    }
    class Program
    {
        static void Main(string[] args)
        {
            circle m = new circle(10);
            circle n = new circle(20);
            double bc = m.zhouchang();
            double mj = (n.mianji()-m.mianji());
            Console.WriteLine("内圆的周长为:"+bc);
            Console.WriteLine("花砖的面积为:"+mj);
        }
    }

面向对象特性: 封装、继承、多态
OOP( 面向对象编程 ) 达到软件工程的
三个主要目标:重用性、灵活性和扩展性。
类中的方法一般分为:构造方法(函数);属性方法(函数):成员变量赋值取值;行为方法(函数):变量运算。






封装:

1,封装含义:

(1)不同类的变量只属于各自的类。

(2)不同对象的成员变量只属于各自的对象,彼此不受影响。

(3)对象中的变量需要通过方法(函数)实现操作,比较安全。

封装为了安全,尽量不用public来声明变量,避免在main函数中可以直接访问赋值而降低了安全性,在类中建立public的方法来赋值,main中调用此方法传值。

2. 成员变量及访问修饰:

private 私有成员 ,protected 受保护的成员,public 公有成员

3. 构造函数:

C# 构造函数与析构函数

4. 重载(函数或方法):

函数名相同,参数不同(参数个数或类型不同)的多个函数就形成了重载。

重载只与函数名和形参有关,与返回类型无关。

实例:

创建一个名为 SumUtils 的类,在类中分别定义计算两个整数、两个小数、 两个字符串类型的和,以及从 1 到给定整数的和。在 Main 方法中分别调用定义好的方法。

class SumUtils
{
    public int Sum(int a,int b)
    {
        return a + b;
    }
    public double Sum(double a,double b)
    {
        return a + b;
    }
    public string Sum(string a,string b)
    {
        return a + b;
    }
    public int Sum(int a)
    {
        int sum = 0;
        for(int i = 1; i < a; i++)
        {
            sum += i;
        }
        return sum;
    }
}

从上面的程序可以看出在该类中定义的方法名称都是 Sum,仅是参数的类型或个数不同而已。在 Main 方法中调用上述定义的方法,代码如下:

class Program
{
    static void Main(string[] args)
    {
        SumUtils s = new SumUtils();
        //调用两个整数求和的方法
        Console.WriteLine("两个整数的和为:" + s.Sum(3, 5));
        //调用两个小数求和的方法
        Console.WriteLine("两个小数的和为:" + s.Sum(3.2, 5.6));
        //调用两个字符串连接的方法
        Console.WriteLine("连个字符串的连接结果为:" + s.Sum("C#", "方法重载"));
        //输出 1 到 10 的和
        Console.WriteLine("1 到 10 的和为:" + s.Sum(10));
    }
}

5.  属性: 

C# get和set访问器:获取和设置字段(属性)的值

(1)属性是用来为成员变量赋值和取值的,它有代替属性方法的作用,一般用属性。

(2)属性定义的时候,属性名后面没有小括号。

(3)属性都是public。

(4)属性中只能包含两个部分:get和set。代码也只能写在get和set的花括号里面。

(5)属性分为只读属性,只写属性和可读写属性,与get和set的有无有关系。

6. this关键字:

this 关键字指代类的当前实例,还可用作扩展方法的第一个参数的修饰符。

以下是 this 的常见用法:

    限定类似名称隐藏的成员,例如:

public class Employee
{
    private string alias;
    private string name;

    public Employee(string name, string alias)
    {
        // Use this to qualify the members of the class
        // instead of the constructor parameters.
        this.name = name;
        this.alias = alias;
    }
}

    将对象作为参数传递给方法,例如:

CalcTax(this);

    声明索引器,例如: 

public int this[int param]
{
    get { return array[param]; }
    set { array[param] = value; }
}

静态成员函数,因为它们存在于类级别且不属于对象,不具有 this 指针。 在静态方法中引用 this 会生成错误。

在此示例中,this 用于限定类似名称隐藏的 Employee 类成员、name 和 alias。 它还用于将某个对象传递给属于其他类的方法 CalcTax

class Employee
{
    private string name;
    private string alias;
    private decimal salary = 3000.00m;

    // Constructor:
    public Employee(string name, string alias)
    {
        // Use this to qualify the fields, name and alias:
        this.name = name;
        this.alias = alias;
    }

    // Printing method:
    public void printEmployee()
    {
        Console.WriteLine("Name: {0}\nAlias: {1}", name, alias);
        // Passing the object to the CalcTax method by using this:
        Console.WriteLine("Taxes: {0:C}", Tax.CalcTax(this));
    }

    public decimal Salary
    {
        get { return salary; }
    }
}

class Tax
{
    public static decimal CalcTax(Employee E)
    {
        return 0.08m * E.Salary;
    }
}

class MainClass
{
    static void Main()
    {
        // Create objects:
        Employee E1 = new Employee("Mingda Pan", "mpan");

        // Display results:
        E1.printEmployee();
    }
}
/*
Output:
    Name: Mingda Pan
    Alias: mpan
    Taxes: $240.00
 */

 7. is关键字(运算符):

is 运算符检查表达式的结果是否与给定的类型相匹配。

从 C# 7.0 开始,还可使用 is 运算符将表达式与模式相匹配,如下例所示:

static bool IsFirstSummerMonday(DateTime date) => date is { Month: 6, Day: <=7, DayOfWeek: DayOfWeek.Monday };

在前面的示例中,is 运算符将表达式与关系模式和带有嵌套常量属性模式相匹配。

is 运算符在以下应用场景中很有用:

  • 检查表达式的运行时类型,如下例所示:

int i = 34;
object iBoxed = i;
int? jNullable = 42;
if (iBoxed is int a && jNullable is int b)
{
    Console.WriteLine(a + b);  // output 76
}

前面的示例演示声明模式的用法。 

  • 检查是否为 null,如下例所示:

if (input is null)
{
    return;
}

将表达式与 null 匹配时,编译器保证不会调用用户重载的 == 或 != 运算符。 

  • 从 C# 9.0 开始,可使用否定模式 执行非 null 检查,如下例所示:

if (result is not null)
{
    Console.WriteLine(result.ToString());
}

8. partial关键字 :

①:(类型)通过分部类型可以定义要拆分到多个文件中的类、结构、接口或记录。

namespace PC
{
    partial class A
    {
        int num = 0;
        void MethodA() { }
        partial void MethodC();
    }
}

②:(方法)分部方法在分部类型的一部分中定义了签名,并在该类型的另一部分中定义了实现。 通过分部方法,类设计器可提供与事件处理程序类似的方法挂钩,以便开发者决定是否实现。 如果开发者不提供实现,则编译器在编译时删除签名。 以下条件适用于分部方法:

  • 声明必须以上下文关键字 partial 开头。

  • 分部类型各部分中的签名必须匹配。

在以下情况下,不需要使用分部方法即可实现:

任何不符合所有这些限制的方法(例如 public virtual partial void 方法)都必须提供实现。

下列示例显示在分部类的两个部分中定义的分部方法:

namespace PM
{
    partial class A
    {
        partial void OnSomethingHappened(string s);
    }

    // This part can be in a separate file.
    partial class A
    {
        // Comment out this method and the program
        // will still compile.
        partial void OnSomethingHappened(String s)
        {
            Console.WriteLine("Something happened: {0}", s);
        }
    }
}

分部方法还可用于与源生成器结合。 例如,可使用以下模式定义 regex:

[RegexGenerated("(dog|cat|fish)")]
partial bool IsPetMatch(string input);

 9. 静态成员:

非静态变量称为实例变量,非静态方法称为实例方法,实例成员的数据存在每个对象中,用对象名来调用。

静态成员包括:静态变量,静态属性,静态方法。

定义一个成员为静态的:在变量或方法前加static,如:static int a;

静态变量是属于类的,每个对象都有的并且相同的东西只保存一份,不和实例变量那样在每个对象里都面保存一份。

可以说它不属于任何对象,也可以说它又属于任何一个对象,给每个对象用,节省空间。

例如:每包粉笔的颜色是静态成员,每支粉笔的剩余长度是实例成员。

静态变量或方法不需要new出来。

在C#中,定义了一个粉笔的类:

class Fenbi
   {
       static string _Color;

    public static string Color
    {
      get { return Fenbi._Color; }
      set { Fenbi._Color = value; }
    }
       int _Lenght;

    public int Lenght
    {
      get { return _Lenght; }
      set { _Lenght = value; }
    }  
   }

(1)在当前类的花括号外,静态成员只能用类名来调用,不能用对象名来调用,而实例成员只能用对象名来调用,不能用类名来调用。

Fenbi.Color ="Yellow";
Fenbi b = new Fenbi();
b.Lenght = 10;

 (2)在当前类的花括号内,静态方法只能直接调用静态成员,不能调用非静态成员,实例方法可以调用非静态和静态的成员。

public static void xiezi()
       {
           Console.WriteLine("写出"+_Color+"的文字了");
       }
public void change()
        {
            Console.WriteLine(_Color+"的粉笔长度变为:"+_Lenght);
         }

10.  拷贝:

浅拷贝:传递引用,不赋值对象。
深拷贝:创建一个新的对象。






继承:

1. 概念:

继承用于创建可重用、扩展和修改在其他类中定义的行为 的新类。 其成员被继承的类称为“ 基类 ,继承这些成员的 类称为“ 派生类 派生类只能有一个直接基类 。 但是, 继承是可传递的 。 如果 ClassB 派生出 ClassC ClassA 派生出 ClassB ,则 ClassC 会继承 ClassB ClassA 中声
明的成员。
由继承的关系我们知道,基类 有的,派生类都有;派生类有 的,基类可能没有。即:
派生类可以赋值给基类,而基类不能赋值给派生类。

 通过在派生的类名后面追加冒号和基类名称,可以指定基类。

public class Animal
{
  protected string name;
  public Animal()
  {
    name = "coco";
  }
}

public class Dog:Animal
{
  public Dog()
  {
    Console.Write(name);
  }
}

基类的初始化:

派生类继承了基类的成员变量和成员方法。因此父类对象应在子类对象创建之前被创建。您可以在成员初始化列表中进行父类的初始化。

using System;
namespace RectangleApplication
{
   class Rectangle
   {
      // 成员变量
      protected double length;
      protected double width;
      public Rectangle(double l, double w)
      {
         length = l;
         width = w;
      }
      public double GetArea()
      {
         return length * width;
      }
      public void Display()
      {
         Console.WriteLine("长度: {0}", length);
         Console.WriteLine("宽度: {0}", width);
         Console.WriteLine("面积: {0}", GetArea());
      }
   }//end class Rectangle  
   class Tabletop : Rectangle
   {
      private double cost;
      public Tabletop(double l, double w) : base(l, w)
      { }
      public double GetCost()
      {
         double cost;
         cost = GetArea() * 70;
         return cost;
      }
      public void Display()
      {
         base.Display();
         Console.WriteLine("成本: {0}", GetCost());
      }
   }
   class ExecuteRectangle
   {
      static void Main(string[] args)
      {
         Tabletop t = new Tabletop(4.5, 7.5);
         t.Display();
         Console.ReadLine();
      }
   }
}

当上面的代码被编译和执行时,它会产生下列结果:

长度: 4.5
宽度: 7.5
面积: 33.75
成本: 2362.5

C# 多重继承 :

多重继承指的是一个类别可以同时从多于一个父类继承行为与特征的功能。与单一继承相对,单一继承指一个类别只可以继承自一个父类。

C# 不支持多重继承。但是,您可以使用接口来实现多重继承。下面的程序演示了这点:

using System;
namespace InheritanceApplication
{
   class Shape
   {
      public void setWidth(int w)
      {
         width = w;
      }
      public void setHeight(int h)
      {
         height = h;
      }
      protected int width;
      protected int height;
   }

   // 基类 PaintCost
   public interface PaintCost
   {
      int getCost(int area);

   }
   // 派生类
   class Rectangle : Shape, PaintCost
   {
      public int getArea()
      {
         return (width * height);
      }
      public int getCost(int area)
      {
         return area * 70;
      }
   }
   class RectangleTester
   {
      static void Main(string[] args)
      {
         Rectangle Rect = new Rectangle();
         int area;
         Rect.setWidth(5);
         Rect.setHeight(7);
         area = Rect.getArea();
         // 打印对象的面积
         Console.WriteLine("总面积: {0}",  Rect.getArea());
         Console.WriteLine("油漆总成本: ${0}" , Rect.getCost(area));
         Console.ReadKey();
      }
   }
}

当上面的代码被编译和执行时,它会产生下列结果:

总面积: 35
油漆总成本: $2450

2. 特点:

             单继承,一个父类可以派生多个子类,但每个子类只能有一个父类

             如果一个类,没有明确指定父类是谁,默认是object。除了object类之外,所有类都有一个父类。

             子类可以从父类继承下父类的成员变量和成员方法。

 3. 访问修饰符合访问权限:

private 的成员不被继承,只能在该类中访问。

protected成员可以被继承,能在该类和派生类中访问到,在外界访问不到。父类中的变量一般用protected。

public成员可以被继承,能在所有地方访问到。

4. base 关键字:

子类中可以用(base.父类中的成员 )来调用父类中的成员,base()调用父类构造,base.xxx()调用父类成员方法。

调用的参数值会被覆盖,方法也会被覆盖。

base 关键字用于从派生类中访问基类的成员:

  • 调用基类上已被其他方法重写的方法。

  • 指定创建派生类实例时应调用的基类构造函数。

仅允许基类访问在构造函数、实例方法或实例属性访问器中进行。

从静态方法中使用 base 关键字是错误的。

所访问的基类是类声明中指定的基类。 例如,如果指定 class ClassB : ClassA,则从 ClassB 访问 ClassA 的成员,而不考虑 ClassA 的基类。

示例 1:

在本例中,基类 Person 和派生类 Employee 都有一个名为 Getinfo 的方法。 通过使用 base 关键字,可以从派生类中调用基类的 Getinfo 方法。

public class Person
{
    protected string ssn = "444-55-6666";
    protected string name = "John L. Malgraine";

    public virtual void GetInfo()
    {
        Console.WriteLine("Name: {0}", name);
        Console.WriteLine("SSN: {0}", ssn);
    }
}
class Employee : Person
{
    public string id = "ABC567EFG";
    public override void GetInfo()
    {
        // Calling the base class GetInfo method:
        base.GetInfo();
        Console.WriteLine("Employee ID: {0}", id);
    }
}

class TestClass
{
    static void Main()
    {
        Employee E = new Employee();
        E.GetInfo();
    }
}
/*
Output
Name: John L. Malgraine
SSN: 444-55-6666
Employee ID: ABC567EFG
*/

示例 2: 

本示例显示如何指定在创建派生类实例时调用的基类构造函数。

public class BaseClass
{
    int num;

    public BaseClass()
    {
        Console.WriteLine("in BaseClass()");
    }

    public BaseClass(int i)
    {
        num = i;
        Console.WriteLine("in BaseClass(int i)");
    }

    public int GetNum()
    {
        return num;
    }
}

public class DerivedClass : BaseClass
{
    // This constructor will call BaseClass.BaseClass()
    public DerivedClass() : base()
    {
    }

    // This constructor will call BaseClass.BaseClass(int i)
    public DerivedClass(int i) : base(i)
    {
    }

    static void Main()
    {
        DerivedClass md = new DerivedClass();
        DerivedClass md1 = new DerivedClass(1);
    }
}
/*
Output:
in BaseClass()
in BaseClass(int i)
*/

5. 继承关系中实例化子类的流程:

先执行父类的构造函数,再执行子类的构造函数。

6. sealed 关键字:

如果用来修饰class,称为密封类,此类无法被继承;如果用来修饰方法,该方法无法被重写。

sealed 修饰符可阻止其他类继承自该类。 在下面的示例中,类 B 继承自类 A,但没有类可以继承自类 B

class A {}
sealed class B : A {}

还可以对替代基类中的虚方法或属性的方法或属性使用 sealed 修饰符。 这使你可以允许类派生自你的类并防止它们替代特定虚方法或属性。

示例 :

在下面的示例中,Z 继承自 Y,但 Z 无法替代在 X 中声明并在 Y 中密封的虚函数 F

class X
{
    protected virtual void F() { Console.WriteLine("X.F"); }
    protected virtual void F2() { Console.WriteLine("X.F2"); }
}

class Y : X
{
    sealed protected override void F() { Console.WriteLine("Y.F"); }
    protected override void F2() { Console.WriteLine("Y.F2"); }
}

class Z : Y
{
    // Attempting to override F causes compiler error CS0239.
    // protected override void F() { Console.WriteLine("Z.F"); }

    // Overriding F2 is allowed.
    protected override void F2() { Console.WriteLine("Z.F2"); }
}

在类中定义新方法或属性时,可以通过不将它们声明为虚拟,来防止派生类替代它们。

将 abstract 修饰符与密封类结合使用是错误的,因为抽象类必须由提供抽象方法或属性的实现的类来继承。

应用于方法或属性时,sealed 修饰符必须始终与 override 结合使用。

因为结构是隐式密封的,所以无法继承它们。

sealed class SealedClass
{
    public int x;
    public int y;
}

class SealedTest2
{
    static void Main()
    {
        var sc = new SealedClass();
        sc.x = 110;
        sc.y = 150;
        Console.WriteLine($"x = {sc.x}, y = {sc.y}");
    }
}
// Output: x = 110, y = 150

在上面的示例中,可能会尝试使用以下语句从密封类继承:

class MyDerivedC: SealedClass { } // Error 

结果是出现错误消息:

'MyDerivedC': cannot derive from sealed type 'SealedClass' 

若要确定是否密封类、方法或属性,通常应考虑以下两点:

  • 派生类通过可以自定义类而可能获得的潜在好处。

  • 派生类可能采用使它们无法再正常工作或按预期工作的方式来修改类的可能性。






多态:

1. 概念:

父类引用指向不同子类实例的时候,父类引用所调用的函数都是子类的函数,由于子类对象不同,父类引用调用的成员表现出来的不同状态就是一种多态。

多态是同一个行为具有多个不同表现形式或形态的能力。

多态性意味着有多重形式。在面向对象编程范式中,多态性往往表现为"一个接口,多个功能"。

多态性可以是静态的或动态的。在静态多态性中,函数的响应是在编译时发生的。在动态多态性中,函数的响应是在运行时发生的。

在 C# 中,每个类型都是多态的,因为包括用户定义类型在内的所有类型都继承自 Object。

多态是同一个行为具有多个不同表现形式或形态的能力。

多态性意味着有多重形式。在面向对象编程范式中,多态性往往表现为"一个接口,多个功能"。

多态性可以是静态的或动态的。在静态多态性中,函数的响应是在编译时发生的。在动态多态性中,函数的响应是在运行时发生的。

在 C# 中,每个类型都是多态的,因为包括用户定义类型在内的所有类型都继承自 Object。

静态多态性:

在编译时,函数和对象的连接机制被称为早期绑定,也被称为静态绑定。C# 提供了两种技术来实现静态多态性。分别为:

  • 函数重载
  • 运算符重载

运算符重载将在下一章节讨论,接下来我们将讨论函数重载。

函数重载:

在同一个范围内对相同的函数名有多个定义。函数的定义必须彼此不同,可以是参数列表中的参数类型不同,也可以是参数个数不同。不能重载只有返回类型不同的函数声明。

下面的实例演示了几个相同的函数 Add(),用于对不同个数参数进行相加处理:

using System;
namespace PolymorphismApplication
{
    public class TestData  
    {  
        public int Add(int a, int b, int c)  
        {  
            return a + b + c;  
        }  
        public int Add(int a, int b)  
        {  
            return a + b;  
        }  
    }  
    class Program  
    {  
        static void Main(string[] args)  
        {  
            TestData dataClass = new TestData();
            int add1 = dataClass.Add(1, 2);  
            int add2 = dataClass.Add(1, 2, 3);

            Console.WriteLine("add1 :" + add1);
            Console.WriteLine("add2 :" + add2);  
        }  
    }  
}

下面的实例演示了几个相同的函数 print(),用于打印不同的数据类型:

using System;
namespace PolymorphismApplication
{
   class Printdata
   {
      void print(int i)
      {
         Console.WriteLine("输出整型: {0}", i );
      }

      void print(double f)
      {
         Console.WriteLine("输出浮点型: {0}" , f);
      }

      void print(string s)
      {
         Console.WriteLine("输出字符串: {0}", s);
      }
      static void Main(string[] args)
      {
         Printdata p = new Printdata();
         // 调用 print 来打印整数
         p.print(1);
         // 调用 print 来打印浮点数
         p.print(1.23);
         // 调用 print 来打印字符串
         p.print("Hello Runoob");
         Console.ReadKey();
      }
   }
}

 当上面的代码被编译和执行时,它会产生下列结果:

输出整型: 1
输出浮点型: 1.23
输出字符串: Hello Runoob

动态多态性 :

C# 允许您使用关键字 abstract 创建抽象类,用于提供接口的部分类的实现。当一个派生类继承自该抽象类时,实现即完成。抽象类包含抽象方法,抽象方法可被派生类实现。派生类具有更专业的功能。

请注意,下面是有关抽象类的一些规则:

  • 您不能创建一个抽象类的实例。
  • 您不能在一个抽象类外部声明一个抽象方法。
  • 通过在类定义前面放置关键字 sealed,可以将类声明为密封类。当一个类被声明为 sealed 时,它不能被继承。抽象类不能被声明为 sealed。

下面的程序演示了一个抽象类:

using System;
namespace PolymorphismApplication
{
   abstract class Shape
   {
       abstract public int area();
   }
   class Rectangle:  Shape
   {
      private int length;
      private int width;
      public Rectangle( int a=0, int b=0)
      {
         length = a;
         width = b;
      }
      public override int area ()
      {
         Console.WriteLine("Rectangle 类的面积:");
         return (width * length);
      }
   }

   class RectangleTester
   {
      static void Main(string[] args)
      {
         Rectangle r = new Rectangle(10, 7);
         double a = r.area();
         Console.WriteLine("面积: {0}",a);
         Console.ReadKey();
      }
   }
}

当上面的代码被编译和执行时,它会产生下列结果:

Rectangle 类的面积:
面积: 70

当有一个定义在类中的函数需要在继承类中实现时,可以使用虚方法

虚方法是使用关键字 virtual 声明的。

虚方法可以在不同的继承类中有不同的实现。

对虚方法的调用是在运行时发生的。

动态多态性是通过 抽象类 和 虚方法 实现的。

以下实例创建了 Shape 基类,并创建派生类 Circle、 Rectangle、Triangle, Shape 类提供一个名为 Draw 的虚拟方法,在每个派生类中重写该方法以绘制该类的指定形状。

using System;
using System.Collections.Generic;

public class Shape
{
    public int X { get; private set; }
    public int Y { get; private set; }
    public int Height { get; set; }
    public int Width { get; set; }
   
    // 虚方法
    public virtual void Draw()
    {
        Console.WriteLine("执行基类的画图任务");
    }
}

class Circle : Shape
{
    public override void Draw()
    {
        Console.WriteLine("画一个圆形");
        base.Draw();
    }
}
class Rectangle : Shape
{
    public override void Draw()
    {
        Console.WriteLine("画一个长方形");
        base.Draw();
    }
}
class Triangle : Shape
{
    public override void Draw()
    {
        Console.WriteLine("画一个三角形");
        base.Draw();
    }
}

class Program
{
    static void Main(string[] args)
    {
        // 创建一个 List<Shape> 对象,并向该对象添加 Circle、Triangle 和 Rectangle
        var shapes = new List<Shape>
        {
            new Rectangle(),
            new Triangle(),
            new Circle()
        };

        // 使用 foreach 循环对该列表的派生类进行循环访问,并对其中的每个 Shape 对象调用 Draw 方法
        foreach (var shape in shapes)
        {
            shape.Draw();
        }

        Console.WriteLine("按下任意键退出。");
        Console.ReadKey();
    }

}

 当上面的代码被编译和执行时,它会产生下列结果:

画一个长方形
执行基类的画图任务
画一个三角形
执行基类的画图任务
画一个圆形
执行基类的画图任务
按下任意键退出。

下面的程序演示通过虚方法 area() 来计算不同形状图像的面积:

using System;
namespace PolymorphismApplication
{
   class Shape
   {
      protected int width, height;
      public Shape( int a=0, int b=0)
      {
         width = a;
         height = b;
      }
      public virtual int area()
      {
         Console.WriteLine("父类的面积:");
         return 0;
      }
   }
   class Rectangle: Shape
   {
      public Rectangle( int a=0, int b=0): base(a, b)
      {

      }
      public override int area ()
      {
         Console.WriteLine("Rectangle 类的面积:");
         return (width * height);
      }
   }
   class Triangle: Shape
   {
      public Triangle(int a = 0, int b = 0): base(a, b)
      {
     
      }
      public override int area()
      {
         Console.WriteLine("Triangle 类的面积:");
         return (width * height / 2);
      }
   }
   class Caller
   {
      public void CallArea(Shape sh)
      {
         int a;
         a = sh.area();
         Console.WriteLine("面积: {0}", a);
      }
   }  
   class Tester
   {
     
      static void Main(string[] args)
      {
         Caller c = new Caller();
         Rectangle r = new Rectangle(10, 7);
         Triangle t = new Triangle(10, 5);
         c.CallArea(r);
         c.CallArea(t);
         Console.ReadKey();
      }
   }
}

 当上面的代码被编译和执行时,它会产生下列结果:

Rectangle 类的面积:
面积:70
Triangle 类的面积:
面积:25

2. 实现的方式:

多态需要通过继承来实现

3. 分类:

分为编译多态(重载overload)和运行多态(重写override)。父类方法被重写了之后也可以在子类中用base.方法 调用。

4. virtual关键字:

虚方法,允不允许重写,要重写父类方法必须是虚方法:public virtual void Eat( )。

5. 运行多态实现的条件:

(1)子类对父类方法的重写(override),父类和子类中都有相同的方法。

(2)父类引用指向子类实例。

class Ren
    {
        protected string _Name;
        protected string _Country;
        public virtual void Eat()
        {
            Console.WriteLine("正在吃饭...");
        }
    }
    class American : Ren
    {
        public override void Eat()
        {
            Console.WriteLine("正在用叉子和刀子吃饭....");
        }
    }
    class Chinese : Ren
    {
        public override void Eat()
        {
            Console.WriteLine("正在用筷子吃饭...");
        }
    }

父类Ren中有个Eat方法为虚方法,在子类Chinese和American中进行了重写,在Main函数中:

Random rand = new Random();
    int n = rand.Next(100);
    Ren a;
    if (n % 2 == 0)
    {
        a = new American();
    }
    else
    {
        a = new Chinese();
    }
    a.Eat();

6. 里氏代换原则和抽象依赖原则:

里氏代换原则,如果某个方法接收的是父类引用,可以向里面传父类或其子类的元素,子类对象替代父类对象。

抽象依赖原则,用父类的引用来指向子类的实例。

例子:怪兽吃人,传入Ren的引用 r ,则r.Cry()表现出来不同的结果。

class Monster
    {
        public void EatFood(Ren r)   //r = a;
        {
            r.Cry();
            Console.WriteLine("人类真好吃,吃饱了!");
        }
    }
    class Ren   
    {
        public virtual void Cry()
        {
            Console.WriteLine(".......");
        }
    }
    class American:Ren
    {
       
        public override void Cry()
        {
              Console.WriteLine("MyGod,God bless me!");
        }
    }
    class Chinese:Ren
    {
        public override void Cry()
        {
            Console.WriteLine("天哪,老天爷保佑我!");
        }
    }

在Main函数中 ,先实例出一个怪兽,随机生成一个Ren的对象,将此对象的引用传入怪兽类里,通过这个引用来表现出不同的状态。

            Monster m = new Monster();
            Random rand = new Random();
            int n = rand.Next(100);
            if (n % 2 == 0)
            {
                American a = new American();
                //或者是这样写 Ren a = new American();

                m.EatFood(a);
            }
            else
            {
                Chinese c = new Chinese();
                //或者是这样写 Ren c = new Chinese();

                m.EatFood(c);
            }

一:抽象方法

1. 在面向对象编程语言中抽象方法指一些只有方法声明,而没有具体方法体的方法。抽象方法一般存在于抽象类或接口中。

    在一些父类中,某些行为不是非常明确,因此无法用代码来具体实现,但是类还必须具备此方法,因此,把这样的方法定义为抽象方法。

2. 声明方法:public abstract Eat(); 方法声明只是以一个分号结束,并且在签名后没有大括号,没有函数体,因为太抽象不清楚,具体的实现由各个子类中重写函数实现。

3. 它的特点:

(1) 抽象方法是隐式的 virtual 方法。

(2) 只允许在抽象类中使用抽象方法声明。

(3) 因为抽象方法只声明不提供实实现,所以没有方法体。抽象方法只在派生类中真正实现,这表明抽象方法只存放函数原型(方法的返回类型,使用的名称及参数),而不涉及主体代码。

(4) 加abstract关键词。

(5)抽象方法的目的在于指定派生类必须实现与这一方法关联的行为。

二:抽象类

1. 抽象类:无法被实例化的类。关键词是abstract,凡是带有abstract关键词的类都无法被new出来。抽象类是不完整的,它只能用作基类。在面向对象方法中,抽象类主要用来进行类型隐藏和充当全局变量的角色。

2. 声明:抽象类声明:public abstract class Ren{};

3. 注意:

(1)凡是带有抽象方法的类肯定是抽象类;抽象类却不一定包含抽象方法。

(2)构造方法,静态成员方法不能声明为抽象方法。

(3)一个非抽象类必须实现从父类继承来的所有抽象方法,如果有一个抽象方法没有实现,则此类必须要加abstract关键字。如果父类被声明为抽象类,并存在未实现的抽象方法,那么子类就必须实现父类中所有的abstract成员,除非该类也是抽象的。

4. 特征:

(1)抽象类不能实例化。

(2)一个抽象类可以同时包含抽象方法和非抽象方法。

(3)不能用sealed修饰符修饰抽象类,因为这两个修饰符的含义是相反的,采用sealed修饰符的类无法继承,而abstract修饰符要求对类进行继承。

(4)从抽象类派生的非抽象类必须包括继承的所有抽象方法和抽象访问器的实际实现。

例子:Ren类中有一个抽象方法Eat(),在其派生类Chinese和American中必须重写这个方法

abstract class Ren
    {
        protected string name;
        public abstract void Eat();
    }

    class Chinese:Ren
    {
        public override void Eat()
        {
            Console.WriteLine("用筷子吃饭");
        }
    }
    class American : Ren
    {
        public override void Eat()
        {
            Console.WriteLine("用刀叉吃饭");
        }
    }

三:接口

1. 关键字:interface,用interface 关键词来定义。

2. 概念:极度抽象的类,无成员变量,无实例属性和实例方法,只有抽象方法或抽象属性,生活中的例子:标准,规则。

3. 写法:接口不用class,用interface,名字一般以I作为首字母;不用写abstract,里面所有都是,不用写public,必须是public。

interface IUSB         //接口
    {
        void start();
        void stop();
    }

4. 特点:

(1)接口中的方法都是抽象的,因此无需加abstract修饰符。

(2)接口中的方法都是公用的,因此无需加public修饰符。

(3)接口就是一个规则标准。

(4)接口可以继承父接口。

(5)一个类可以实现(继承)多个接口。一个类只能有一个父类,但可以实现多个接口。

例子:简单的IUSB接口,里面有两个抽象方法start()和stop(),派生类实现接口必须实现接口中的所有方法。

interface IUSB         //接口
    {
        void start();
        void stop();
    }
    class UDisk : IUSB  //实现接口必须实现里面的所有方法
    {
        public void start()
        {
            Console.WriteLine("U盘启动了");
        }
        public void stop()
        {
            Console.WriteLine("U盘停止了");
        }
    }
    class Cammer : IUSB
    {

        public void start()
        {
            Console.WriteLine("摄像头启动了");
        }

        public void stop()
        {
            Console.WriteLine("摄像头关闭了");
        }
    }

    class computer
    {
        public void CheckUSB(IUSB usb)
        {
            usb.start();
        }
        public void CloseUSB(IUSB usb)
        {
            usb.stop();
        }
    }

用的时候:

computer c = new computer();
       UDisk u = new UDisk();
       Cammer m = new Cammer();

       c.CheckUSB(u); //插入U盘
       c.CheckUSB(m); //插入摄像头

       c.CloseUSB(u); //拔出U盘
       c.CloseUSB(m); //插入摄像头

接下来重点介绍一下几个关键字:

1,virtual:

virtual 关键字用于修改方法、属性、索引器或事件声明,并使它们可以在派生类中被重写。 例如,此方法可被任何继承它的类替代:

public virtual double Area()
{
    return x * y;
}

虚拟成员的实现可由派生类中的替代成员更改。

备注:

调用虚拟方法时,将为替代的成员检查该对象的运行时类型。 将调用大部分派生类中的该替代成员,如果没有派生类替代该成员,则它可能是原始成员。

默认情况下,方法是非虚拟的。 不能替代非虚方法。

virtual 修饰符不能与 staticabstract``private 或 override 修饰符一起使用。 以下示例显示了虚拟属性:

class MyBaseClass
{
    // virtual auto-implemented property. Overrides can only
    // provide specialized behavior if they implement get and set accessors.
    public virtual string Name { get; set; }

    // ordinary virtual property with backing field
    private int num;
    public virtual int Number
    {
        get { return num; }
        set { num = value; }
    }
}

class MyDerivedClass : MyBaseClass
{
    private string name;

   // Override auto-implemented property with ordinary property
   // to provide specialized accessor behavior.
    public override string Name
    {
        get
        {
            return name;
        }
        set
        {
            if (!string.IsNullOrEmpty(value))
            {
                name = value;
            }
            else
            {
                name = "Unknown";
            }
        }
    }
}

除声明和调用语法不同外,虚拟属性的行为与虚拟方法相似。

  • 在静态属性上使用 virtual 修饰符是错误的。

  • 通过包括使用 override 修饰符的属性声明,可在派生类中替代虚拟继承属性。

2,override:

扩展或修改继承的方法、属性、索引器或事件的抽象或虚拟实现需要 override 修饰符。

在以下示例中,Square 类必须提供 GetArea 的重写实现,因为 GetArea 继承自抽象 Shape 类:

abstract class Shape
{
    public abstract int GetArea();
}

class Square : Shape
{
    int side;

    public Square(int n) => side = n;

    // GetArea method is required to avoid a compile-time error.
    public override int GetArea() => side * side;

    static void Main()
    {
        var sq = new Square(12);
        Console.WriteLine($"Area of the square = {sq.GetArea()}");
    }
}
// Output: Area of the square = 144

override 方法提供从基类继承的方法的新实现。 通过 override 声明重写的方法称为重写基方法。 override 方法必须具有与重写基方法相同的签名。

不能重写非虚方法或静态方法。 重写基方法必须是 virtualabstract 或 override

override 声明不能更改 virtual 方法的可访问性。 override 方法和 virtual 方法必须具有相同级别访问修饰符

不能使用 newstatic 或 virtual 修饰符修改 override 方法。

重写属性声明必须指定与继承的属性完全相同的访问修饰符、类型和名称。 ,只读重写属性支持协变返回类型。 重写属性必须为 virtualabstract 或 override

示例:

定义一个名为 Employee 的基类和一个名为 SalesEmployee 的派生类。 SalesEmployee 类包含一个额外字段 salesbonus,并且重写方法 CalculatePay 以将它考虑在内。

class TestOverride
{
    public class Employee
    {
        public string name;

        // Basepay is defined as protected, so that it may be
        // accessed only by this class and derived classes.
        protected decimal basepay;

        // Constructor to set the name and basepay values.
        public Employee(string name, decimal basepay)
        {
            this.name = name;
            this.basepay = basepay;
        }

        // Declared virtual so it can be overridden.
        public virtual decimal CalculatePay()
        {
            return basepay;
        }
    }

    // Derive a new class from Employee.
    public class SalesEmployee : Employee
    {
        // New field that will affect the base pay.
        private decimal salesbonus;

        // The constructor calls the base-class version, and
        // initializes the salesbonus field.
        public SalesEmployee(string name, decimal basepay,
                  decimal salesbonus) : base(name, basepay)
        {
            this.salesbonus = salesbonus;
        }

        // Override the CalculatePay method
        // to take bonus into account.
        public override decimal CalculatePay()
        {
            return basepay + salesbonus;
        }
    }

    static void Main()
    {
        // Create some new employees.
        var employee1 = new SalesEmployee("Alice",
                      1000, 500);
        var employee2 = new Employee("Bob", 1200);

        Console.WriteLine($"Employee1 {employee1.name} earned: {employee1.CalculatePay()}");
        Console.WriteLine($"Employee2 {employee2.name} earned: {employee2.CalculatePay()}");
    }
}
/*
    Output:
    Employee1 Alice earned: 1500
    Employee2 Bob earned: 1200
*/

3,sealed:

应用于某个类时,sealed 修饰符可阻止其他类继承自该类。

还可以对替代基类中的虚方法或属性的方法或属性使用 sealed 修饰符。 这使你可以允许类派生自你的类并防止它们替代特定虚方法或属性。

实例:

在下面的示例中,Z 继承自 Y,但 Z 无法替代在 X 中声明并在 Y 中密封的虚函数 F

class X
{
    protected virtual void F() { Console.WriteLine("X.F"); }
    protected virtual void F2() { Console.WriteLine("X.F2"); }
}

class Y : X
{
    sealed protected override void F() { Console.WriteLine("Y.F"); }
    protected override void F2() { Console.WriteLine("Y.F2"); }
}

class Z : Y
{
    // Attempting to override F causes compiler error CS0239.
    // protected override void F() { Console.WriteLine("Z.F"); }

    // Overriding F2 is allowed.
    protected override void F2() { Console.WriteLine("Z.F2"); }
}

在类中定义新方法或属性时,可以通过不将它们声明为虚拟,来防止派生类替代它们。

将 abstract 修饰符与密封类结合使用是错误的,因为抽象类必须由提供抽象方法或属性的实现的类来继承。

应用于方法或属性时,sealed 修饰符必须始终与 override 结合使用。

因为结构是隐式密封的,所以无法继承它们。

若要确定是否密封类、方法或属性,通常应考虑以下两点:

  • 派生类通过可以自定义类而可能获得的潜在好处。

  • 派生类可能采用使它们无法再正常工作或按预期工作的方式来修改类的可能性。

  • 2
    点赞
  • 16
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值