【C#】【类】

51 阅读7分钟

类的创建

类似JAVA,注意 required这个修饰符,要求new 对象时必须设置这些参数,同时可以允许调用者设置或使用该属性。

//创建类对象
Customer customer1 = new Customer(){Name = "biko",GoodsCount = 5};
Customer customer2 = new Customer(){Name = "duckyo",GoodsCount = 3};
Console.WriteLine(customer1.Name + customer1.GoodsCount);
Console.WriteLine(customer2.Name + customer2.GoodsCount);

//类的声明
public class Customer{
    private string _name = "";
    private int _goodsCount = 0;

    //类似JAVA中 getter  setter 函数,如果不设置,则Customer对象无法获取Name GoodsCounts变量
    public required string Name { get; set;}   //要求调用方必须将这些属性设置未new表达式的一部分
    public required int GoodsCount{ get; set;}

    //构造函数
    public Customer(string name, int goodsCount)
    {
        _name = name;
        _goodsCount = goodsCount;
    }
    public Customer(){}

}

类的继承

sealed 修饰的类无法被继承。 接口不支持继承,但它们可以实现接口。

//如何使用基类和派生类
WorkItem item = new WorkItem("Fix bugs", "Fix all bugs in my code branch", new TimeSpan(3, 4, 0,0));
ChangeRequest change = new ChangeRequest("Change Base Class Design", "Add members to teh class", new TimeSpan(4,0,0),1);

Console.WriteLine(item.ToString());

public class WorkItem
{
    private static int currentID;

    protected int ID{ get; set; }
    protected string Title{ get; set; }
    protected string Description{ get; set; }

    protected TimeSpan jobLength { get; set; }

    //构造函数
    public WorkItem()
    {
        ID = 0;
        Title = "Default Title";
        Description = "Default Description";
        jobLength = new TimeSpan();
    }
    public WorkItem(string title, string description, TimeSpan jobLength)
    {
        this.ID = GetNextID();
        this.Title = title;
        this.Description = description;
        this.jobLength = jobLength;
    }

    //静态构造函数,初始化currentID的值
    static WorkItem() => currentID = 0;

    protected int GetNextID() => ++currentID;

    public void Update(string title,TimeSpan jobLength)
    {
        this.Title = title;
        this.jobLength = jobLength;
    }

    //重写虚函数
    public override string ToString() =>
        $"{this.ID} - {this.Title}";
}

//继承
public class ChangeRequest : WorkItem
{
    //添加一个新属性
    protected int originalItemID { get; set; }

    //构造函数
    public ChangeRequest(){}
    public ChangeRequest(string title,string desc, TimeSpan jobLen, int originalItemID)
    {
        this.ID = GetNextID();
        this.Title= title;
        this.Description= desc;
        this.originalItemID = originalItemID;
        this.jobLength = jobLen;
    }
}

抽象方法和虚方法

  • 当基类将方法声明为virtual时(基类可以定义并实现虚方法),派生类可以使用其自己的实现override该方法。
  • 如果基类将成员声明为abstract,则必须在直接继承自该类的任何非抽象类中重写该方法。
  • 如果派生类本身是抽象的,则它会继承抽象成员而不会实现它们。
var shapes = new List<Shape>{
    new Rectangle(),
    new Circle()
};

foreach (var shape in shapes)
{
    shape.Draw();
}

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("Performing base class drawing tasks");
    }
}

public class Circle : Shape
{
    public override void Draw()
    {
        Console.WriteLine("Drawing a circle");
        base.Draw();       //调用基类的方法
    }
}

public class Rectangle : Shape
{
    public override void Draw()
    {
        Console.WriteLine("Drawing a rectangle");
        base.Draw();
    }
}

多形性概述

通过虚拟方法,派生类可以选择不同的派生行为:

  1. 派生类可以重写基类中的虚拟成员,并定义新的行为:

    仅当基类成员声明为virtualabstract时,派生类才能重写基类成员,派生成员必须使用override关键字显式指示该方法将参与虚调用。实例参见上边代码。

  2. 派生类可能会继承基类的方法而不重写,保留现有的行为,但允许进一步派生的类重写方法;

  3. 派生类可以定义隐藏基类实现的成员的新非虚实现。

    如果希望派生类具有与基类中成员同名的成员,则可以使用new关键字隐藏基类成员。new关键字放置在要替换的类成员的返回类型之前,通过将派生类的实例强制转换为基类的实例,可以从客户端代码访问隐藏的基类成员。

    public class BaseClass { public void DoWork() { WorkField++; } public int WorkField; public int WorkProperty { get { return 0; } } } public class DerivedClass : BaseClass { public new void DoWork() { WorkField++; } public new int WorkField; public new int WorkProperty { get { return 0; } } }
    
  4. newoverride的区别:二者的区别主要在于,将派生类实例强制转为基类的实例,对于new修饰的方法,此时则会访问基类的方法,但对于override修饰的方法,则一直会访问派生类中的方法。

  5. 如果类A声明了一个虚拟成员,类B从A派生,类C从类B派生,不管类B是否为虚拟成员声明重写,类C都会继承该虚拟成员,并可以重写它。可以使用sealed关键字停止虚拟继承。

D itemD = new D();
itemD.DoWork();
B itemB = itemD;
itemB.DoWork();
public class A
{
    public virtual void DoWork()
    {
        Console.WriteLine("Class A is doing sth");
    }
}

public class B : A
{
    public override void DoWork()
    {
        Console.WriteLine("Class B is doing sth");
    }
}

public class C : B
{
    public sealed override void DoWork()
    {
         Console.WriteLine("Class C is doing sth");
    }
    //如果不希望从C派生出来的类,依然继承该虚拟方法,则可以:
    // public  sealed override void DoWork(){}
}

public class D : C
{
     public new void DoWork()
    {
         Console.WriteLine("Class D is doing sth");
    } 
}

//如果D中方法为new,输出 D C;
//如果D没有new 该方法,输出 C C;

  1. 可以使用base关键字访问基类的该方法或者该属性。

抽象类

  • 使用abstract修饰的类为抽象类,抽象类不能被实例化,主要作用是提供一个可供多个派生类共享的通用基类定义。
  • abstract也可用于修饰方法,抽象方法没有实现,方法定义后边是分号,而不是常规的方法块。抽象类的派生类必须实现所有抽象方法。当抽象类从基类继承虚方法的时候,抽象类可以使用抽象方法重新给该虚方法:
public class A
{
    public virtual void DoWork(int i){
        Console.WriteLine("class A is doing sth");
    }
}

public abstract class B : A
{
    public abstract override void DoWork(int i);
}

public class C : B
{
    public override void DoWork(int i){
        Console.WriteLine("class C is doing sth");
    }
}

分部类和方法

每个源文件包含类型或方法定义的一部分,编译应用程序时将把所有部分组合起来。

分部类

在以下几种情况下需要拆分类定义:

  • 通过单独的文件声明某个类可以让多位程序员同时对该类进行处理。
  • 你可以向该类中添加代码,而不必重新创建包括自动生成的源代码的源文件。 Visual Studio 在创建Windows 窗体、Web 服务包装器代码等时会使用这种方法。 你可以创建使用这些类的代码,这样就不需要修改由Visual Studio生成的文件。
  • 源代码生成器可以在类中生成额外的功能。

拆分类,需要使用partial关键字修饰符:

public partial class Employee
 {
    public void DoWork()
    {

    }
 }

 public partial class Employee
 {
    public void GoToLunch()
    {
        
    }
 }
  • partial关键字修饰的分部类,各个部分都必须具有相同的可访问性,如public,private等。
  • 如果任意部分声明为抽象abstract,则整个类型都被视为抽象的;
  • 如果任意部分声明为密封sealed,则整个类型都被视为密封的;
  • 如果任意部分声明基类型,则整个类型都将继承该类。指定基类的所有部分必须一致,但忽略基类的部分仍然继承该基类。
  • 嵌套类型也是可以分部的。即便它们所嵌套于的类型本身并不是分部的也可以。
class Container 
{ 
    partial class Nested 
      { 
        void Test() { } 
      }
    partial class Nested 
     { 
         void Test2() { } 
     } 
}

一些限制

  1. 要作为同一类型的各个部分的所有分部类型定义都必须使用 partial 进行修饰。
  2. partial 修饰符只能出现在紧靠关键字 classstructinterface前面的位置。
  3. 分部定义不能跨越多个模块,必须都在同一程序集和同一模块(.exe/.dll中)进行定义。
  4. 类名和泛型类型参数在所有的分部类型定义中都必须匹配。 泛型类型可以是分部的。 每个分部声明都必须以相同的顺序使用相同的参数名。

分部方法

分部类或结构可以包含分部方法。 类的一个部分包含方法的签名。 可以在同一部分或另 一部分中定义实现。

当签名遵循以下规则时,分部方法不需要实现:

  • 声明未包含任何访问修饰符。 默认情况下,该方法具有 private 访问权限。
  • 返回类型为 void。
  • 没有任何参数具有 out 修饰符。
  • 方法声明不能包括以下任何修饰符:virtual,override,sealed,new,extern.
  • 当未提供实现时,在编译时会移除该方法以及对该方法的所有调用。

任何不符合所有这些限制的方法(例如 public virtual partial void 方法)都必须提供 实现。 此实现可以由源生成器提供。

 partial void OnNameChanged();            //在 file1.cs中定义

 partial void OnNameChanging(){           //在 file2.cs中实现
    //方法实现
 }