简介

本文记录c#学习,仅供个人参考。

基本语法

控制台方法相关

使用命名空间System中的console类

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
using System;
namespace Project
{
internalk class Program
{static void Main(string[] args)
{
Console.WriteLine();//输出(自动换行)
Console.Write();//输出(不会自动换行)
Console.ReadLine();//用户端输入
Console.ReadKey();//检测玩家是否输入(只能输入一个字符)
Console.Readkey(true).keychar//得到输入的内容,返回输入的字符,若填了true则窗口不会显示输入的内容
Console.Clear();//清空
//设置控制台大小,缓冲区大小
Console.SetWindowSize(width,height);//下同
Console.SetBufferSize();
//控制台左上角是原点,右边是x正半轴,下边是y轴正半轴。
//设置光标位置
Console.SetCursorPosition(left,top);//横纵距离单位不同,1y=2x;
Console.ForegroundColor=ConsoleColor.red;//改变文字颜色(例子中设置成了红色)
Console.BackgroundColor=ConsoleColor.white;//改变背景颜色(例子中设置成了白色)
//如果想要窗口全变色的话,设置完颜色后需要执行一次clear清空之前的文字。
Console.CursorVisible = false;//光标显隐(有true和false两种状态,当为false时光标不显示)
Environment.Exit(0);//关闭控制台
}


}

}

变量相关

输入#region,再按tab键自动补全,可以将#region和#endregion之间的代码折叠起来

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
using System
{
namespace Project
{
class Program
{
static void Main(string[] args)
{
#region myregion
#endregion
}
}
}

}

变量的区别:float浮点数申请需要末尾加一个f,不加的话默认是double类型,float有效数字保留8位,从第一个非零数字开始数起,decimal能存储25~27位的有效数字,但不建议使用,末尾加m;c#中char类型占两个字节,能用汉字赋初值,但部分生僻字不能。

类型转换

隐式和显式转换和c语言,c++没多大差别。字符串转换:类型.Parse(“字符串”);(比如说int.Parse(“123”)等于整型123)。

Convert.To类型名();转换精度更高。其他类型转string:变量类型.toString();

1
2
3
4
5
6
7
8
//比如说
string str=1.toString();
str=true.toString();
Console.WriteLine("12354"+1+true);//这里1和true会自动转换成字符串类型

//parse示例
int a=int.Parse("123");//此时a里的值为123
char b=char.Parse("A");//此时b里的值为A

异常捕获

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
//语法
try
{
//希望进行异常捕获的语法快放入try中
//如果try中的代码报错了就不会让程序卡死
}
catch
{
//然后跳入catch中执行catch中的代码
//catch(Exception e) 进行具体的报错追踪,通过e得到具体的报错信息
}//到此为必备部分
finally
{
//最后执行的代码,不过程序有没有出错都会执行其中的代码
}//可选部分



字符串拼接

直接将字符串+字符串就能实现拼接。

或者使用string.Format(“带拼接的内容”,内容1,内容2…..);(有点类似于格式化输出)

1
2
string.Format("{0}を愛するみんなさん、{1}んにちは!、ハメドリ君だ{2}","ドスケベセックス","シコ","ハメ");
//要用0~n加大括号表示要拼接的内容,后面写要拼接的内容。

位运算

位与(&):连接两个数值进行位运算,将数值转为二进制。对位运算,有零为零。

1
2
3
4
5
int a=1;//(001)
int b=5;//(101)
//001
//101
//001(最终结果为1)结果小于等于最小的数

位或(|):连接两个数值进行位运算,将数值转为二进制。对位运算,有一为一。

1
2
3
4
5
int a=1;//(001)
int b=3;//(011)
//001
//011
//011(最终结果为3)结果大于等于最大的数

异或(^):连接两个数值进行位运算,将数值转为二进制。对位运算,相同为零,不同为一。

1
2
3
4
5
int a=1;//(001)
int b=5;//(101)
//001
//101
//100(结果为4)

位取反(~):写在数值前面,零变一,一变零。

1
2
int a=5;//(101)
//~a==2(010);

左移(<<)和右移(>>):让一个二进制的数左移或右移。

左移几位,右边多加几个零。

右移几位,右侧去掉几个数。

随机数

1
2
Random r=new Random();
int i=r.Next(a,b);//生成随机数[a,b)的随机数赋值给i

复杂数据类型

枚举

被命名的整型常量的集合,一般用它来表示状态,类型等等。通常和switch语句搭配使用

声明枚举:创建一个自定义的枚举类型。(枚举可以声明在namespace语句块中,也可以声明在class和struct语句中,不能在函数语句块中声明)

声明枚举变量:使用自定义的枚举类型,创建一个枚举变量。

类型转换用强制转换。用tosString();转成枚举项的名字。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
enum E_自定义枚举名//以E或E_开头,作为命名规范
{
自定义枚举项名字,//可以自己赋值,为整型变量
自定义枚举项名字1,//这项的值为上一项加一,下同
...
自定义枚举项名字n

}
eg:
enum E_player
{
main,
other
}//声明枚举
E_player playertype=E_player.mian;//声明枚举变量
playertype=(E_player)Enum.Parse(typeof(E_player),"枚举项名称");//字符串转成枚举

数组

语法:变量类型 []变量名=new 变量类型[数组大小]{这里可以赋值};(默认初始化为零)//一维数组

变量类型[,]变量名=new 变量类型[行数,列数];//二维数组

1
2
3
4
5
const int N=100
int[] a=new int[N]{此处可以赋值};//一维数组
int[,] a=new int [3,3]{此处可以赋值};//二维数组
Console.WriteLine(a.GetLength(0);//得到二维数组行的长度
Console.WriteLine(a.GetLength(1);//得到二维数组列的长度

交错数组

语法:变量类型 [] []变量名=new 变量类型[] []

1
2
3
int [][]arr=new int[3][]{new int[3]{1,2,3},new int[2]{1,2},new int[1]{1}};
int [][]arr=new int[][]{new int[3]{1,2,3},new int[2]{1,2},new int[1]{1}};
int [][]arr={new int[3]{1,2,3},new int[2]{1,2},new int[1]{1}}

值类型与引用类型

引用类型:string,数组,类

值类型:其它,结构体

引用类型和值类型存储的内存区域不相同,存储方式也不同。

值类型存储在栈上,系统分配,自动回收,小而快。

引用类型存储在堆空间,手动申请和释放,大而慢。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
using System.Diagnostics.CodeAnalysis;
using System.Runtime.InteropServices;

namespace 测试程序
{

class Project
{
static void Main(string[] args)
{
int a = 10;
int b = a;
int[] arr = new int[] { 1, 2, 3, 4 };
int[] arr2 = arr;
Console.WriteLine("a的值为{0},b的值为{1}",a,b);
Console.WriteLine("arr[0]的值为{0},arr2[0]的值为{1}",arr[0], arr2[0]);
b = 20;
arr2[0] = 9;
Console.WriteLine("修改之后的值为:");
Console.WriteLine("a的值为{0},b的值为{1}", a, b);
Console.WriteLine("arr[0]的值为{0},arr2[0]的值为{1}", arr[0], arr2[0]);
}
}

}
//运行结果为:
//a的值为10,b的值为10
//arr[0]的值为1,arr2[0]的值为1
//修改之后的值为:
//a的值为10,b的值为20
//arr[0]的值为9,arr2[0]的值为9

string类型

虽然是引用类型,却不遵循引用类型的赋值方式,一般情况下引用类型赋值会把地址赋值给新变量,修改变量值时会直接修改地址内存内存储的值,但是string类型在赋值时会新开辟一段内存存储,使变量名指向新的地址,和值类型类似。

ref和out

可以在函数内部改变外部传入的内容,类似于c++中的引用。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
using System.Collections.Specialized;
using System.ComponentModel.Design;
using System.Diagnostics.CodeAnalysis;
using System.Reflection.Metadata.Ecma335;
using System.Runtime.InteropServices;

namespace 测试程序
{

class Project
{
static void changevaule(ref int a)
{
a = 3;
}

static void changearr(ref int[]arr)
{
arr = new int[] { 10,20,30};
}


static void Main(string[] args)
{
int[] arr = new int[] { 1,2,3};
Console.WriteLine(arr[1]);
changearr(ref arr);
Console.WriteLine(arr[1]);
}

}

}//本来new是会新开辟一段内存,但用了ref之后,则会修改原来地址的值
//ref和out的用法一样,ref传入的变量必须初始化,out不用
//out传入的变量必须在内部赋值,ref不用,并且out传入的的变量必须在函数内部赋值,ref则不一定

变长参数和参数默认值

变长参数关键字:params

变长参数只能是函数的最后一个参数。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
namespace 测试程序
{

class Project
{
static int Sum(params int[]arr)
{
int sum = 0;
for(int i=0;i<arr.Length;i++)
{
sum += arr[i];
}
return sum;
}
static int speak(string str="hello")
{
Console.WriteLine(str);
}


static void Main(string[] args)
{
int ans = Sum(1,2,3,4,5,6,7);
Console.WriteLine(ans);//结果为28
speak();//可以不传参数,打印出默认值,有可选参数和普通参数的情况下可选参数要放后面
}

}

}

结构体

构造函数:函数名与结构体名相同,没有返回值,this关键字使用this.和class不相同。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
student s1=new student(//参数);
namespace 测试程序
{

class Project
{
struct student
{

int age;
string name;
bool sex;
public student()
{
age = 114514;
name = "田鼠浩二";
sex = true;
}

public void speak()
{
Console.WriteLine("我叫{0},我是{1}性,今年{2}岁",name,sex?'男':'女',age);
}
};

static void Main(string[] args)
{
student s1=new student();
s1.speak();

}

}

}

面向对象

三大特性:封装、继承、多态

封装

类和对象

类一般声明在namespace语句块中

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
//类声明的语法
class type
{
//成员变量
//成员方法
//成员属性

//构造函数和析构函数
//索引器
//运算符重载
//静态成员
}

//例子(形容人类)
//类用帕斯卡命名法
//同一个语句块中的不同类不能重名
//类创建的过程叫做实例化对象
class Person
{
//实例化对象的基本语法
//类名 变量名;
//类名 变量名=null;
//类名 变量名=new 类名();
}
Person p;
Person p2=null;
Person p3=new Person();

成员变量

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
//基本规则
//声明在类语句块中
//用来描述对象的特征
//可以是任意变量类型
//数量不做限制
//是否赋值根据需求决定
enum E_Animal
{
dog,
cat,
bird
}
class Pet
{
string name;
E_Animal type;
}
enum E_SexType
{
Man,
Woman
}
class Person
{
//名字
string name="田鼠浩二";
int age;
E_SexType sex;
Person grilFriend;
//朋友
Person[] boyfriend;
Pet pet=new Pet();
}
//访问修饰符
//public:公共的,内部和外部都能访问
//private:私有的,只有类内部可以使用
//protect:保护的,类和其子类才能访问和使用

//成员函数的使用和初始值
default(类型名);//得到该类型的默认值

成员方法

别加static关键字

用法跟函数类似

1
2
3
4
5
6
7
8
9
//成员方法(函数)用来表现对象行为
//描述对象行为
//受到访问修饰符规则影响
//方法数量不受限制
//返回值参数不做限制
//不要加static关键字



构造、析构、垃圾回收

构造函数:在实例化对象时,会调用的用于初始化的函数,如果不写,默认存在一个无参构造函数

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
//写法
//没有返回值
//函数名和类名必须相同
//没有特殊需求,默认是public
class Person
{
public string name;
public int age;
//类中允许无参构造函数
//结构体中不允许
//可以重载构造函数
public Person()
{
name="田鼠浩二";
age=114514;
}
public Person(string name,int age)
{
//this是防止形参和类中实参冲突用的
//写了this表示是指类中的成员变量,不是传进来的形式参数
this.name=name;
this.age=age;
}

public Person(string name,int age):this()
{
//这种写法会调用无参构造函数,再调用此函数
}
public Person(string name)
{

}
public Person(string name,int age):this(name)
{
//这种写法会执行只含有name参数的构造函数,也就是上面的函数,再执行此函数
}
//this()里头也可以写常量,会先调用参数为该常量类型的构造函数
}

析构函数:但引用类型的堆内存被回收时,会调用该函数

//对于需要手动管理内存的语言(比如c++),需要在析构函数中做一些内存回收处理,但是c#中存在自动垃圾回收机制GC,所以几乎不会用到构造函数,在unity开发中几乎不会用到

语法:~类名()

{

}

垃圾回收机制

先放一边,之后再补

成员属性

基本概念:用于保护成员变量,为成员属性的获取和复制添加逻辑处理,解决3p的局限性,属性可以让成员变量在外部只能获取,不能修改或者是只能修改不能获取。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
//基本语法
get{}
set{}
//get和set前面是可以加访问修饰符的,默认是public
//因为这种特性,所以在外部可以只赋值或者只返回值,可以保护数据
//属性本身的访问修饰符权限大于get和set
//get和set可以只有一个,一般只会出现只有get的情况
class Person()
{
private string name;
private int age;
private int money;
private bool sex;
//属性的命名一般使用帕斯卡命名法
public string Name
{
get{
//可以在返回之前添加一些逻辑规则
//意味着这个属性可以获取的内容
return name;
}
set
{
//可以在设置之前添加一些逻辑规则
//value用于表示外部传入的值
name=value;
}

}
public int Money
{
get
{
//解密处理
return money-5;
}
set
{
//加密处理
money=value+5;
//内存里存储的不是实际值,但是返回的是实际值
}
}

public float Height
{
get;
private set;
//只能在内部改
//自动属性
//作用:外部能得不能改的特征
//如果类中有一个特征是只希望外部能得不能改的,又没什么特殊处理(没有在get和set中写逻辑的需求和想 法),那么可以直接使用自动属性
}
}

//成员属性的使用
Person p=new Person();
p.Name="田鼠浩二";//这里执行的是set语句块,将"田鼠浩二"赋值给name
Console.Write(p.Name);//执行的是get语句块,返回了name的值




索引器

基本概念:让对象想数组一样通过索引访问其中元素,是程序看起来更直观,更容易编写(就是专门用来访问类中数组的,类似于cpp中的运算符重载),和函数写法类似,只是把括号改成中括号

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
//基本语法
//访问修饰符 返回值 this[参数类型 参数名,参数类型 参数名...]
//内部写法和规则和成员属性相同
//get{}
//set{}
class Person
{
private strint name;
private int age;
private Person[] friends;

public Person this[int index]
{
get
{
return friends[index];
}
set
{
//value代表传入的值
friends[index]=value;

}
}
}

//使用方法
Person p=new Person();
p[0]=new Person();//相当于访问了set方法
Console.Write(p[0]);//相当于访问了get方法

//索引器可以重载
//索引器的主要作用
//可以让我们一种括号的形式范围自定义类中的元素
//比较适用于 在类中有数组变量时使用 可以方便访问和逻辑处理

静态成员

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
//静态关键字 static
//用static修饰的成员变量 方法 属性等
//称为静态成员
//特点:直接用类名点出使用

class Test
{
//静态成员变量
public static float PI=3.1415926f;
//成员变量
public testInt=100;
//静态成员方法
public static float CalcCircle(float r)
{
return PI*r*r;
}
//普通成员方法
public void TestFun()
{
Console.Write("123");
}
}

Console.Write(Test.PI);//可以直接用类名点成员来使用,不用先创建对象,不用实例化对象

//为什么可以直接点出来使用
//程序开始运行时,就会分配内存空间,所以我们就能直接使用
//静态成员和程序同生共死
//只要使用了它,直到程序结束时内存空间才会被释放
//静态成员存储在静态存储区,具有唯一性

//静态函数中不能使用非静态成员
//非静态函数可以使用静态成员

//静态成员的作用
//常用于唯一变量的申明
//方便别人获取的对象申明
//静态方法:
//常用的唯一的方法申明 比如 相同规则的数学计算相关函数

//常量和静态变量
//const可以理解为特殊的static
//相同点
//都可以通过类名点出来使用
//不同点
//const必须初始化,不能像static没用这个规则
//const只能修饰变量,static可以修饰很多
//const一定是写在访问修饰符的后面的,static没有这个要求

静态类和静态构造函数(工具类)

用static修饰的类,特点:只能包含静态成员,不能被实例化。作用:将常用的静态成员写在静态类中,方便使用,静态类不能被实例化,体现了工具类的唯一性,比如Console就是一个静态类。

静态构造函数:特点:静态类和普通类都可以有,不能使用访问修饰符,不能有参数,只会调用一次

作用:在静态构造函数中初始化 静态变量

使用:静态类中的静态构造函数

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
  static class TestStatic
{
public static int testIndex=0;

public static void TestFun()
{

}

public static int TestIndex
{
get;
set;
}
}


//静态类中的静态构造函数(使用类中内容会自动调用静态构造函数)
static class StaticClass
{
public static int testInt = 100;
public static int testInt2 = 200;

static StaticClass()
{
Console.WriteLine("静态构造函数");
}
}

//普通类中的静态构造函数(使用类中内容会自动调用静态构造函数)
class Test
{
public static int testInt=200;
//静态构造函数
static Test()
{
Console.WriteLine("静态构造");
}
//非静态构造函数(不是重载)
public Test()
{
Console.WriteLine("普通构造");
}
}

拓展方法

基本概念:为现有的 非静态 变量类型 添加新方法,作用:提升程序拓展性,不需要在对象中重新写方法,不需要继承来添加方法,为别人封装的类型写额外的方法。

特点:一定是写在静态类中,一定是一个静态函数,第一个参数为拓展目标,第一个参数用this修饰

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
//基本语法
//访问修饰符 static 返回值 函数名(this 拓展类名 参数名,参数类型 参数名,参数类型 参数名,参数类型 参数名...)

static class Tools
{
//为interesting拓展了一个成员方法
//成员方法需要实例化对象后才能使用
//value代表使用该方法的实例化对象
public static void SpeakValue(this int value)//这里的value是int类型变量本身的值
{
Console.WriteLine("田鼠浩二为int拓展的方法"+value);
}

public static Fun3(this Test t)
{
Console.WriteLine("为Test拓展的方法");
}
}

//使用
int i=10;
i.SpeakValue();
//运行结果:
//田鼠浩二为int拓展的方法10

//为自定义的类拓展方法
class Test
{
public int i=10;

public void Fun1()
{
Console.Write("123");
}

public void Fun2()
{
Console.WriteLine("456");
}
}

//使用
Test t=new Test();
t.Fun3();
//如果拓展方法和类中方法重名了,比如将Fun3改名为Fun2,那么将会执行类中的方法而拓展方法将会失效。

运算符重载

概念:让自定义类和结构体能够使用运算符

使用关键字 operator

特点:一定是一个公共的静态方法,返回值卸载operator前,逻辑处理自定义

作用:让自定义类和结构体对象可以进行运算,条件运算符需要成对实现,一个符号可以多个重载,不能使用ref和out(相当于自定义一种运算方式)

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
//基本语法
public static 返回类型 operator 运算符(参数列表)

class Point
{
public int x;
public int y;

public static Point operator +(Point p1,Point p2)
{
Point p=new Point();
p.x=p1.x+p2.x;
p.y=p1.y+p2.y;
return p;
}
//一个符号可以有多个重载
public static Point operator +(Point p1,int value)
{
Point p=new Point();
p.x=p1.x+value;
p.y=p1.y+value;
return p;
}
}

Point p1=new Point();
p1.x=1;
p1.y=1;
Point p2=new Point();
p2.x=2;
p2.y=2;
Point p3=p1+p2;
Point p4=p3+2;//不能写成p4=2+p3,有顺序,如果要写,要重写一个重载

可重载的运算符:算术运算符,逻辑运算符(只有非!),位运算符,条件运算符(需要成对实现,重载了大于就要重载小于)

不可重载的运算符:逻辑与,逻辑或,索引符,强转运算符,特殊运算符,点,三目运算符,赋值符号

内部类和分布类

内部类:一个类中再申明一个类

特点:使用时要用包裹者点出自己

作用:亲密关系的变现

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
class Person
{
public int age;
public string name;
public Body body;
public class Body
{
Arm leftArm;
Arm rightArm;
class Arm
{

}
}
}

Person p=new Person();
Person.Body body=new Person.Body();
Person.Arm

分布类:把一个类分成几部分申明

关键字:partial

作用:分布描述一个类,增加程序的拓展性。(可以写在多个脚本文件中,分布类的访问修饰符要一致,不能有重复成员)其实就是把一个类分成好几个语句块中写。

分部方法:将方法的申明和实现分离

特点:不能加访问修饰符(默认私有),只能在分布类中申明,返回值只能是void,可以有参数但不用,out关键字,局限性大,了解即可。

继承

继承的基本概念

基本概念:一个类a继承一个类b,类a会继承类b的所有成员,a类将拥有类b的所有特征和行为。

被继承的类叫做父类、基类、超类。继承的类叫做子类、派生类。子类可以有自己的特征和行为。

特点:单根性 子类只能有一个父亲。传递性 子类可以间接继承父类的父亲(类似于树状结构)

c#中允许父类和子类同名的成员,但不建议使用(会覆盖父类的同名成员)。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
//基本语法
//class 类名 : 被继承的类名
//{

//}

class Teacher
{
//名字
public string name;
//职工号
protected int number;//如果这里使用private则外部无法使用,protected子类可以使用
//介绍名字的行为
public void SpeakName()
{
Console.WriteLine(name);
}
}

Class TeachingTeacher:Teacher
{
//科目
public string subject;
//介绍科目
public void SpeakSubject()
{
Console.WriteLine(subject+"老师");
}

}

TeachingTeacher tt=new TeachingTeacher();
tt.name="田鼠浩二";
tt.number=1;
tt.SpeakName();//可以使用父类的成员

tt.subject="unity";
tt.SpeakSubject();

里氏替换原则(LSP)(面向对象七大原则之一)

基本概念:任何父类出现的地方,子类都可以替代。父类容器装子类对象,因为子类包含了父类的全部内容

作用:方便进行对象存储和管理

is:判断一个对象是否是指定类对象,返回值是bool,是为真,不是为假。

as:将一个对象转换为指定类对象,返回值为指定类对象,失败返回null

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
class GameObject
{

}

class Player : GameObject
{
public void PlayerAtk()
{
Console.WriteLine("玩家攻击");
}
}

class Monster : GameObject
{
public void MonsterAtk()
{
Console.WriteLine("怪物攻击");
}
}

class Boss : GameObject
{
public void BossAtk()
{
Console.WriteLine("Boss攻击");
}
}

//里氏替换原则 用父类容器装载子类对象
GameObject player = new Player();
GameObject monster = new Monster();
GameObject boss = new Boss();

GameObject[] objects = new GameObject[] { new Player,new Monster,new Boss};

if(player is Player)//如果是真的话会返回真,否则返回假
{

}

Player p=player as Player;
Player p=monster as Player;

继承中的构造函数

特点:当申明一个子类对象时,先执行父类的构造函数,再执行子类的构造函数

父类的无参构造函数很重要。子类可以通过base关键字代表父类,调用父类构造函数

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
class Father
{
public Father()//没有这个子类会报错
{

}

public Father(int i)
{
Console.WriteLine("Father构造");
}
}

class Son:Father//父类没有无参构造函数会报错(无参构造函数被顶替了)
{
public Son(int i):base(i)//通过base调用指定父类构造
{

}

public Son(int i,string str):this(i)//通过this先调用上面的构造函数,间接指定了父类的构造函数
{

}

}//子类实例化默认自动调用的是父类的无参构造函数

万物之父和装箱拆箱

关键字:object,概念:是所有类型的父类,是一个类(引用类型),作用:可以利用里氏替换原则,用object容器装所有对象,可以用来表示不确定类型,作为函数参数类型。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
class Father
{

}

class Son:Father
{
public void Speak()
{

}
}

Father f=new Son();
if(f is Son)
{
(f as Son).Speak();
}

object o=new Son();

if(o is Son)
{
(o as Son).Speak();
}
//值类型
object o2=1f;
//用强转
float f1=(float)o2;
//特殊的string类型
object str="123123";
string str2=str as string;

object arr=new int[10];
int []ar=arr as int[];

//装箱拆箱
//发生条件
//用object存储类型(装箱)
//再把object转为值类型(拆箱)

//装箱
//把值类型用引用类型存储
//栈内存会迁移到堆内存中

//拆箱
//把引用类型存储的值类型取出来
//堆内存会迁移到栈内存中

//好处:不确定类型可以方便参数的存储和传递
//坏处:存在内存迁移,增加性能消耗

//装箱
object v=3;
//拆箱
int inValue=(int)v;

static void TestFun(params int[] array)//将int换成object就可以传任意类型(传不确定类型)
{

}

密封类(不是很重要)

概念:使用sealed密封关键字修饰的类

作用:让类无法再被继承

1
2
3
4
5
6
7
8
9
10
11
12
class Father
{

}

sealed class Son:Father
{
//这个类无法被继承
}

//主要作用:不允许最底层子类被继承

多态

多态vob

多态:多种状态,让继承同一父类的子类们,在执行相同方法时有不同的表现,解决的问题:让同一个对象有唯一行为的特征。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
class Father
{
public void SpeakName()
{

}
}

class Son:Father
{
public new void SpeakName()//覆盖父类的方法
{

}
}

Father f=new Son();
f.SpeakName();//执行了父类的方法,用了父类容器
(f as Son).SpeakName();//执行了子类的方法

//多态的实现
//函数重载是编译时的多态,开始就写好的

//我们学习的是运行时多态(vob、抽象函数、接口)
//v:virtual(虚函数)
//o:override(重写)
//b:base(父类)

class GameObject
{
public string name;

public GameObject(string name)
{
this.name=name;
}

public virtual void Atk()
{
Console.WriteLine("游戏对象进行攻击");
}
}

class Player:GameObject
{
public Player(string name):base(name)
{

}
//重写虚函数
public override void Atk()
{
//base的作用
//代表父类 可以通过base来保留父类的行为,如果写了base父类的同名函数也会执行
base.Atk();
console.WriteLine("让玩家对象进行攻击");
}
}

即使使用了父类容器装载子类的实例化对象,但是通过重写之后可以直接使用子类的同名函数,不需要通过as进行转化了

抽象类和抽象方法

抽象类

概念:被抽象关键字abstract修饰的类,特点:不能被实例化,可以包含抽象方法,继承抽象类必须重写其抽象方法

抽象函数

又叫纯虚方法,用abstract修饰的方法,特点:只能在抽象类中申明,没有方法体,不能是私有的,继承后必须要实现,用override重写

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
abstract class Thing
{
//抽象类中 封装的所有知识点都可以在其中书写
public string name;

//可以在抽象类中写抽象函数
}

class Water:Thing
{

}

//抽象类不能被实例化
Thing t=new Water();//遵循里氏替换原则

//抽象方法
abstract class Fruits
{
public string name;

public abstract void Bad();//必须继承过后实现,不能是私有的,没有方法体,c++中的纯虚函数

public virtual void Test()
{
//虚方法可以选择是否写逻辑
}
}

class Apple:Fruits
{
public override void Bad()
{

}

public override void Test()
{

}//虚方法是可以由我们子类选择性来实现的
}

class SuperApple:Apple
{
//虚方法和抽象方法都可以被子类无限重写
public override void Bad()
{

}

public override void Test()
{

}
}

接口

概念:是行为的抽象规范,是一种自定义类型,关键字:interface

接口申明的规范:不包含成员变量,只包含方法、属性、索引器、事件,成员不能被实现,成员可以不用写访问修饰符,不能是私有的,接口不能继承类,但是可以继承另一个接口

接口的使用规范:类可以继承多个接口,类继承接口后,必许实现接口中的所有成员

特点:和类的声明类似,接口是用来继承的,接口不能被实例化,但是可以作为容器存储对象

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
//接口关键字:interface
//语法:
//interface 接口名
//{
//}
//接口是抽象行为的“基类”
//使用帕斯卡命名法,但是前面加个I

interface IFly
{
void Fly();//不需要方法体,不能加私有,不然违反实现接口中的所有成员

string Nmae//属性
{
get;
set;//get和set也不能有语法快
}

int this[int index]//索引器
{
get;
set;
}

event Action doSomething//事件
{

}
}

//接口的使用
class Animal
{

}//基类
//类可以继承一个类和多个接口
//继承了接口后,必须实现其中的内容,并且必须是public的
//接口成员若在内部实现,则在外部不能被实现
class Person:Animal,IFly
{
public virtual void Fly()//让子类可以重写这个函数
{

}

public string Name
{
get;
set;
}

public int this [int index]
{
get
{
return 0;
}
set
{

}
}

public event Action doSomething;
}//继承一个类和一个接口

//实现的接口函数,可以加virtual再在子类中重写
//接口也遵循里氏替换原则

IFly f=new Person();

接口的主要作用:类似于行为的多态,可以为不同种对象但是又有相同行为的对象提供方法

接口可以继承接口,不需要去实现,待类继承接口后,类自己去实现所有内容。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
//显式实现接口
//当一个类继承两个接口
//但是接口中存在着同名方法时

interface IAtk
{
void Atk();
}
interface ISuperAtk
{
void Atk();
}

class Player:IAtk,ISuperAtk
{
//显示实现接口,用接口名加行为名实现,主要用于不同接口之间同名函数的不同表现
public void IAtk.Atk()
{

}

public void ISuperAtk()
{

}
}


抽象类和接口的区别

抽象类和抽象方法:abstract修饰的类和方法,抽象类不能实例化,抽象方法只能在抽象类中申明,是纯虚方法,必须在子类中实现。

接口:interface 自定义类型,是行为的抽象,不包含成员变量,仅包含方法,属性,索引器,事件,成员都不能是实现,建议不写访问修饰符,默认public

相同点:都可以被继承,都不能直接实例化,都可以包含方法申明,子类必须实现未实现的方法,都遵循里氏替换原则。

不同点:抽象类中可以有构造函数,接口中不能,抽象类只能被单一继承,接口可以被继承多个,抽象类中可以有成员变量,接口中不能,抽象类中可以申明成员方法,虚方法,抽象方法,静态方法;接口只能申明没有实现的抽象方法,抽象类方法可以使用访问修饰符,接口中建议不写,默认public

使用:表示对象的用抽象类,表示行为拓展的用接口,不同对象拥有的共同行为,我们往往可以使用接口来实现

密封函数(不是很重要)

概念:用密封关键字sealed修饰的重写函数,作用:让虚方法或者抽象方法之后不能再被重写,特点:和override一起出现

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
abstract class Animal
{
public string name;
public abstract void Eat();
public virtual void Speak()
{
Console.WriteLine("叫");
}
}

class WhitePerson:Person
{
public override void Eat()
{
base.Eat();
}

public override void Speak()
{
base.Speak();
}


}

class Person:Animal
{
public sealed override void Eat()//加了sealed之后这个方法在之后的子类中无法被继承,如上面的
{

}
public override void Speak()
{

}
}

命名空间

概念:命名空间是用来组织和重用代码的,作用:就像是一个工具包,类就像是一件件的工具,都是申明在命名空间里的

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
//语法
namespace 命名空间名
{
//类
//类
}
namespace MyGame
{
class GameObject
{

}
}

namespace MyGame//可以直接分开写,和上面的命名空间是同一个
{
class Player:GameObject
{

}
}

using 命名空间名//引用命名空间写最上面

//不同命名空间允许有同名类
//使用同名类的话要点出命名空间名
//命名空间可以包裹命名空间
//如果直接引用了最外面的类,那么被包裹的的命名空间的类将不能被使用

//关于修饰类的访问修饰符
//internal 只能在该程序集中使用 命名空间中的类默认为internal,本地的
//abstract 抽象类
//sealed 密封类
//partial 分布类

万物之父中的方法

object中的静态方法

1
2
3
4
5
6
7
8
9
10
11
12
13
14
//Equals 判断两个对象是否相等
//最终的判断权,交给左侧对象的Equals方法
//不管值类型引用类型都会按照左侧Equals方法的规则来进行比较
class Test
{
}

Test t=new Test();
Test t2=new Test();
Object.Equals(t,t2);//返回值为false,因为二者的地址不同

//referenceEquals 比较两个对象是否是相同的引用,主要是用来比较引用类型的对象
Object.referenceEquals(t2,t)//返回的也是false,专门用来比引用的东西

object中的成员方法

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
//普通方法GetType
//该方法在反射中是非常重要的方法
//主要作用是获取对象运行时的type

Test t=new Test();
Type type =t.GetType();

//普通方法MemberwiseClone
//该方法用于获取对象的浅拷贝对象,口语化的意思就是会返回一个新的对象
//但是新对象中的引用变量会和老对象中的一致
//类似于as关键字

class Test
{
public int i=1;
public Test2 t2=new Test();
public Test clone()//因为MemberwiseClone的访问修饰符为protected,无法在外部调用
{
return MemberwiseClone() as Test;
}
}


class Test2
{
public int i=2;
}

Test t2=t.Clone();//得到个新地址,里头的值类型地址发生了改变,里头的引用类型的地址和t完全一致
Console.WriteLine(t.i);
Console.WriteLine(t.t2.i);
Console.WriteLine(t2.i);
Console.WriteLine(t2.t2.i);

t2.i=20;
t2.t2.i=21;

object中的虚方法

1
2
3
4
5
6
7
8
9
10
11
12
13
//虚方法Equals
//默认实现还是比较两者是否为同一个引用,相当于ReferenceEquals。
//但是微软在所有值类型的父类System.ValueType中重写了该方法,用来比较值相等。
//我们也可以重写该方法,定义自己的比较相等的规则

//虚方法GetHashCode
//该方法是获取对象的哈希码
//(一种通过算法算出的,表示对象的唯一编码,不同对象哈希码可能一样,具体指根据哈希算法决定)
//我们可以通过重写该函数来定义对象的哈希码算法,正常情况下,我们使用的极少,基本不用

//虚方法ToString
//该方法用于返回对象代表的字符串,我们可以重写他定义我们自己的对象转字符串规则
//该方法非常常用,但我们调用打印方法时,默认使用的就是对象的ToString方法后打印出的内容

string类型

字符串本质是一个char数组

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
//字符串指定位置的获取
string str="田鼠浩二";
Console.WriteLine(str[0]);//打印出的是田,string类里面写了个索引器
//string转成char数组
char[]chars=str.ToCharArray();
Console.WriteLine(chars[1]);//打印出鼠

for(int i=0;i<str.Length;i++)
{
Console.WriteLine(str[i]);
}

//字符串拼接
str=string.Format("{0}{1}",1,3333);
console.WriteLine(str);//打印出13333

//正向查找字符的位置

str="我是田鼠浩二";
str.indexOf("田");//返回的是int 2(下标)

int index=str.IndexOf("吊");//返回的是-1(没找到)
Console.WriteLine(index);//打印出的是-1

//反向查找字符的位置

str="我是田鼠浩二田鼠浩二";
index=str.LastIndexOf("田鼠浩二");//打印出的是6

//没找到返回的是-1

//移除指定位置后的字符
str="我是田鼠浩二田鼠浩二";
str=str.Remove(4);//返回一个新字符串,删除了第四个位置开始(包括第四个位置)的字符串
Console.WriteLine(str);//我是田鼠

//执行两个参数移除
//参数一 开始位置
//参数二 字符个数
str=str.Remove(1,1);//返回的是 我田鼠

//替换指定字符串

str="我是田鼠浩二田鼠浩二";
str=str.Replace("田鼠浩二","田所浩二");//返回的是新字符串
Console.WriteLine(str);//会将所有字符串进行替换

//大小写转换
str="naohdflkdsafs";
str=str.ToUpper();//返回新字符串
Console.WriteLine(str);//小写字母全转成大写
str=str.ToLower();
Console.WriteLine(str);//大写字母全转成小写字母

//字符串截取
//截取从指定位置开始之后的字符串
str="田鼠浩二";
str.Substring(2);
Console.WriteLine(str);//打印出的是浩二

//重载
//第一个参数是位置,第二个是个数
str=str.Substring(2,3);
Console.WriteLine(str);//报错,长度越界了,不会自动识别越界
str=str.Substring(0,1);
Console.WriteLine(str);//打印出的是二

//字符串切割(较重要)
str="1,2,3,4,5,6,7,8";
string[]strs=str.Split(',');//传进去的是字符,将逗号进行切割
for(int i=0;i<strs.Length;i++)
{
Console.WriteLine(strs[i]);
}
//打印出的是
//1
//2
//3
//4
//5
//6
//7
//8

StringBuilder

优势是可以预先分配内存,避免过度浪费,其他和string差不多

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
//string是特殊的引用
//每次重新赋值或者拼接都会分配新的空间
//如果一个字符串经常改变会非常浪费空间

//StringBuilder
//c#提供的一个用于处理字符串的公共类
//修改字符串而不创建新的对象,可以提升性能
//使用时需要引用命名空间(System.Test)
StringBuilder str=new StringBuilder("123123123");
Console.WriteLine(str);//打印出123123123
//StringBuilder存在一个容量的问题,每次往里面增加时,会自动扩容
//获得容量
Console.WriteLine(str.Capacity);//打印出16,说明后面有多个空位
//每次容量越界都会乘以2,也就是容量的大小是2的指数,也会产生垃圾,但相比原来要小得多
//获得字符长度
Console.writeLine(str.Length);

//增删查改操作
//增加
str.Append("3333");//在后面加3333

str.AppendFormat("{0}{1}",100,200);//也有这种拼接的形式加字符串

//插入
str.Insert(0,"田鼠浩二");//位置,插入字符

//删除
str.Remove(010);//位置,长度(同样要注意越界问题,不会识别越界)

//清空
str.Clear();

//查找
Console.WriteLine(str[0)];

//改
str[0]='A';//string的索引器是只读的,但是stringbuilder可以

//替换
str.Replace("1","田");//被替换字符,替换字符

//重新赋值
str.Clear();//先清空
str.Append("123");//再添加

//判断相等
//Equals(object中的重载)
str.Equals("123")//返回true

类和结构体的区别

类是引用类型,结构体是值类型。都可以用来形容对象,结构体没有继承和多态的特性,结构体不具备继承的特性,不能用protected保护访问修饰符。结构体成员变量不能指定初始值,结构体不能申明无参的构造函数,结构体申明有参构造函数后,无参的不会被顶掉,结构体不能申明析构函数,结构体不能被静态static修饰,类可以,结构体不能在自己内部申明和自己一样的结构体变量,类可以。

结构体可以继承接口,因为接口是行为的抽象。

选择:想要用继承和多态是直接用类(比如玩家,怪物等),对象是数据集合时优先考虑结构体(比如位置,坐标等)

从值类型和引用类型赋值时的区别考虑,比如经常被赋值传递的对象,并且改变赋值对象,原对象不想跟着变化时,用结构体,比如坐标,向量,旋转等。

多脚本文件

新建脚本文件:

一般一个类一个脚本,一个结构体一个脚本,一个接口一个脚本,枚举在特殊情况下也可以一个脚本。

在文件夹中新建脚本文件:

可以将相同类型的脚本放在一个文件夹中

c#脚本文件后缀是.cs c#解决方案中的一些重要文件夹 如bin 解决方案资源管理器窗口 工程右键 点添加

文件中新建脚本文件需要注意命名空间改变

UML类图

统一建模语言(Unified Modeling Language,UML)是一种为面向对象系统的产品进行说明、可视化和编制文档的一种标准语言,是非专利的第三代建模和规约语言。UML是面向对象设计的建模工具,独立于任何程序设计语言。

使用一些高级的UML可视化软件,不用写代码,通过做一些图表相关内容就可以直接生成代码,在其基础上进行开发。他的最终目标是直接能通过图形就把业务逻辑完成

UML类图是UML其中很小的一部分,可以帮助我们理清对象间关系

面向对象七大原则

单一职责原则(SRP)

类被修改的几率很大,因此应该专注于单一的功能。如果把多个功能放在同一个类中,功能之间就形成了关联,改变其中一个功能,可能终止另一个功能。例如程序,策划,美术三个工种是三个类,应该各司其职,只做自己的工作

开闭原则(OCP)

对拓展开发,对修改关闭。拓展开放:模块的行为可以被拓展从而满足新的需求 拓展关闭:不允许修改模块的源代码(或者尽量使修改最小化)继承就是最典型的开闭原则的体现,可以通过添加子类和重写父类的方法来实现。

里氏替换原则(LSP)

任何父类出现的地方,子类都可以替代。

依赖倒转原则(DIP)

要依赖与抽象,不要依赖于具体的实现。

迪米特原则(LoP)

最少知识原则,一个对象应当对其它对象尽可能少的了解,不要和陌生人说话。

一个对象的成员,要尽可能少的直接和其他类建立关系,降低耦合性

接口分离原则(ISP)

不应该强迫别人依赖他们不需要使用的方法,一个接口不需要提供太多的行为,一个接口应该尽量只提供一个对外的功能,让别人去选择需要实现什么样的行为,而不是把所有行为封装到一个接口里。

合成复用原则(CRP)

尽量使用对象组合,而不是继承来达到复用的目的,继承关系是强耦合,组合关系是低耦合,例如脸是眼睛、鼻子、嘴巴、耳朵的组合,而不是继承,角色和装备也是组合。

数据结构

ArrayList

本质是一个object类型的数组,在system.collections命名空间里。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
ArrayList array=new ArrayList();

//增加
array.Add();//里面放内容,因为是object类,所以可以放任何类型
ArrayList array2=new ArrayList();
//批量增加
array.AddRange(array2);//将array2的所有内容加到array中
//插入
array.Insert(1,"1234565");//第一个参数是位置下标,第二个是元素内容


//删除
array.Remove();//里面写具体要删除的元素,全部元素都会被删除
//移除指定位置的元素
array.RemoveAt(2);//写下标

//查
array[0];//返回第一个元素,和数组相同

//查看元素是否存在
arr.Contains()//里面具体写元素,存在返回true,否则返回false

//正向查找元素位置
array.IndexOf();//里面具体填写要找的元素,找到返回第一个找到的下标,没找到返回-1

//反向查找元素位置
array.LastIndexOf();//这个是返回的是从后面找到的第一个的下标

//改
array[0]="000";//直接赋值

//遍历
//当前长度
array.Count
//容量
array.capacity

for(int i=0;i<array,Count;i++)
{
Console.WriteLine(array[i]);
}

//迭代器遍历
foreach(object item in array)//每次都将array中元素放入item中再操作
{
Console.WriteLine(item);
}

//装箱拆箱
//将值类型装入就是装箱,将值对象取出来转换使用就是拆箱
int i=1;
array[0]=i;//装箱
i=(int)array[0];//拆箱

Stack

栈,先进后出,本质也是一个object数组,只不过封装了特殊的存储规则

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
Stack stack=new Stack();

//压栈
stack.Push();//太熟悉了.jpg

//弹出
stack.Pop();//太熟悉了.jpg,返回其中的元素

//查
stack.Peek();//查看栈顶,返回栈顶元素但不弹出

//查看元素是否存在于栈中
stack.Contains();//返回bool值

//清空
stack.Clear();

//遍历
//长度
stack.Count
//遍历顺序:从栈顶到栈底
//迭代器
foreach(object temp in stack)
{
Console.WriteLine(temp);//从栈顶到栈底
}

//另一种遍历方法
//将栈转换为object数组
object[]array=stack.ToArray();
for(int i=0;i<array.Length;i++)
{
//遍历数组
}

//循环弹栈
while(stack.Count>0)
{
object o=stack.Pop();
Console.WriteLine(o);
}

Queue

队列,先进先出,本质也是一个object数组,只不过封装了特殊的存储规则

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
Queue queue=new Queue();
//入队
queue.Enqueue();//不多说了,累死了
//出队
queue.Dequeue();//返回队头
//查
queue.Peek();//和栈一样

if(queue.Contains())//这个也是一样的
{

}

//改
//和栈还是一样
queue.Clear();

//遍历
//长度
queue.Count;
foreach(object item in queue)//万能的迭代器
{
//遍历
}

//还有一种遍历和栈一样转数组,不多说了

//循环出列
//这个和栈还是一样
//不多说了。。。

HashTable

哈希表,也就是散列表,使用键来访问集合中的元素,就是c++中的map

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
Hashtable hashtable=new Hashtable();

//增加
hashtable.Add(1,"123");//1是键,“123”是值
hashtable.Add("123",2);//这个"123"是键,2是值
hashtable.Add(true,true);//类似
//不能出现相同的键

//删除
//只能通过键去删
hashtable.Remove();//里面写键,删除对应的键和元素
//删除不存在的键没有反应,不会报错

//清空
hashtable.Clear();

//查
//通过键查看值
//找不到返回空
hashtable[键];//键对应的值

//查看是否存在
//根据键检测
hashtable.Contain(键);//返回的是bool值
hashtable.ContainKey(键);//同上

//根据值检测
hashtable.ContainValue(值);//返回的是bool值,里面写值

//改
//只能改键对应的值内容 无法修改键

//遍历
//得到对数
hashtable.Count;//键的对数
//遍历所有键
foreach(object item in hashtable.Keys)
{
Console.WriteLine(item);//键
Console.writeLine(hashtable[item]);//值
}
//遍历所有值
foreach(object item in hashtable.Values)
{
Console.WriteLine(item);//值
}
//键值一起遍历
foreach(DictionaryEntry object item in hashtable)
{
Console.WriteLine(item.key,item.Value);//键,值
}
//迭代器遍历
IDictionaryEnumerator myEnumerator= hashtable.GetEumerator();
bool flag=myEnumerator.MoveNext();
while(flag)
{
Console.WriteLine(myEnumerator.Key,myEnumerator,Value);//键,值
}
//装箱拆箱
//不多说了

泛型

概念:实现了类型参数化,达到代码重用的目的,相当于类型占位符,定义类或者方法时使用替代符代表变量类型,当真正使用类或者方法时再具体指定类型。(c++中vector类)

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
//泛型类和泛型接口
//基本语法
class 类名<泛型占位字母>
interface 接口名<泛型占位字母>
//泛型函数
//基本语法
函数名<泛型占位字母>(参数列表)
//泛型占位字母可以有多个,用逗号隔开

//泛型类和接口

class TestClass<T>
{
public T value;
}

TestClass<int> t=new TestClass<int>();
t.value=10;
Console.WriteLine(t.value);

TestClass<string>t2=new TestClass<string>();

泛型约束

概念:让泛型有一定的限制,关键字:where

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
//泛型约束一共有6种
//值类型 where 泛型字母:struct
//引用类型 where 泛型字母:class
//存在无参公共构造函数 where 泛型字母:new()
//某个类本身 where 泛型字母:类名
//某个接口的派生类型 where 泛型字母:接口名
//另一个泛型类型本身或者派生类型 where 泛型字母:另一个泛型字母

//where 泛型字母:(约束的类型)

//值类型约束
class Test1<T>where T:struct//只能填值类型了
{
public T value;

public void TextFun<K>(K,k)where K:struct
{

}
}

class Test2<T>where T:class
{
public T value;

public void TestFun<K>(K,v) where K:class//只能填引用类型

public Test2(int a)
{

}
}

class Test3<T>where T:new()
{
public T value;

public void TestFun(K,k)where K:new()//只能填公共无参构造
{

}
}

//其他类型不详细讲了


void main()
{
Test1<object>t1=new Test1<object>();//报错
Test1<int>t2=new Test2<int>();

Test3<Test1>t3=new Test3<Test1>();
Test3<Test2>t4=new Test3<Test2>();//报错,Test2里面申明了有参构造函数将无参构造顶掉了
}

泛型数据结构类

List

是c#中封装好的一个类,是可变类型的泛型数组,List帮助我们实现了许多方法,类似于cpp中的vector

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
//申明
List<int>list=new List<int>();
List<string>list2=new List<bool>();

//增
list.Add();

//删除
list.Remove();//删除指定元素
list.RemoveAt();//删除指定下标元素

//查
list[0];//和vector一样
list.IndexOf();//查找
list.LastIndexOf();//反向查找

//改
list.Insert();//插入指定位置

//遍历
//长度
list.Count;
//容量
list.Capacity;

foreach(int temp in list)
{
Console.WriteLine(temp);
}

Dictionary

字典,有泛型的hashtable,就是hashtable,尽量用这个,别用非泛型类型,这个类型和hashtable的使用方法是一样的,这里笔者不多说了,和hashtable区别:找不到键的话直接报错,用TryGetValue方法不会报错

LinkedList

封装好的类,是一个泛型的双向链表

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
//申明
LinkedList<int>linkedlist=new LinkedList<int>();

//尾部添加元素
linkedlist.AddLast();
//头部增加元素
linkedlist.AddFirst();
//在某一个节点之后添加节点
LinkedListNode<int>n=linkedlist.Find(20);
linkedlist.AddAfter(n,12);//在20元素的节点后插入加一个12的节点
//在某一个节点值之前
AddBefore()//用法和AddAfter()一样

//删
//移除头节点
linkedlist.RemoveFirst();
//移除尾节点
linkedlist.RemoveLast();
//移除指定节点
linkedlist.Remove(具体元素);

//清空
linkedlist.Clear();

//查
//头节点
LinkedListNode<int>first=linkedlist.First;
//尾节点
LinkedListNode<int>last=linkedlist.Last;
//指定节点
LinkedListNode<int>node=linkedlist.Find(具体元素);//没找到返回null
//是否存在
linkedlist.Contains(元素);

//改
//先用查找得到节点
//再更改
node.value=新值;

//遍历
foreach(int item in linkedlist)
{
Console.WriteLine(item);//直接得到里面的值
}

泛型栈和队列

和c++中的一样

委托

概念:委托是函数的容器,可以理解为表示函数的变量类型,用来存储传递函数,委托的本质是一个类,用来定义函数的类型,不同的函数必须对应和各自“格式”一致的委托。(类似于函数指针),可以作为某些触发机制,在前置条件完成后,将委托中的函数(行为)全部执行

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
//关键字
//delegate
//语法:访问修饰符 delegate 返回值 委托名(参数列表);

//写在哪里?
//可以申明在namespace和class语句块中
//更多的写在namespace中
//就是函数申明语法前加了一个delegate关键字

//定义自定义委托
//访问修饰符默认不写,为public,在别的命名空间也能用
//private 其他命名空间就不能用了

//申明了一个可以用来存储无参无返回值函数的容器
//这里只是定义了规则 并没有使用
delegate void MyFun();
//同一语句块中,委托规则的申明是不能重名的
//用来装载或传递 返回值为int 有一个int参数的函数的 委托 容器规则
delegate int MyFun2(int a);

static void Fun()
{
Console.WriteLine("你好");
}

static int Fun2(int value)
{
return value;
}

Main()
{
//专门用来装载函数的容器
MyFun f=new MyFun(Fun);//相当于将Fun()装载到了MyFun这个委托里面
//调用委托
f,Invoke();
//作用:将函数存起来,之后调用
//第二种方法
MyFun f2=Fun;
//调用委托
f2();

Test t=new Test();

t.TestFun(Fun,Fun2);
}

//委托常用在
//1.作为类的成员
//2.作为函数的参数

class Test
{
public MyFun fun;
public MyFun2 fun2;//两个委托类型的变量

public void TestFun(MyFun fun,MyFun fun2)
{
//先处理一些别的逻辑,当这些逻辑处理完了,再执行传入的函数(延迟执行函数)
int i=1;
i+=2;
i+=2;
fun();
fun2(i);
this.fun=fun;
this.fun2=fun2;
}
//删除
//如果委托容器中没有对应函数,也不会报错
public void RemoveFun(MyFun fun,MyFun2 fun2)
{
this.fun-=fun;
this.fun2-=fun2;
}
}

//委托变量可以存储多个函数

MyFun ff=null;

ff+=Fun;//委托增加函数

ff+=Fun;

ff();//执行两次Fan()

//清空容器
ff=null;
if(ff!=null)//如果ff不为空,执行
{
ff();
}


系统定义好的委托(使用系统自带的委托需要引用system):

1
2
3
4
public delegate void Action();//无返回值无参函数
public delegate TResult Func<out TResult>();//泛型委托,可以自己填写任意类型返回值存储无参函数的委托
public delegate void Action<in T>(T obj);//可以填最多16个参数的无返回值函数都可以放进去,系统里申明了16个委托
public delegate TResult Func<in T,out TResult>(T arg);//有返回值的多参数委托,也是最多16个委托

回调机制(补充)

回调是一种编程模式,其中一个函数作为参数传递给另一个函数,并在特定条件满足或者事件发生时调用。

一般可以使用委托实现。因为在回调中通常只存在一个订阅者,所以强调的是完成时调用我的单向通知,

调用者是知道被调用者的。

观察者模式(补充)

观察者模式是一种行为设计模式,定义了对象间的一对多依赖关系,当一个对象状态改变时,所有依赖它的对象多会自动收到通知并更新。一般可以使用事件实现。因为观察者模式中通常有多个订阅者,强调“‘某事发生了”的广播通知,让多个订阅者知道事件的发生

事件

事件是基于委托的存在,事件是委托的安全包裹,让委托的使用更具有安全性,事件是一种特殊的变量类型,是特殊封装的委托。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
//语法
//访问修饰符 event 委托类型 时间名;
//事件的使用:
//事件是作为成员变量存在于类中
//委托怎么用,事件就怎么用
//事件相对于委托的区别
//不能在类外部赋值
//不能再类外部调用
//只能作为成员存在于类和接口以及结构体中
class Test
{
//委托成员变量 用于存储函数的
public Action myFun;
//事件成员变量 用于存储函数的
public event Action myEvent;

public Test()
{
//事件和委托的使用一摸一样,只是有些细微的区别
myFun=TestFun;
myEvent=TestFun;
myEvent+=TestFun;//也可以使用+-运算符
}

public void TestFun()
{

}
}


class Project
{

static void Main(string[] args)
{
Test t = new Test();

t.myFun = null;//可以

t.myEvent = null;//报错,事件不能在类外部赋值

t.myFun();//可以

t.myEvent();//报错,事件不能再类外部调用
}


}

//事件有什么用
//防止外部随意清空委托
//防止外部随意调用委托
//事件相当于给委托进行了一次封装,使其更加安全

空条件运算符(补充)

在调用委托和事件时通常要判断是否为空,具体可写一个条件语句判断,这里可以用更加简洁的空条件运算符(?.)

1
2
3
4
5
6
if(event!=null)
{
event();
}
//和上面完全等价
event?.();

匿名函数

概念:没有名字的函数,主要是配合委托和事件进行使用,脱离委托和事件是不会使用匿名函数的

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
//基本语法
//delegate(参数列表)
//{
//函数逻辑
//};
//何时使用
//函数中传递委托参数时
//委托或事件赋值时

//使用
//无参数无返回
//这样申明匿名函数,只是声明函数而已,还没有调用
Action a = delegate()//声明了一个没有名字的函数直接放进了委托中
{
Console.WriteLine("匿名函数逻辑");
};

a();//打印出上述函数内容

//有参数
Action<int,string> b = delegate(int a,string b)
{
Console.WriteLine(a);
Console.WriteLine(b);
};

b(100,"123");

//有返回值
Func<string> c= delegate ()
{
return "12345";//根据返回值自动判断返回类型
}

Console.WriteLine(c());//打印出"12345"

//一般情况下会作为函数参数传递,或者作为函数返回值
Test t=new Test();
//参数传递
t.Dosomething(100,delegate(){

//这里可以写逻辑
//可以直接现写一个函数放进委托里面
});
//返回值
t.GetFun()();//t.GetFun返回的是委托,后面的括号相当于是调用委托

class Test
{
public Action action;
//作为参数传递时
public void Dosomething(int a,Action fun)
{
Console.WriteLine(a);
fun();
}
//作为返回值
public Action GetFun()
{
return delegate()//函数可以看作是特殊的委托,所以可以直接返回函数
{
Console.WriteLine("函数内部返回的一个匿名函数逻辑");
};
}
}

//匿名函数的缺点
//因为没有名字,单独添加到委托或者事件容器中后不记录 无法单独移除
//就算减等逻辑一样的匿名函数也没有作用

lambda表达式

可以将lambda表达式理解为匿名函数的简写,他除了写法不同外,使用上和你匿名函数一样,都是和委托和事件配合使用的

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
//lambda表达式
//(参数列表)=>
//{
//函数体
//}

//使用
//无参无返回值
Action a = ()=>
{
Console.WriteLine(" ");
};
a();
//有参
Action<int> a =(int b)=>
{
//有参数的lambda表达式
};
a2(100);
//参数类型都可以省略 参数类型和委托或者事件容器一致
Action<int>a3= (value)=>
{
//省略了参数类型
}
//有返回值
Func<string,int>a4=(value)=>
{
return 1;
}

//闭包
//内层的函数可以引用包含在他外层的函数的变量(延长变量的生命周期)
//即使外层的函数已经终止
//该变量提供的值并非创建时的值,而是在父函数范围内的最终值

class Test
{
public event Action action;

public Test()
{
int value=30;

action=()=>
{
Console.WriteLine(value);
};
}
}

List排序

List自带的排序方法

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
List<int>list=new List<int>();
list.Add(2);
list.Add(3);
list.Add(1);
list.Sort();//排序方法

//自定义类的排序
class Item:IComparable<Item>//继承排序接口
{
public int money;

public Item(int money)
{
this.money=money;
}

public int Comparable(Item other)
{
//返回值的含义
//小于0
//放在传入对象的前面
//等于0
//保持当前对象的位置不变
//大于0
//放在传入对象的后面

if(this.money>other.money)
{
return 1;
}
//写成return this.money>other.money也可以

else
{
return -1;
}
}
}

List<Item>itemList=new List<Item>();

itemList.Add(new Item(34));
itemList.Add(new Item(12));
itemList.Add(new Item(23));
itemList.Add(new Item(90));
itemList.Add(new Item(89));
//调用排序方法
itemList.Sort();

//通过委托函数进行排序
class ShopItem
{
public int id;
public ShopItem(int id)
{
this.id=id;
}
}

List<ShopItem>shopItems=new List<ShopItem>();
shopItems.Add(new ShopItem(3));
shopItems.Add(new ShopItem(2));
shopItems.Add(new ShopItem(1));
shopItems.Add(new ShopItem(6));
shopItems.Add(new ShopItem(9));

static int SortShopItem(ShopItem a,ShopItem b)
{
//传入的两个对象为列表的两个对象
//进行两两比较,用左边的和右边的条件比较
//返回值规则和之前的一样 0做标准 负数在左 正数在右
if(a.id>b.id)
{
return 1;
}

else return -1;
return 0;
}

//用匿名函数写
ShopItems.Sort((a,b)=>{return a.id-b.id};);//非常精简

shopItems.Sort(SortShopItem);//直接把函数传进去,使用排序函数


协变逆变

概念:协变:和谐的变化。因为里氏替换原则,父类可以装载子类,比如string变成object。

逆变:不正常的变化。比如object变成string。

协变和逆变是用来修饰泛型的

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
//协变:out
//逆变:in
//用于在泛型中修饰泛型字母的
//只有泛型接口和泛型委托能使用

//作用
//返回值和参数
//用out修饰的泛型,只能作为返回值
delegate T TestOut<out T>();
//用in修饰的泛型,只能作为参数
delegate void TestIn<in T>(T t);

//示例
delegate T TestOut<out T>();

delegate void TestIn<in T>(T t);

class Father
{

}

class Son:Father
{

}

Main()
{
//协变 父类总是可以被子类替换
//看起来就是son -->father

TestOut<Son>os=()=>
{
reutrn new Son();
};

TestOut<Father>of=os;//放回的是Son类型
Father f=of();

//逆变
TestIn<Father>iF=(value)=>
{

};

TestIn<Son>iS=iF;
iS(new Son())//实际上调用的是 iF的函数,但是传入的是Son类型变量
//两个都符合里氏替换原则
}


多线程

进程(process)是计算机的程序关于某数据集合上的一次运行活动,是系统进行资源分配和调度的基本单位,是操作系统结构的基础。打开一次应用程序就是在操作系统中开启了一个进程,进程之间可以相互独立运行,互不干扰,也可以相互访问,操作。

线程,操作系统中能够进行运算调度的最小单位。它被包含在进程之中,是进程中的实际运作单位,一条线程指的是进程中一个单一顺序的控制流,一个进程中可以并发多个线程(简单理解:就是代码从上到下运行的一条管道)

多线程

我们可以通过代码开启新的线程。

同时运行代码的多条管道,就叫多线程。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
//线程类 Thread
//需要引用命名空间 using System.Threading;
//申明一个新的线程
//线程执行中的代码需要封装到一个函数中
Thread t=new Thread(NewThreadLogic);

//启动线程
t.Start();

//设置为后台线程
//当前台线程结束了的时候,整个程序就结束了,即使还有后台线程正在运行
//后台线程不会防止应用程序的进程被终止掉
//如果不设置为后台线程,可能导致进程无法正常关闭
t.IsBackGround=true;//设置为后台线程

//关闭释放一个线程
//如果开启的线程不是死循环,是能够结束的逻辑,那么不用特意的去关闭它
//如果是死循环,想要终止这个线程有两种方法
//死循环中bool标识

//通过线程中提供的方法
//终止线程
t.ABort();
t=null;
static void NewThreadLogic()
{
while(true)
{

}
//新开线程 执行的代码逻辑
}

//线程休眠
//让线程休眠多少毫秒
Thread.Sleep();

//多线程之间共享数据
//多个线程使用的内存是共享的,都属于该应用程序
//所以要注意 当多线程 同时操作同一片内存区域时可能会出问题
//可以通过加锁的形式避免问题

//lock
//当我们想要在多个线程中访问同样的东西,进行逻辑处理时
//为了避免不必要的逻辑顺序执行的差错
//lock(引用类型对象)
//锁住同一个变量,当发现该变量没有被占用时再去执行代码块中的逻辑

//多线程的意义
//寻路和网络通信等等

预处理器指令

编译器:编译器是一种翻译程序,它用于将源语言程序翻译为目标语言程序

源语言程序:某种程序设计语言写成的,比如c#、c++等语言写成的程序

目标语言程序:二进制数表示的伪机器代码写的程序

预处理器指令:知道编译器在实际编译开始之前对信息进行预处理

预处理器都是以#开始

预处理器指令不是语句,所以他们不以分号结束,目前我们经常用到的折叠代码块就是预处理器指令

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
//#define //定义一个符号
//写在脚本文件最前面的
//#undef //取消定义一个符号
//#if
//#elif
//#else
//#endif
//和if语句规则一样,一般配合#define定义的符号使用
//用于告诉编译器进行编译代码的流程控制

//#warning
//#error
//告诉编译器
//是报警告还是报错误
//一般还是配合if使用

反射

程序集:是经由编译器编译得到的,供进一步编译执行的那个中间产物,在windows系统中,他一般表现为后缀为:.dll(代码库文件),或者是.exe(可执行文件)的格式。

程序集就是我们写的一个代码集合,我们现在的所有代码最终都会被编译器翻译为一个程序集供别人使用

元数据: 用来描述数据的数据。程序中的类、变量等等信息就是程序的元数据,保存在数据集中

反射:程序在运行时可以查看其它程序集或者自身的元数据。一个运行的程序查看本身或者其他程序的元数据的行为就叫做反射。就是在程序运行时,通过反射可以得到其他程序及或者他们自己程序集代码的各种信息。类、函数、变量、对象等等。实例化他们,执行他们。操作他们

反射可以在程序编译后获得信息,所以它提高了程序的拓展性和灵活性。

程序运行时得到所有元数据,包括元数据的特性。

程序运行时,实例化对象,操作对象

程序运行时创建新对象,用这些对象执行任务

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
class Test
{
private int i=1;
public int j=0;
public string str="123";

public Test()
{

}

public Test(int i)
{
this.i=i;
}

public Test(int i,string str):this(i)
{
this.str=str;
}

public void Speak()
{

}
}

//语法相关
//Type
//它是反射功能的基础
//是访问元数据的主要方式
//使用Type的成员获取有关类型声明的信息
//有关类型的成员(如构造函数、方法、字段、属性和类的事件)

//获取Type
//万物之父object中的GetType()可以获取对象的Type
int a=32;
Type type=a.GetType();
Console.Write(Type);
//打印出System Int32

//通过typeof关键字 传入类名 也能得到对象的Type
Type type2=typeof(int);
Console.Write(type2);
//同样可以得到类型

//通过类的名字也可以获得类型
//类名要包含命名空间
Type type3=Type.GetType("System.Int32");
Console.WriteLine(type3);

//上面三个type指向的内存都是一样的

//得到类的程序集信息
//可以通过Type得到类型所在的程序集信息
Console.WriteLine(type1.Assembly);f
//打印出对应的程序集信息

//获取类中的所有的公共成员
//首先得到对方的Type
Type t=typeof(Test);
//然后得到所有公共成员
//需要引用命名空间 using System.Reflection;
MemberInfo[]infos=t.GetMembers();
for(int i=0;i<infos.Length;i++)
{
Console.WriteLine(infos[i]);
}//得到所有公共成员

//获取类的公共构造函数并调用
//获取所有构造函数
ConstructorInfo[] ctors=t.GetConstructors();
for(int i=0;i<ctors.Length;i++)
{
Console.WriteLine(ctors[i]);
}

//获取其中一个构造函数并执行
//得到构造函数传入 Type数组 数组中内容按顺序是参数类型
//执行构造函数传入 object数组 表示按顺序传入的参数
//得到无参构造
ConstructInfo info=info=t.GetConstructor(new Type[0]);
//执行无参构造 无参构造 没有参数 传null即可
Test obj=info.Invoke(null) as Test;//返回的是object变量,调用构造函数返回的Test类型,所以实际存放的又是Test类型,要用as转成Test

//得到有参构造
ConstructInfo info2=t.GetConstructor(new Type[] {typeof(int)});
obj=info2.Invoke(new object[]{2})as Test;
Console.WriteLine(obj.str);

//获取所有成员变量信息
FieldInfo[] fieldInfos=t.GetFields();
for(int i=0;i<fieldinfos.Length;i++)
{
Console.WriteLine(fieldinfos[i]);
}
//得到指定名称的公共成员变量信息
FieldInfo infoJ=t.GetField("j");//传变量名
Console.WriteLine(infoJ);
//通过反射获取和设置对象的值
Test test=new Test();
test.j=99;
test.str="2222;
//通过反射 获取对象的某个变量的值
Console.WriteLine(infoJ.GetValue(test));
//通过反射 设置指定对象的某个变量的值
infoJ.SetValue(test,100);
Console.WriteLine(infoJ.GetValue(Test));

//获取类的公共成员方法
//通过Type类中的GetMethod方法,得到类中的方法
//MethodInfo是方法的反射信息
Type strType=typeof(string);
//如果存在方法重载 用Type数组表示参数类型
MethodInfo[] methods=strType.GetMethods();
for(int i=0;i<methods.Length;i++)
{
Console.WriteLine(methods[i]);
}

//Activator
//用于快速实例化对象的类
//用于将Type对象快捷实例化为对象
//先得到Type
//然后 快速实例化一个对象
Type testType =typeof(Test);
//无参构造
Test testObj=Activator.CreateInstace(testType) as Test;
Console.WriteLine(testObj.str);
//有参数构造
testObj=Activator.CreateInstance(testType,99)as Test;//调用一个参数的构造函数
Console.WriteLine(testObj.j);

testObj=Activator.CreateInstance(testType,88,"14566")as Test;
Console.WriteLine(testObj.j,testObj.str);

//Assembly
//程序集类
//主要用来加载其他程序集,加载后
//才能用Type来使用其他程序集中的信息
//比如想要使用不是自己程序集中的内容,首先要加载程序集
//比如 dll文件(库文件)
//简单地把库文件看成一种代码仓库,他提供给使用者一些可以直接拿来用的变量,函数或类

//三种加载程序集的函数
//一般用来加载在同一文件下的其他程序集
//Assembly assembly=Assembly.Load("程序集名称");(同一工程)
//Assembly assembly2=Assembly.LoadFrom("包含程序集清单的文件的名称或路径");
//Assembly assembly3=Assembly.LoadFile("要加载文件的完全限定路径");

//先加载一个指定程序集
Assembly assmbly=Assembly.LoadFrom("");
Type[] types=assembly.GetTypes();
for(int i=0;i<types.Length;i++)
{
Console.WriteLine(types[i]);
}
//之后再加载程序集中的一个类对象才能使用反射
Type icon=assembly.GetType("");
MemberInfo[] members=icon.GetMembers();
for(int i=0;i<members.Length;i++)
{
Console.WriteLine(Members[i]);
}
//通过反射实例化icon对象
//略

特性

特性是一种允许我们想程序的程序集添加元数据的语言结构,是用于保存程序结构信息的某种特殊类型的类。特性提供功能强大的方法以声明信息与c#代码相关联,特性与程序实体相关后即可在运行时使用反射查询特性信息。特性的目的是告诉编译器把程序结构的某组元数据嵌入程序集中。可以放置在几乎所有的声明中。特性本质是一个类,可以利用特性类为元数据添加额外信息,之后可以用反射来获取这些额外信息。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
//自定义特性
//继承特性基类
class MyCustomAttribute:Attribute
{
//特性中的成员 一般根据需求来写
public string info;

public MyCustomAttribute(string info)
{
this.info=info;
}

public void TestFun()
{
Console.WriteLine("特性的方法");
}
}
//特性的使用
//基本语法
//[特姓名(参数列表)]
//本质上 就是在调用特性类的构造函数
[MyCustom("田鼠浩二")]//Attribute被系统默认省略
class MyClass
{
[MyCustom("这是一个成员变量")]
public int value;

[MuCustom("计算加法的函数")]
public void TestFun([MyCustom("函数参数")]int a)
{

}
}


class Program
{
static void Main(string [] args)
{
Console.WriteLine("特性");

Myclass mc=new MyClass();
Type t=mc.GetType();
t=typeof(MyClass);
t=Type.GetType("");

//判断是否使用了某个特性
//参数一:特性的类型
//参数二:是否搜索继承链(属性和时间忽略该参数)
//只会判断类有没有应用,不判断成员
if(t.IsDefind(typeof(MyCustomAttribute),false))
{
Console.WriteLine("该类型应用了MyCustom特性");
}

//获取Type元数据中的所有特性
object[]array= t.getCustomAttributes(true);
for(int i=0;i<array.Length;i++)
{
if(array[i] is MyCustomAttribute)
{
console.WriteLine((array[i] as MyCustomAttribute).info);
(array[i] as MyCustomAttribute).TestFun();

}
}
}
}