类
类的创建
类似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();
}
}
多形性概述
通过虚拟方法,派生类可以选择不同的派生行为:
-
派生类可以重写基类中的虚拟成员,并定义新的行为:
仅当基类成员声明为
virtual或abstract时,派生类才能重写基类成员,派生成员必须使用override关键字显式指示该方法将参与虚调用。实例参见上边代码。 -
派生类可能会继承基类的方法而不重写,保留现有的行为,但允许进一步派生的类重写方法;
-
派生类可以定义隐藏基类实现的成员的新非虚实现。
如果希望派生类具有与基类中成员同名的成员,则可以使用
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; } } } -
new和override的区别:二者的区别主要在于,将派生类实例强制转为基类的实例,对于new修饰的方法,此时则会访问基类的方法,但对于override修饰的方法,则一直会访问派生类中的方法。 -
如果类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;
- 可以使用
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() { }
}
}
一些限制
- 要作为同一类型的各个部分的所有分部类型定义都必须使用
partial进行修饰。 partial修饰符只能出现在紧靠关键字class、struct或interface前面的位置。- 分部定义不能跨越多个模块,必须都在同一程序集和同一模块(.exe/.dll中)进行定义。
- 类名和泛型类型参数在所有的分部类型定义中都必须匹配。 泛型类型可以是分部的。 每个分部声明都必须以相同的顺序使用相同的参数名。
分部方法
分部类或结构可以包含分部方法。 类的一个部分包含方法的签名。 可以在同一部分或另 一部分中定义实现。
当签名遵循以下规则时,分部方法不需要实现:
- 声明未包含任何访问修饰符。 默认情况下,该方法具有 private 访问权限。
- 返回类型为 void。
- 没有任何参数具有 out 修饰符。
- 方法声明不能包括以下任何修饰符:
virtual,override,sealed,new,extern. - 当未提供实现时,在编译时会移除该方法以及对该方法的所有调用。
任何不符合所有这些限制的方法(例如 public virtual partial void 方法)都必须提供 实现。 此实现可以由源生成器提供。
partial void OnNameChanged(); //在 file1.cs中定义
partial void OnNameChanging(){ //在 file2.cs中实现
//方法实现
}