第3章 创建类型
3.1 类#
class ClassName{ }
3.1.1 字段#
字段是类或结构体中的变量成员。
- static
- public internal private protected
- new (继承修饰符)
- unsafe
- readonly
- volatile (线程访问修饰符)
- 强迫使用的字段的读写操作都在代码指示的位置发生,而不是通过优化在其他位置生成。
static readonly int x=10,y=40;
3.1.2 方法#
方法由一组语句实现某个行为。
表达式体方法
int Foo(int x){return x*2} int Foo(int x)=>x*2; void Foo(int x)=>Console.WriteLine(x);
方法重载
只要确保方法签名不同,可以在类中重载方法。(多个方法公用一个名称)
- 方法的签名包括方法名和参数的个数和类型。
局部方法:在方法中允许定义另一个方法。
void Test(){ int Foo(int x)=>2*x; }
3.1.3 实例构造器#
构造器执行类或结构体的初始化代码。
public class Test(){ private int x; private int y; public Test(int x){ this.x=x; } public Test(int x,int y):this(x){ this.y=y; } // 解构器 方法名必须为Deconstruct,配合out参数使用 public void Deconstruct(out int x,out int y){ x=this.x; y=this.y; } } // 解构器使用 var test=new Test(3,4); (int a,int b)=test; Console.WriteLine(a+" "+b);// 3 4
3.1.4 this 引用#
this 引用仅在类或结构体的非静态成员中有效。
3.1.5 属性#
属性和字段很像,但属性内部像方法一样含有逻辑。
public int Width {get;set;} = 120;
3.1.6 索引器#
索引器为访问类或结构体中封装的列表或字典典型数据元素提供了自然的访问接口。
- [ ]
class Sentence{ string[] words="I am Bob"; public string this[int wordNum]{ get{return words [wordNum];} set{words [wordNum] =value;} } } // 使用索引器 Sentence s=new Sentemce(); Console.WriteLine(s[2]);// Bob s[2]="Alice"; Console.WriteLine(s[2]);// Alice
3.1.7 常量#
常量是一种值永远不会改变的静态字段。 const
3.1.8 静态构造器#
每个类型的静态构造器只会执行一次,而不是每个实例执行一次。
- 一个类只能定义一个静态构造器(名称与类名相同,并且没有参数)
class Test{ static Test(){ } }
执行顺序:静态字段——>静态构造——>字段——>构造器
3.1.9 静态类#
static 类只能包含静态成员,并且不能有子类。
3.1.10 终结器#
终结器是只能在类中使用的方法。
- 该方法在垃圾回收器回收未引用的对象占用的内存前调用。
class Test(){ ~Test(){ } }
3.1.11 分部类型和方法#
分部类型允许一个类分开在两个文件定义。
// Test1.cs partial class Test{ } // Test2.cs partial class Test{ }
分部类中的分部方法(partial)一个声明,一个手动实现。
3.1.12 nameof 运算符#
nameof 运算符返回任意符号的字符串名称(类型,成员,变量等)。
3.2 继承#
类通过继承另一个类来扩展对自身进行扩展或定制。
3.2.1 类型转型#
向上转型:创建一个基类指向子类的引用 (向上转型仅仅影响引用,不会影响被引用的对象)
Son son=new Son(); Parent parent=son;
向下转型:从基类引用创建一个子类的实现 (显示转换,可能出错)
Parent parent=new Parent(); Son son=(Son)parent;
is 和 as 运算符
is 运算符检查引用的转换是否能够成功,即对象是否从某个特定的类派生(或是实现某个接口)
if(a is TestClassName){ Console.WriteLine((TestClassName)a); } // 等价代码 if(a is TestClassName t){ Console.WriteLine(t); }
is ⇒ 验证一个数据项是否属于特定类型。
as ⇒ 相比is 更进一步,尝试将对象类型转换为特定类型。如果类型不能转换,as操作符会返回null。
3.2.2 虚函数成员#
提供特定实现的子类可以重写(override)标识为virtual 的函数。
- 方法、属性、索引器和事件 都可以声明为virtual
public class A{ public virtual int Method(int x){ return 2*x; } } public class B:A{ public override int Method(int x){ return 3*x; } }
3.2.3 抽象类和抽象成员#
抽象类不能实例化,抽象类的子类必须实现所有抽象类的方法、属性等。 abstract
public abstract class A{ public virtual int Method(int x); } public class B:A{ public override int Method(int x){ return 3*x; } }
3.2.4 隐藏继承成员#
父类和子类有相同的成员,那么子类的成员就会隐藏父类的成员。
public class A{ public int x=1; } public class B:A{ public int x=2; } // 编译器会发生警告。使用new 修饰符,阻止编译器发生警告。 public class B:A{ public new int x=2; }
new 和重写
public class Class1 { public virtual void Method() { Console.WriteLine("base 默认方式!"); } } public class Class2 : Class1 { public override void Method() { Console.WriteLine("override 方式!"); } } public class Class3 : Class1 { public new void Method() { Console.WriteLine("new 方式!"); } } // 测试代码 Class2 class2=new Class2(); class2.Method(); Class1 class1 = class2; class1.Method(); Class3 class3 = new Class3(); class3.Method(); Class1 class11 = class3; class11.Method(); /* 测试结果: override 方式! override 方式! new 方式! base 默认方式! */
3.2.5 密封函数和类#
函数和类可以使用 sealed 关键字 进行密封,防止被继承。
3.2.6 base 关键字#
base 和this相近,作用:(1)从子类访问重写的基类函数成员(2)调用基类的构造函数
class B:A{ public B(int x):base(x){ } void Method(){ Console.WriteLine(base.x); } }
3.2.7 构造器和继承#
子类必须声明自己的构造器。派生类可以访问基类的构造器,但并非自动继承。
如果子类的构造器省略base 关键字,那么基类的无参数构造器将被隐式调用。
构造器和字段初始化顺序:
public class A{ int x=1; // 3rd public A(int x){ ... // 4th } } public class B:A{ int y=1; // 1st public B(int x):base(x+1){ // 2nd ... // 5th } } // 调用代码 B b=new B(3);
3.3 object 类型#
3.3.1 装箱和拆箱#
装箱:值类型实例 转换为 引用类型实例的行为。
拆箱:引用类型实例 转换为 值类型实例的行为。
int x=10; // 装箱 string y=x; // 拆箱 int z=(int)y;
3.3.2 GetType 方法和typeof 运算符#
GetType() 运行时计算 —— 非静态的字段
typeof 编译时静态计算 —— 类
获取类型
3.3.3 ToString() 方法#
所有内置类型都有此方法。
int x=10; Console.WriteLine(x.ToString());
3.3.4 object 成员列表#
3.4 结构体#
结构体 与类相似,不同点:(1)结构体是值类型
(2)结构体不支持继承,除了隐式派生与System.ValueType
public struct Point{ public int x; public int y; public Point(int x,int y){ this.x=x; this.y=y; } } // 调用 Point p1=new Point(); Point p2=new Point(2,3); Console.WriteLine(p1.x + " " + p1.y); // 0 0 Console.WriteLine(p2.x + " " + p2.y); // 2 3
3.5 访问修饰符#
3.6 接口#
接口和类相似,但接口只为成员提供定义而不提供实现。
类是单继承,但可以多继承接口。
public interface IClass { void Method(); } public interface IClass2 { void Method2(); } public class Test:IClass,IClass2{ public void Method(){ Console.WriteLine("调用接口方法"); } public void Method2(){ Console.WriteLine("调用接口方法2"); } }
接口和装箱
interface IA{ void Method(); } struct S:IA{ public void Method(){ } } S s=new S(); s.Method(); IA ia=s; // 装箱 struct(值类型)=>IA(引用类型) ia.Method();
3.7 枚举#
枚举类型是一种特殊的值类型,可以在枚举类型中定义一组命名的数值常量。
public enum States{ Left,Right,Top,Bottom, LeftRight=Left | Right, TopBottom=Top | Buttom, All=LeftRight | TopBottom } // 使用枚举,默认int类型 States state=States.Left; // 里面的类型设置为 byte 类型 public enum States:byte{ Left,Right,Top,Bottom } // 枚举类型转换 byte x=(byte)States.Right; // 枚举可以使用的运算符 = == != < > <= >= + - ^ & | ~ += -= ++ -- sizeof
3.8 嵌套类型#
嵌套类型是声明在另一个类型内部的类型。
public class A{ public class B{ public enum Colors{ Red,Blue,Green } } }
嵌套类的特征:
-
可以访问它的外层类中的private成员,以及外层类能够访问的所有内容。
-
可以在声明上使用所有的访问修饰符
-
嵌套类型的默认可访问性是private而不是internal
-
从外层类以外访问嵌套类型,需要使用外层类名进行限定(就像访问静态成员那样)
A.B.Colors color=A.B.Colors.Red;
3.9 泛型#
类似C++的模板。
3.9.1 什么是泛型?#
public class Stack<T>{ int position; T[] data=new T[100]; public void Push(T obj) => data[position++]=obj; public T Pop() =>data[--position]; }
3.9.2 为什么需要泛型?#
泛型是为了解决代码能够跨越类型复用而设计的。
3.9.3 泛型方法#
使用泛型方法,许多基本算法就可以通用方式实现。
static void Swap<T>(ref T a,ref T b){ T temp=a; a=b; b=temp; } // 使用 int x=4; int y=5; Swap<int>(ref x,ref y);
3.9.4 声明类型参数#
// 泛型重载 class A{} class A<T>{} class A<T1,T2>{}
3.9.5 泛型的默认值#
还是使用 default 关键字。 default(T)
3.9.6 泛型的约束#
3.9.7 继承泛型类型#
泛型类和非泛型类都可以派生子类。
class Stack<T>{ } // 子类中仍可以令基类中类型参数都保持开放。 class SpecialStack<T>:Stack<T>{ } // 子类可以用具体的类型来封闭泛型参数 class IntStack:Stack<int>{ } // 子类型还可以引入新的类型参数 class List<T>{ } class KeyedList<T,TKey>:List<T>{ }
3.9.8 自引用泛型声明#
一个类型可以使用自身类型作为具体类型来封闭类型参数。
public interface IEquatable<T>{ bool Equals(T obj); } public class Test:IEquatable<Test>{ public string Color{get;set;} public int CC{get;set;} public bool Equals(Test t){ if(t==null) return false; return t.Color==Color && t.CC==CC; } } // 第二种方式 class Foo<T> where T:IComparable<T>{} class Bar<T> where T:Bar<T>{}
3.9.9 静态数据#
3.9.10 协变#
假定A可以转换为B,如果X<A>可以转换为X<B>,那么称X有一个协变类型参数。
- 可变性(协变和逆变)不是自动的
- 数组支持协变。如果B是A的子类,则B[]可以转换为A[](A和B都是引用类型)
声明协变类型参数:
// 假定Stack<T>类实现了IPoppable接口 public interface IPoppable<out T>{T Pop();} //T上的out修饰符表明T只用于输出的位置(例如方法的返回值) //out 修饰符将类型参数标记为协变参数 var bears=new Stack<Bear>(); bears.Push(new Bear()); IPoppable<Animal> animals=bears; Animal a=anlmals.Pop();
3.9.11 逆变#
假定A可以转换为B,如果X<B>可以转换为X<A>,那么称X有一个逆变类型参数。
public interface IPushable<in T>{ void Push(T obj) } IPushable<Animal> animals=new Stack<Animal>(); IPushable<Bear> bears=animals; bears.Push(new Bear());
【推荐】国内首个AI IDE,深度理解中文开发场景,立即下载体验Trae
【推荐】编程新体验,更懂你的AI,立即体验豆包MarsCode编程助手
【推荐】抖音旗下AI助手豆包,你的智能百科全书,全免费不限次数
【推荐】轻量又高性能的 SSH 工具 IShell:AI 加持,快人一步
· 分享4款.NET开源、免费、实用的商城系统
· 全程不用写代码,我用AI程序员写了一个飞机大战
· MongoDB 8.0这个新功能碉堡了,比商业数据库还牛
· 白话解读 Dapr 1.15:你的「微服务管家」又秀新绝活了
· 上周热点回顾(2.24-3.2)