类 Class
类是最常见的一种引用类型
class YourClassName{}
对象初始化器
对象如何可访问的字段/属性在构建之后,可以通过对象初始化器直接为其设定值 ```csharp public class Rectangle { public readonly float Width, Height; public Rectangle(){} }
Rectangle = new Rectangle() {Width = 1, Height = 2};//大括号内为对象初始化器
<a name="x8kAI"></a>## 静态类- 类也可以是静态的- 其成员必须全是静态- 不可以有子类- 例如- System.Console- System.Math<a name="U6STT"></a>## 局部类 Partial type- 允许一个类型的定义分布在多个地方(文件)- 典型应用:一个类的一部分是自动生成的,另一部分需要手动写代码```csharp//FormGen.cspartial class Form{}//Form.cspartial class Form{}
- 每个分布的类都必须使用partial来声明
- 每个分布类成员不能冲突,不能有同样参数的构造函数
- 分布类完全靠编译器来解析,必须在一个程序集里面
- 如果有弗雷,可以在一个或者多个分布类上指明,但必须一致
-
继承
一个类可以继承另外一个类,从而对原有的类进行扩展和自定义
- 子类可以重用父类的功能
C#不允许多继承
public class Person{}public class Student : Person{}
多态
引用是多态的,类型为x的变量引用子类对象
- 因为子类具有父类的所有特性,所以参数可以是子类 ```csharp public static void Display(Person son){}
Display(new Student());
<a name="dG1GS"></a>## 抽象类和抽象成员- 使用abstract声明的类是抽象类- 抽象类不可以被实例化,只有其局的的子类才可以实例化- 抽象类可以定义抽象成员- 抽象成员和virtual成员很小,但是不提供具体的实现。子类必须提供实现,除非子类抽象```csharppublic abstract Person{public abstract string name { get; }}public Student : Person{public override string name => "x";}
隐藏被继承的成员
- 子类和父类拥有相同的属性
- 按照以下规则进行解析:
- 编译时对A的引用会绑定到A.Counter
- 编译时对B的引用会绑定到B.Counter
- 编译时会警告
- 故意异常父类成员可以在子类成员前加上new ```csharp class A { public int Counter = 1; }
class B : A { public int Counter = 2; //public new int Counter = 2; 故意隐藏父类成员可以加上new修饰符,new 仅仅是一致编译器警告而已 }
A a = new A(); //Counter 1 B b = new B(); // 2 A x = new B(); // 1
<a name="2cmQW"></a>### 隐藏和重写区别```csharpOverrider over = new Overrider();BaseClass b1 = over;over.Foo(); //Overrider.Foob1.Foo(); //Overrider.FooHider h = new Hider();BaseClass b2 = h;h.Foo(); //Hider.Foob2.Foo(); //BaseClass.Foo
引用转换
- 一个对象的引用可以隐式转换到其弗雷的引用(向上转换)
- 转换到子类的引用需要显示转换(向下转换)
-
向上转换
子类引用创建父类引用
Student s = new Student();Person p = s;Console.WriteLine(s == p) //True 都指向子类对象
父类对象引用指向子类对象的时候,父类对象可视范围更小,它只能调用父类的属性
向下转换
从父类的引用创建出子类的引用
Student s = new Student();Person p = s;Student s1 = (Student) p;
底层对象不会收到影响
-
操作符 As
as操作符会执行向下转换,如果转换失败不会抛出异常,值会变成null
Person p = new Person();Student s = p as Student;
操作符 Is
is操作符会检验引用的转换是否成功。换句话说,判断对象是否派生于某个类(或者实现了某个接口)
- 通常用于向下转换之前的验证
C#7里,使用is操作符可以引入一个变量
if (new Student() is Person)...if (p is Student s)...
接口
接口与class类似,但是它职位其成员提供了规格,而没有提供具体的实现
- 接口的成员都是隐式抽象的
- 一个class或者struct可以实现多个接口
- 接口的成员都是隐式public的,不可以声明访问修饰符
- 实现接口对他所有成员进行public的实现
- 可以隐式的把一个对象转化成它实现的接口
- 接口可以继承其他接口
- 实现多个接口可能会造成成员签名的冲突,文明可以显示实现接口成员来解决该问题
- 隐式实现的接口成员默认是sealed的
- 如果想要进行重写的话,必须在基类中把成员标记为virtual或者abstract
- 显示实现的接口成员不可以被标记为virtual,不可以重写,但是可以重新实现
- 子类可以重新实现父类实现的接口成员
- 最好办法是设计一个无需重新实现的基类:
- 隐式实现成员的时候,按需标记virtual
- 显示实现成员的时候,可以这样做
public class TextBox : IUndoable{void IUndoable.Undo() => Undo();protected virtual void Undo() => ...;}
public interface Ienumerator{bool MoveNext();object Current { get;}void Reset();}
字段 Field
字段是Class或Struct的成员,它是一个变量
class Person{string name; //Name是一个字段int Age = 18; //Age也是一个字段}
初始化
字段可以选择是否初始化
- 未被初始化的字段会有一个默认值
- 字段的初始化在构造函数之前运行
可以同时声明多个字段
static readonly int legs = 2,eyes = 2;
属性 Properties
属性的声明和字段声明很像,但多了一个get set块 ```csharp class Person { int y; string name {
get{}set{}
} int x {
get => x * x;set => x = y / x;
} int y { get; set; } = 123 //自动属性,属性初始化器
}
<a name="9fSmM"></a># 属性和字段的区别- 属性赋予了实现这对获取和复制的完全控制权。- 这种控制权限允许是现在选择容易所需的内部表示,不向属性的使用者公开内部细节<a name="fE289"></a># 索引器- 索引器提供访问封装了列表之或字典值的**class**/**struct**的元素的一种语法,比如字符串- 语法与数组类似,但是索引的参数任意- 一个类型可以声明多个索引器,参数类型有不同- 一个索引器可以有多个参数<a name="762pV"></a>## 实现索引器- 定义一个this属性,并通过中括号指定参数```csharpclass Sentence{string[] words = "hello world";public string this[int wordNum]{get { return words[wordNum]; }set { words[wordNum] = value; }}public string this[int wordNum] => words[wordNum]; //只读索引器}
修饰符 Readonly
- readonly 修饰符防止字段在构造之后呗改变
被 readonly 修饰符修饰后的字段,只能声明的时候被赋值,或在构造函数里被赋值
class Person{static readonly int legs = 2;}
修饰符 Virtual
标记为virtual的函数可以被子类重写, 包括方法、属性、索引器、事件 ```csharp public class Asset { public string Name; public virtual decimal Liablility => 0; }
public House : Asset { public decimal Mortgage; public override decimal Liability => Mortgage; }
<a name="wMXPD"></a># 密封Sealed- 重写的成员,可以使用sealed关键字把它“密封”起来,以防被子类重写- 也可以sealed类本身- 应用于某个类时,`sealed` 修饰符可阻止其他类继承自该类。 在下面的示例中,类 `B` 继承自类 `A`,但没有类可以继承自类 `B````csharpclass A {}sealed class B : A {}
重写 Override
- virtual方法和重写方法的签名、返回类型、可访问程度必须是一样的
-
注意
构造函数里调用virtual方法可能比较危险,因为编写子类的开发人员可能不知道他们在重写方法的时候,面对的是一个未完全初始化的对象
重写的方法可能会访问用来与还未被构造函数初始化的字段属性或方法
方法 Method
通常有一些语句,用来执行某个动作(一般做一件事,提高聚合度)
- 参数
- 返回类型
- void(无返回)
-
签名
类型内方法的签名必须唯一
签名:方法名、参数类型(顺序,但参数名称和返回类型无关)
class Person{void Eat(Food food){}void Eat(Food food){ //签名不唯一报错}void Eat(Food foods){ //签名与参数名称无关,故签名相同报错}void Eat(Foods food){ //签名参数类型不一样,不报错}string Eat(Food food){ //签名与返回类型无关,故签名相同报错}}
表达式体 Expression-Bodied
C#6.0语法糖,当方法执行单一任务,或者返回计算结果不涉及额外逻辑可以简写方法体
int Sum(int x, int y){return x + y;}//改进后int Sum(int x, int y) => x + y;
重载 Overload
类里面的方法可以进行重载(允许多个同名的方法同时存在),只要方法的签名不同即可 ```csharp void Foo(int x) {} void Foo(double x) {} void Foo(int x, float y) {}
void Foo(int x) {} float Foo(int x) {} //编译报错,签名相同
void Goo(int[] x) {} void Goo(params int[] x) {} //与params修饰符无关
void Foo(int x) {} void Foo(ref int x) {} //传递方式不一样 void Foo(out int x) {} //和前面一样 都是按引用传递
<a name="LwZqe"></a>## 本地方法- 不可以使用Static```csharpvoid Do(){int Calculate(int x) => x * x;//作用域在Do方法里//可以访问函数内的变量}
构造方法
- 在class或struct上初始化代码
- 返回类型和类型一致,并且返回类型省略不写
- C#7之后允许但语句的构造函数写成expression-bodied成员的形式 ```csharp public class Panda { string name; public Panda(string n) => name = n;
}
- class或struct可以重载构造函数- 调用重载构造函数时使用this- 同一类型下构造函数A调用B的时候,B先执行- 可以把表达式传递给另一个构造函数,但函数内不能使用this引用类中非静态方法,因为此时对象未被初始化```csharppublic class Panda{string name;int old;public Panda(string n) => name = n;public Panda(string n, int o):this(n){this.old = o;this.Do();}public static Do(){}}
静态构造方法
- 静态构造函数,每个类型执行一次
- 非静态构造函数,每个实例执行一次
- 一个类型只能定义一个静态构造函数
- 必须无参
- 方法名与类型一致
- 类型使用之前的一瞬间,编译器会自动调用类型的静态构造函数:
- 实例化一个类型
- 访问一个类型的静态成员
- 只能使用unsafe和extern修饰符
class Test{static Test() {Console.Write("Test");}}
终结器 Finalizer
- Finalizer是class专有的方法
- 在GC回收未引用对象的内存之前运行
- 其实就是对object的Finalize()方法重写的一种语法
可以用表达式体
class Test{~Test(){}}
初始化顺序
静态字段的初始化器在静态构造函数被调用之前的一瞬间运行
- 如果没有静态构造函数,静态字段初始化器在类型被使用之前的一瞬间执行,或者更早
-
Deconstructor C#7
作用与构造函数相反,把字段反复制给一堆变量
- 方法名必须为Deconstruct
- 可以被重载
可以为扩展方法 ```csharp public class Rectangle { public readonly float Width, Height public Rectangle(float width, float height) {
Width = width;Height = height;
}
public void Deconstruct(out float width, out float height) {
width = Width;height = Height;
}
}
class Test { public static void Main(string[] args) { var rect = new Rectangle(3, 4); (float width, float height) = rect; //rect.Deconstruct(out width, out height); //rect.Deconstruct(var out width, var out height);
}
}
<a name="d1pgZ"></a># Object 类型- object(System.Object)是所有类型的终极父类- object是引用类型- 值类型可以转化为object,反之亦然- 值类型和object之间转化的时候,CLR会执行一些特殊的工作,以弥装箱和拆箱```csharpint x = 0;object o = x; //box the intint y = (int) o;//unbox the int
Enum 枚举
- 枚举是一种特殊的值类型,它可以让你指定一组命名的数值常量
- 每个枚举都对应一个底层整型数值(Enum.GetUndelyingType())。默认
- 为int
- 0,1,2…按照声明顺序自动赋值
- 可以指定其他作为枚举类型
public enum Borderside : byte {Left, Right, Top, Bottom}
- 可以单独指定枚举成员对应的整数值
也可以指定部分,然后未被赋值的成员按照它签名已经赋值的成员的值递增
public enum Borderside{Left, Right, Top, Bottom}
Flags Enum 组合枚举
可以对枚举的成员进行组合
- 为了避免起义,枚举成员的需要显示赋值。典型用2的乘幂
- flags enum,可以使用位操作符 ```csharp [Flags] public enum Borderside{None=0, Left=1, Right=2, Top=4, Bottom=8}
Borderside leftRight = Borderside.Left | Borderside.Rigth; //底层原理 //top: 0000 0001
//rigth:0000 0010
```
