C# 系列 -- 类相关

349 阅读17分钟

类相关

类 Class

当你定义一个类时,它定义了类的对象 由什么组成 及在这个对象上 可执行什么操作。对象是类的 实例。构成类的 方法和变量 称为类的 成员

类的定义

类的定义是以关键字 class 开始,后跟类的名称。类的主体,包含在一对花括号内。下面是类定义的一般形式:

<access specifier> class  class_name  
{  
    // 成员变量 
    <access specifier> <data type> variable1;  
    <access specifier> <data type> variable2;  
    ...  
    <access specifier> <data type> variableN;  
    // 成员方法 
    <access specifier> <return type> method1(parameter_list)  
    {  
        // method1 内容  
    }  
    <access specifier> <return type> method2(parameter_list)  
    {  
        // method2 内容   
    }  
    ... 
}  

请注意:

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

下面的实例说明了目前为止所讨论的概念:

using System;  
namespace BoxApplication  
{  
    class Box  
    {  
       public double length;   // 长度  
       public double breadth;  // 宽度  
       public double height;   // 高度  
    }  
    class Boxtester  
    {  
        static void Main(string[] args)  
        {  
            Box Box1 = new Box();        // 声明 Box1,类型为 Box
            double volume = 0.0;         // 体积  
  
            // Box1 详述  
            Box1.height = 5.0;  
            Box1.length = 6.0;  
            Box1.breadth = 7.0; 
             
            // Box1 的体积  
            volume = Box1.height * Box1.length * Box1.breadth;  
            Console.WriteLine("Box1 的体积: {0}",  volume);  
  
            // ... BoxN 同上
            Console.ReadKey();  
        }  
    }  
}  

运行结果:

Box1 的体积: 210
BoxN 的体积: ...

成员函数和封装

类的成员函数是一个在类定义中有它的定义或原型的函数,就像其他变量一样。作为类的一个成员,它能在类的任何对象上操作,且能访问该对象的类的所有成员。

成员变量是对象的属性(从设计角度),且它们保持私有来实现封装。这些变量只能使用公共成员函数来访问。

让我们使用上面的概念来设置和获取一个类中不同的类成员的值:

using System;  
namespace BoxApplication  
{  
    class Box  
    {  
       private double length;   // 长度  
       private double breadth;  // 宽度  
       private double height;   // 高度  
       public void setLength( double len )  
       {  
            length = len;  
       }
       public void setBreadth( double bre )  
       {  
            breadth = bre;  
       }
       public void setHeight( double hei )  
       {  
            height = hei;  
       }  
       public double getVolume()  
       {  
           return length * breadth * height;  
       }  
    }  
    class Boxtester  
    {  
        static void Main(string[] args)  
        {  
            Box Box1 = new Box();        // 声明 Box1,类型为 Box  
            Box Box2 = new Box();        // 声明 Box2,类型为 Box  
            double volume;               // 体积  
  
  
            // Box1 详述  
            Box1.setLength(6.0);  
            Box1.setBreadth(7.0);  
            Box1.setHeight(5.0);  
  
            // Box2 详述  
            Box2.setLength(12.0);  
            Box2.setBreadth(13.0);  
            Box2.setHeight(10.0);  
         
            // Box1 的体积  
            volume = Box1.getVolume();  
            Console.WriteLine("Box1 的体积: {0}" ,volume);  
  
            // Box2 的体积  
            volume = Box2.getVolume();  
            Console.WriteLine("Box2 的体积: {0}", volume);  
             
            Console.ReadKey();  
        }  
    }  
}  

运行结果:

Box1 的体积: 210
Box2 的体积: 1560

C# 中的构造函数

类的构造函数是类的一个【特殊的成员函数】,当创建类的新对象时执行。构造函数的名称 与类的名称完全相同,它没有返回任何类型。

下面的实例体现【创建类的新对象时构造函数会执行】:

using System;  
namespace LineApplication  
{  
   class Line  
   {  
      private double length;   // 线条的长度  
      public Line()  
      {  
         Console.WriteLine("对象已创建");  
      }
      public void setLength( double len )  
      {  
         length = len;  
      }  
      public double getLength()  
      {  
         return length;  
      }  
  
      static void Main(string[] args)  
      {  
         Line line = new Line();      
         // 设置线条长度  
         line.setLength(6.0);  
         Console.WriteLine("线条的长度: {0}", line.getLength());  
         Console.ReadKey();  
      }  
   }  
}  

运行结果:

对象已创建
线条的长度: 6

构造函数的应用场景包括:

  • 在创建对象时,需要初始化对象的状态
  • 为对象分配所需的内存空间
  • 在对象创建时执行一些初始化操作,例如打开文件、连接数据库
  • 在子类中重写基类的构造函数,以便在创建子类对象时初始化基类的状态

由于默认的构造函数没有任何参数,如果需要一个带有参数的构造函数可以有参数,这种构造函数叫做参数化构造函数。这种技术可以帮助你在创建对象的同时给对象赋初始值,具体请看下面实例:

struct Person {
    public string name;
    public int age;
    public bool isStudent;
    public string address;
}

public class PersonInfo
{
    public string name;
    public int age;
    public bool isStudent;
    public string address;

    public PersonInfo(Person per)
    {
        name = per.name;
        age = per.age;
        isStudent = per.isStudent;
        address = per.address;
    }
}

PersonInfo p = new PersonInfo(new Person {
    name = "Tom",
    age = 20,
    isStudent = true,
    address = "China"
});
Console.WriteLine(p.name);
Console.WriteLine(p.age);
Console.WriteLine(p.isStudent);
Console.WriteLine(p.address);

运行结果:

Tom
20
true
China

C# 中的析构函数

类的析构函数也是类的一个【特殊的成员函数】,当类的对象超出范围时执行。析构函数的名称是在类的名称前加上一个 波浪形(~)作为前缀,它不返回值,也不带任何参数。

下面的实例体现【在对象被销毁时析构函数执行】:

using System;  
namespace LineApplication  
{  
   class Line  
   {  
      private double length;   // 线条的长度  
      public Line()  // 构造函数  
      {  
         Console.WriteLine("对象已创建");  
      }  
      ~Line() //析构函数  
      {  
         Console.WriteLine("对象已删除");  
      }  
  
      public void setLength( double len )  
      {  
         length = len;  
      }  
      public double getLength()  
      {  
         return length;  
      }  
  
      static void Main(string[] args)  
      {  
         Line line = new Line();  
         // 设置线条长度  
         line.setLength(6.0);  
         Console.WriteLine("线条的长度: {0}", line.getLength());            
      }  
   }  
}  

运行结果:

对象已创建
线条的长度: 6
对象已删除

析构函数的应用场景包括:

  • 在对象被销毁时执行一些清理操作,例如关闭文件、释放资源
  • 在对象销毁时释放对象所占用的内存空间

需要注意的是,C# 中的垃圾回收机制会自动释放对象所占用的内存空间,因此在大多数情况下,析构函数不是必须的。如果您需要执行一些清理操作,可以使用 IDisposable 接口和 using 语句来实现。例如:

public class MyClass : IDisposable
{
    private FileStream _file;

    public MyClass(string fileName)
    {
        _file = new FileStream(fileName, FileMode.Open);
    }

    public void Dispose()
    {
        _file.Dispose();
    }
}

using (MyClass obj = new MyClass("test.txt"))
{
    // 使用 obj 对象
}

在上面的示例中,我们实现了 IDisposable 接口,并在 Dispose 方法中释放了 _file 对象所占用的资源。然后,我们使用 using 语句创建了一个 MyClass 对象,并在 using 语句结束时自动调用了 Dispose 方法。

C# 类的静态成员

我们可以使用 static 关键字把类成员定义为静态的。当我们声明一个类成员为静态时,意味着无论有多少个类的对象被创建,只会有一个该静态成员的副本。

在 C# 中,静态变量和静态成员是与类本身相关联的,而不是与类的实例相关联。它们在类的所有实例之间共享,而不是为每个实例单独存储。以下是静态变量和静态成员的一些应用场景:

静态变量

静态变量用于存储与类相关的数据,而不是与类的实例相关的数据。静态变量在所有实例之间共享,因此对一个实例所做的更改会影响其他实例。以下是静态变量的一个应用场景:

public class Counter
{
    public static int count = 0;

    public Counter()
    {
        count++;
    }
}

Counter c1 = new Counter();
Counter c2 = new Counter();
Console.WriteLine(Counter.count); // 输出 2

在上面的示例中,我们使用静态变量 count 来记录 Counter 类的实例数量。每次创建一个新的 Counter 实例时,count 变量都会增加。因为 count 变量是静态的,所以它在所有实例之间共享。

静态方法

静态方法用于执行与类相关的操作,而不是与类的实例相关的操作。静态方法可以直接通过类名调用,无需创建类的实例。以下是静态方法的一个应用场景:

public class MathHelper
{
    public static int Add(int a, int b)
    {
        return a + b;
    }
}

int sum = MathHelper.Add(1, 2);

在上面的示例中,我们创建了一个名为 MathHelper 的类,其中包含一个静态方法 Add。这个方法用于计算两个整数的和。因为 Add 方法是静态的,所以我们可以直接通过类名调用它,无需创建 MathHelper 类的实例。

总之,静态变量和静态成员在以下场景中很有用:

  • 当需要在类的所有实例之间共享数据时,可以使用静态变量。
  • 当需要执行与类相关的操作,而不是与类的实例相关的操作时,可以使用静态方法。
  • 当需要提供一组与类本身相关的工具方法时,可以使用静态方法。如 String.Concat(str1, str2)等

封装

封装 被定义为"把一个或多个项目封闭在一个物理的或者逻辑的包中"。在面向对象程序设计方法论中,封装是为了防止对实现细节的访问。

抽象和封装是面向对象程序设计的相关特性。抽象允许相关信息可视化,封装则使开发者实现所需级别的抽象

C# 封装根据具体的需要,设置使用者的访问权限,并通过 访问修饰符 来实现。

一个 访问修饰符 定义了一个类成员的范围和可见性。C# 支持的访问修饰符如下所示:

  • Pubilc :任何公有成员可以被外部的类访问。
  • Private :只有同一个类中的函数可以访问它的私有成员。
  • Protected :该类内部和继承类中可以访问。
  • internal : 同一个程序集的对象可以访问。
  • Protected internal :3 和 4 的并集,符合任意一条都可以访问。

范围比较:

public > protected internal > internal/protected > private

形象比喻:

假设:一个人A为父类,他的儿子B,妻子C,私生子D(注:D不在他家里)

如果我们给A的事情增加修饰符:

  • public事件,地球人都知道,全公开
  • protected internal事件,A,B,C,D都知道,其它人不知道(即 protected、internal 的并集)
  • protected事件,A,B,D知道(A和他的所有儿子知道,妻子C不知道)
  • internal事件,A,B,C知道(A家里人都知道,私生子D不知道)
  • private事件,只有A知道(隐私?心事?)

Public 访问修饰符

Public 访问修饰符允许一个类将其成员变量和成员函数暴露给其他的函数和对象。任何公有成员可以被外部的类访问。

下面的实例说明了这点:

using System;  
  
namespace RectangleApplication  
{  
    class Rectangle  
    {  
        // 成员变量  
        public double length;  
        public double width;  
  
        public double GetArea()  
        {  
            return length * width;  
        }  
        public void Display()  
        {  
            Console.WriteLine("长度: {0}", length);  
            Console.WriteLine("宽度: {0}", width);  
            Console.WriteLine("面积: {0}", GetArea());  
        }  
    }
  
    class ExecuteRectangle  
    {  
        static void Main(string[] args)  
        {  
            Rectangle r = new Rectangle();  
            r.length = 4.5;  
            r.width = 3.5;  
            r.Display();  
            Console.ReadLine();  
        }  
    }  
}

运行结果:

长度: 4.5
宽度: 3.5
面积: 15.75

在上面的实例中,成员变量 length 和 width 被声明为 public,所以它们可以被函数 Main() 使用 Rectangle 类的实例 r 访问。

成员函数 Display()  和 GetArea()  可以直接访问这些变量。

成员函数 Display()  也被声明为 public,所以它也能被 Main()  使用 Rectangle 类的实例 r 访问。

Protected Internal 访问修饰符

Protected Internal 访问修饰符允许在本类,派生类或者包含该类的程序集中访问。这也被用于实现继承。

Internal 访问修饰符

Internal 访问修饰符允许一个类将其成员变量和成员函数暴露给当前程序中的其他函数和对象。换句话说,带有 internal 访问修饰符的任何成员可以被定义在该成员所定义的应用程序内的任何类或方法访问。

下面的实例说明了这点:

using System;  
  
namespace RectangleApplication  
{  
    class Rectangle  
    {  
        // 成员变量  
        internal double length;  
        internal double width;  
         
        double GetArea()  
        {  
            return length * width;  
        }  
        public void Display()  
        {  
            Console.WriteLine("长度: {0}", length);  
            Console.WriteLine("宽度: {0}", width);  
            Console.WriteLine("面积: {0}", GetArea());  
        }  
    }     
    class ExecuteRectangle  
    {  
        static void Main(string[] args)  
        {  
            Rectangle r = new Rectangle();  
            r.length = 4.5;  
            r.width = 3.5;  
            r.Display();  
            Console.ReadLine();  
        }  
    }  
}

运行结果:

长度: 4.5
宽度: 3.5
面积: 15.75

在上面的实例中,请注意成员函数 GetArea()  声明的时候不带有任何访问修饰符。如果没有指定访问修饰符,则使用类成员的默认访问修饰符,即为 private

Protected 访问修饰符

Protected 访问修饰符允许子类访问它的基类的成员变量和成员函数。这样有助于实现继承。我们将在继承的章节详细讨论这个。更详细地讨论这个。

xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx

Private 访问修饰符

Private 访问修饰符允许一个类将其成员变量和成员函数对其他的函数和对象进行隐藏。只有同一个类中的函数可以访问它的私有成员。即使是类的实例也不能访问它的私有成员。

下面的实例说明了这点:

using System;  
  
namespace RectangleApplication  
{  
    class Rectangle  
    {  
        // 成员变量  
        private double length;  
        private double width;  
  
        public void Acceptdetails()  
        {  
            Console.WriteLine("请输入长度:");  
            length = Convert.ToDouble(Console.ReadLine());  
            Console.WriteLine("请输入宽度:");  
            width = Convert.ToDouble(Console.ReadLine());  
        }  
        public double GetArea()  
        {  
            return length * width;  
        }  
        public void Display()  
        {  
            Console.WriteLine("长度: {0}", length);  
            Console.WriteLine("宽度: {0}", width);  
            Console.WriteLine("面积: {0}", GetArea());  
        }  
    }     
    class ExecuteRectangle  
    {  
        static void Main(string[] args)  
        {  
            Rectangle r = new Rectangle();  
            r.Acceptdetails();  
            r.Display();  
            Console.ReadLine();  
        }  
    }  
}  

运行结果:

请输入长度:
4.4
请输入宽度:
3.3
长度: 4.4
宽度: 3.3
面积: 14.52

在上面的实例中,成员变量 length 和 width 被声明为 private,成员函数 AcceptDetails()  和 Display()  可以访问 这些变量,但 不能 被函数 Main() 访问

由于成员函数 AcceptDetails()  和 Display()  被声明为 public,所以它们可以被 Main()  使用 Rectangle 类的实例 r 访问。


继承

C# 中的继承允许一个类从另一个类继承成员。这有助于实现代码重用和多态,提高了代码的可读性和可维护性。

继承思想,例如:长方形、正方形、平行四边形属于四边形;牛羊狗属于哺乳类动物。

基类和派生类

假设,有一个基类 Shape,它的派生类是 Rectangle,其中使用 : 关键字表示继承:

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;
   }

   // 派生类
   class Rectangle: Shape
   {
      public int getArea()
      { 
         return (width * height); 
      }
   }
   
   class RectangleTester
   {
      static void Main(string[] args)
      {
         Rectangle Rect = new Rectangle();

         Rect.setWidth(5);
         Rect.setHeight(7);

         // 打印对象的面积
         Console.WriteLine("总面积: {0}",  Rect.getArea());
         Console.ReadKey();
      }
   }
}

运行结果:

总面积: 35

方法重写覆盖

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());
      }
   }  
   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(); // 调用基类 Rectangle 的 Display 方法
         Console.WriteLine("成本: {0}", GetCost());
      }
   }
   class ExecuteRectangle
   {
      static void Main(string[] args)
      {
         Tabletop t = new Tabletop(4.5, 7.5);
         t.Display();
         Console.ReadLine();
      }
   }
}

以上代码中 base 关键字代表派生类 Tabletop 的基类(父类)Rectanglebase 关键字允许我们在派生类中访问基类的成员。在 Tabletop 类的 Display 方法中,base.Display(); 语句表示调用类 Rectangle 的 Display 方法。

这样做的目的是:

  1. 在派生类 Tabletop 的 Display 方法中保留基类 Rectangle 的 Display 方法的功能
  2. 同时在 Tabletop 类中添加新的功能(在这里是输出成本)。

运行结果:

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

实现多重继承

在 C# 中,类不支持多重继承,即一个类不能直接从多个基类继承。然而,可以使用接口 interface 来实现类似多重继承的功能。一个类可以实现多个接口,从而继承多个接口的成员。

以下是一个使用接口实现多重继承的示例:

public interface IFirstInterface
{
    void MethodA();
}

public interface ISecondInterface
{
    void MethodB();
}

public class MyClass : IFirstInterface, ISecondInterface
{
    public void MethodA()
    {
        Console.WriteLine("MethodA from IFirstInterface");
    }

    public void MethodB()
    {
        Console.WriteLine("MethodB from ISecondInterface");
    }
}

class Program
{
    static void Main(string[] args)
    {
        MyClass obj = new MyClass();
        obj.MethodA(); // 输出 "MethodA from IFirstInterface"
        obj.MethodB(); // 输出 "MethodB from ISecondInterface"
    }
}

在上面的示例中,我们定义了两个接口 IFirstInterface 和 ISecondInterface,分别包含一个方法 MethodA 和 MethodB。然后,我们创建了一个名为 MyClass 的类,它实现了这两个接口。这样,MyClass 类就继承了这两个接口的成员,实现了类似多重继承的功能。

多态性

静态多态性

运算符重载

应用场景:向量运算

using System;

public struct Vector2
{
    public float x;
    public float y;
    // 构造函数初始化赋值向量
    public Vector2(float x, float y)
    {
        this.x = x;
        this.y = y;
    }
    // 向量求和
    public static Vector2 operator +(Vector2 a, Vector2 b)
    {
        return new Vector2(a.x + b.x, a.y + b.y);
    }
    // 向量求差
    public static Vector2 operator -(Vector2 a, Vector2 b)
    {
        return new Vector2(a.x - b.x, a.y - b.y);
    }
    // 向量乘常数
    public static Vector2 operator *(Vector2 a, float scalar)
    {
        return new Vector2(a.x * scalar, a.y * scalar);
    }
    // 以直观格式输出向量
    public override string ToString()
    {
        return $"({x}, {y})";
    }
}

class Program
{
    static void Main(string[] args)
    {
        Vector2 vec1 = new Vector2(1, 2);
        Vector2 vec2 = new Vector2(3, 4);

        Vector2 sum = vec1 + vec2;
        Console.WriteLine($"Sum: {sum}"); // 输出 "两向量之和: (4, 6)"

        Vector2 difference = vec1 - vec2;
        Console.WriteLine($"Difference: {difference}"); // 输出 "两向量之差: (-2, -2)"

        Vector2 product = vec1 * 2;
        Console.WriteLine($"Product: {product}"); // 输出 "向量1 的 2 倍: (2, 4)"
    }
}

函数重载

一个类里有多个 相同名字参数的个数/类型不同 的成员函数,实例中调用这些同名函数时会根据参数的不同输出执行不同的函数

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;  
        }  
    }
    public class Printdata
    {
        public void print(int i)  
        {  
            Console.WriteLine("输出整型: {0}", i );  
        }
        public void print(double f)  
        {  
            Console.WriteLine("输出浮点型: {0}" , f);  
        }
        public void print(string s)  
        {  
            Console.WriteLine("输出字符串: {0}", s);  
        }
    }
    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);
            
            Printdata p = new Printdata();
            p.print(1); // 调用 print 来打印整数
            p.print(1.23); // 调用 print 来打印浮点数
            p.print("Hello Runoob"); // 调用 print 来打印字符串
        }  
    }  
}

动态多态性

abstract 抽象类 是一种特殊类型的类它,不能被实例化,其主要目的是为其他类 提供一个基类,并定义这些派生类应该实现的 公共接口。抽象类可以包含抽象成员(如抽象方法、抽象属性等)和非抽象成员。

  • abstract 创建【抽象类】
  • abstract 创建 必须 被重写的【抽象方法】
  • virtual 创建可以(非必须)被重写的【虚方法】
  • override 重写抽象类中抽象方法

实例:创建一个名为 GameCharacter 的抽象基类,表示游戏中的角色。这个类包含一个抽象方法 PerformAction,用于执行角色的行为。

public abstract class GameCharacter
{
    public string Name { get; set; }

    public GameCharacter(string name)
    {
        Name = name;
    }

    public abstract void PerformAction(); // 必须被重写
    public virtual void PerformAction(); // 可以被重写,也可以不被重写
}

然后,我们创建两个派生类 PlayerCharacter 和 NonPlayerCharacter,分别表示玩家角色和非玩家角色。这两个派生类重写了基类的 PerformAction 方法,为它们提供了不同的实现。

public class PlayerCharacter : GameCharacter
{
    public PlayerCharacter(string name) : base(name) { }

    public override void PerformAction()
    {
        Console.WriteLine($"{Name}是一个玩家角色");
    }
}

public class NonPlayerCharacter : GameCharacter
{
    public NonPlayerCharacter(string name) : base(name) { }

    public override void PerformAction()
    {
        Console.WriteLine($"{Name}是一个 NPC 角色");
    }
}

在游戏客户端中,我们可以使用多态性来创建和管理游戏角色。例如,我们可以创建一个 GameCharacter 类型的列表,用于存储玩家角色和非玩家角色。然后,我们可以遍历列表并调用每个角色的 PerformAction 方法,执行相应的行为。

using System;
using System.Collections.Generic;

class Program
{
    static void Main(string[] args)
    {
        List<GameCharacter> characters = new List<GameCharacter>
        {
            new PlayerCharacter("Player1"),
            new NonPlayerCharacter("NPC1"),
            new PlayerCharacter("Player2"),
            new NonPlayerCharacter("NPC2")
        };

        foreach (var character in characters)
        {
            character.PerformAction();
        }
    }
}

运行结果:

Player1是一个真实玩家角色
NPC1是一个NPC角色
Player2是一个真实玩家角色
NPC2是一个NPC角色

接口 Interface

  • 通常接口命令以 I 字母开头
  • 接口(interface)可以帮助我们更灵活地组织代码、实现代码重用和提高可维护性

场景:玩家角色和非玩家角色有共同的行为,又有各自特定的行为

解决:共同的行为用基类写,特定的行为用接口写

首先,我们创建一个名为 BaseCharacter 的基类,表示游戏中的角色。这个基类包含一个方法 CommonAction,用于执行角色的共同行为。

public abstract class BaseCharacter
{
    public void CommonAction()
    {
        Console.WriteLine("Performing a common action.");
    }
}

接下来,我们创建一个名为 ICharacter 的接口,表示游戏中的角色。这个接口包含一个方法 PerformAction,用于执行角色的特定行为。

public interface ICharacter
{
    void PerformAction();
}

然后,我们创建两个继承自 BaseCharacter 类并实现了 ICharacter 接口的类:PlayerCharacter 和 NonPlayerCharacter,分别表示玩家角色和非玩家角色。这两个类实现了 ICharacter 接口的 PerformAction 方法,为它们提供了不同的实现。

public class PlayerCharacter : BaseCharacter, ICharacter
{
    public void PerformAction()
    {
        Console.WriteLine("Performing a player action.");
    }
}

public class NonPlayerCharacter : BaseCharacter, ICharacter
{
    public void PerformAction()
    {
        Console.WriteLine("Performing a non-player action.");
    }
}

在游戏客户端中,我们可以创建玩家角色和非玩家角色的对象,并调用它们的共同行为和特定行为。

using System;
using System.Collections.Generic;

class Program
{
    static void Main(string[] args)
    {
        List<ICharacter> characters = new List<ICharacter>
        {
            new PlayerCharacter(),
            new NonPlayerCharacter(),
            new PlayerCharacter(),
            new NonPlayerCharacter()
        };

        foreach (var character in characters)
        {
            character.PerformAction(); // 执行特定行为
            (character as BaseCharacter).CommonAction(); // 执行共同行为
        }
    }
}

类和结构体的区别

  • 相同点:两者都可以在里面写简单类型变量和方法
  • 不同点:
    1. 类型不同:类是引用类型,结构体是值类型
    2. 实例化:类可以通过 new 实例化,结构体不可以
    3. 内存位置不同:类在堆中,结构体在值中