c#个人向简单总结
前言:由于博主了解过c,java,c++,所以下面在总结时会略过相似的,只总结了自己不熟的部分
文章目录
- c#个人向简单总结
-
- 入门level1
- 基本level2
- 核心level3
-
- 垃圾回收机制GC
- 类class
- 静态static
- 继承
- [多态vob = 虚函数virtual + 重写override + 父类指针base](#多态vob = 虚函数virtual + 重写override + 父类指针base)
- 抽象abstract和接口interface
- 万物之父Object
- 进阶level?
入门level1
输入与输出
c#
#region//折叠,中间放内容
Console.WriteLine("helloworld");//输出
Console.ReadLine();//输入
#endregion
基础变量
c#
sbyte, int, short, long//有符号
byte, uint, ushort, ulong//无符号
float, double, decimal//浮点数,隐式转换中的decimal不能存其他两个浮点数
bool, char, string//其他,char为2字节
强转方式
- c语言同款括号
()
直接转 int.Parse(要转的内容)
Convert.ToXXX(要转的内容)
,XXX为具体的类型
控制台进阶
c#
Console.ReadKey(true).keyChar;//常用于不显示在控制台的单字符获取
Console.Clear();//清空控制台
Console.SetWindowSize(x, y);//设置窗口大小
Console.SetBufferSize(x, y);//设置窗口缓冲区大小
Console.SetCursorPosition(x, y);//设置光标位置
Console.ForegroundColor = ConsoleColor.Red;//设置文字颜色,Red可替换
Console.CursorVisible = false;//光标显示隐藏
Enviroment.Exit(0);//关闭控制台
随机数使用
Random
类new
一个变量r
,随后用r.Next(x. y)
=》随机生成一个x(包含)到y(不包含)间的数
基本level2
数组
c#
int[] a;//一维
a.length;//获取长度
int[,] a;//二维(真反人类。。)
a.Getlength();//参数为0时获取行长度,为1时获取列长度
int[][] a;//交错,意思是列的长度不固定,行的长度固定(你不是有两个括号的吗,就不能把上面那个改成交错这个改成二维吗。。。)
//获取行的长度时与二维数组一致,但获取列的长度时与一维数组一致,其实这样获取可以推测他底层的实现了。。。
函数
- 传实参修饰符
ref
:必须在传入前初始化out
:必须在函数内部进行赋值
- 变长参数
params
- 可以给参数赋默认值,并且函数存在重载
核心level3
垃圾回收机制GC
简要理解
C#中采用分代算法,将内存分为0代 ,1代 和2代内存
其中大部分新分配内存的对象都会放在0代内存中,当0代内存分配满后,GC会开始检查每个分配的对象上是否有标记可达 (还在用,比如说还有引用指向堆中的对象,则视为可达),若不可达,则将这些不可达的视为垃圾释放,而可达的则会搬迁到1代内存
同样,如果1代内存满了,则会进行相同的过程,不同的是,此时0代也会一起释放
而为了避免过于频繁地触发GC,大对象(80多kb以上)创建时会直接放到2代内存中
手动触发
GC.Collect()
,通常用于场景切换时手动释放,避免在游戏过程中触发GC让玩家感到卡顿
类class
成员属性:保护成员变量
c#
class Test{
public Name{
get;//写方法体时需要return
set;//写方法体时关键字value表示传入内容
}//内部两个方法可以通过添加访问修饰符达到只能读不能改的效果
}
构造函数
构造函数可以在后跟上:
调用其他构造函数进行一部分初始化内容
c#
class Test{
int age;
int height;
Test() {
age = 0;
}
Test(int height):Test() {
this.height = height;//this和常见语言中一样,指类自己
}
}
索引器
c#
class Test{
int[] a;
Test() {
a = new int[30];
}
public int this[int i] {
return a[i];
}
}
可以在外部使用类名[x]
直接赋值
分布partial
修饰类时可以分开几部分写一个类,不能有重复成员,访问修饰符要一致
修饰方法时可一边声明一边实现
类和结构体的比较
- 结构体是个值对象,在栈区;类是个引用对象,在堆区
- 结构体不能用
protected
修饰符,不能给成员变量赋初值,不能自己写无参构造,而类没有这三点限制 - 基于上面这三点,自然,结构体有了有参构造后无参构造不会被顶掉,且在有参构造中必须初始化所有成员,而类这样做会顶掉无参,不用一次性初始化所有成员
- 因为结构体在栈区,所以它没有析构函数,没有静态,类有
- 结构体不能继承结构体,但是可以继承接口
- 结构体不能将自己的
struct
作为成员,类可以这样做(但这样声明自己有点吓人了,一不小心就无限递归爆栈了)
选择:数据集合和经常要改变赋值但原对象不想变的可以用struct
,要继承多态的用class
静态static
static和const的区别
const
常量必须初始化且不能更改,只能修饰变量,不能写在访问修饰符前,static
没有这些限制
静态构造函数
- 不能有访问修饰符和参数
- 只调用一次,主要用于初始化静态成员
- 普通类也可以有
拓展方法:写在静态类中的一个静态方法
主要用于为非静态类型添加方法
c#
public static void Develop(this int value, 参数1, 参数2, ...) {}
//此例是为int类添加一个叫做Develop的方法,this必须写在第一个正是参数前,this后跟着要添加方法的类
运算符重载operator:一定为公共静态方法
- 条件运算符要成对实现 ,比如重载了
==
就必须同时重载!=
- 不能用
ref
和out
c#
public static 返回值 operator 运算符(参数){}
//书写规则与c++一致
继承
用法与c++一致,直接子类:父类
,不过又和Java一样是单继承(。。。。)
is和as关键字
is
用于判断对象,as
用于转换对象(比如里氏替换遍历时遇到父类装子类对象,要调用子类特有的方法,可使用as
)
密封sealed
修饰类时即不允许继承该类
修饰从父类重写的方法时不让自己的后代再重写
多态vob = 虚函数virtual + 重写override + 父类指针base
virtual
和override
配合使用,子类可重写父类中的虚函数,重写后想用父类中的该方法可用base
调用
抽象abstract和接口interface
abstract
修饰的类即抽象类不能实例化,和Java一样可以多继承接口
抽象类和接口的比较
相同点:
- 都可以被继承,都不能实例化
- 都可以由方法的声明,子类必须实现其未实现的方法
- 都遵循多态
不同点:
- 前者有构造函数,后者无
- 前者单继承,后者多继承
- 前者可以有成员变量,后者不行
- 前者可以声明成员方法,虚方法,抽象方法以及静态方法,后者只能声明抽象方法
- 前者的方法随便写访问修饰符,后者建议不写,默认未公共
选择:表对象选前者,行为拓展用后者
抽象方法和虚方法的比较
最主要区别:虚方法子类可选择性重写,抽象方法(相当于c++里的纯虚方法=0)子类必须重写
万物之父Object
- 成员方法
GetType()
:后面说,反射的重点 - 成员方法
MemberwiseClone()
:浅拷贝,返回新的对象(其内部的引用对象成员与老对象一致) - 虚方法
toString()
:可以自己实现一个,但大多数情况没必要 - 虚方法
Equals()
:不重新实现它就是静态方法,默认比较是否为同一引用,如果要自定义比较就是虚方法(。。。)
用Object
存值为装箱,把其内值取出来as
了为拆箱,多态的最大受益者
插曲提一嘴:StringBuilder
比起String
效率上的优势,主要在于修改而不是创建,会自动扩容,要用的时候再查提供的方法
进阶level?
泛型<>
和Java那个几乎没区别
泛型约束where
顾名思义,限制泛型修饰的范围,放在后面就行
c#
class Test<T> where T:class{}
这个where
后面的class
可以替换来限制不同类型,可以一次写几个也没关系,如果有多个泛型多写几个where
也可以
值类型:struct
;引用类型:class
;存在公共无参构造函数new()
;
某个类或者它娃:类名
;某个接口或者它娃:接口名
;另一个泛型类型或派生:另一个泛型字母
协变out与逆变in
也是修饰泛型的,限制泛型作用的地方
协变out
:修饰后的泛型只能作为返回值
逆变in
:修饰后的泛型只能作为参数
除此之外,协变是和谐的变化,逆变不和谐,协变修饰后是正常的父类装子类,遵循里氏替换,而被逆变修饰后子类可以去装父类
数据结构(容器?)
ArrayList
:本质为Object
数组HashTable
:哈希表,本质同上Stack
和Queue
:不用泛型的时候本质同上,用泛型的重载时本质是泛型数组List
:本质为泛型数组Dictionary
:字典,本质同上LinkedList
/LinkedListNode
:本质为泛型双向链表,可以从字面意思上看出是有头和身子区分的
委托delegate
装函数名的,用invoke()
方法调用或者直接()
也行,函数指针的包装版(太掉价了。。。)
自定义委托在一个正常的函数声明前加delegate
即可
可以做加减法运算,装多个函数,调用时一起调用,也可以清空
系统自带委托
Action
:无参无返回值Func<T>
:无参有返回值Action<T,...>
:有参无返回值(泛型指定参数)Func<T,T,...>
:有参有返回值(第一个泛型仍为返回值)
事件event
和委托一个效果,但是更安全,在委托外面再包装一层,定义时public event 定义好的委托名 事件名
- 只能在类,接口,结构体中定义
- 不允许外部赋值调用
匿名函数与lambda表达式
通常和委托配套使用
c#
//匿名函数
Action a = delegate(){函数逻辑};
//lambda表达式
Action a = (参数)=>{函数逻辑};
List自定义排序
-
法一:要排序的类继承
IComparable
接口,实现其中的CompareTo()
方法(以类的自定义为例,通常需要传入另一个类作为other
与本类this
比较) -
法二:使用
List
自带的Sort
方法重载,传入逻辑比较函数c#list.Sort((a, b)=>{return a.id > b.id ?1 : -1;});
多线程Thread
c#
Thread t = new Thread(要开线程的函数);
//线程方法
t.Start();//开启线程
t.isBackground = true;//后台运行
Thread.Sleep(时间);//线程休眠
lock(obj){逻辑}//obj为任意Object对象,其他也可以,在不同线程内加入lock关键字,可以让线程达到交替执行的效果
反射
运行中获取程序集(源文件内容)
Type类
获取Type
:
Object
万物之父类中的方法GetType()
typeof(类名)
关键字- 通过类名
Type.GetType("哪个命名空间里的类名")
获取对象里的信息:
c#
Type t = typeof(Test);
MemberInfo[] x = t.GetMembers();//获取所有公共成员
ConstructorInfo[] x = t.GetConstructors();//获取所有构造函数
FieldInfo[] x = t.GetFields();//获取所有公共成员变量
MethodInfo[] x = t.GetMethod();//获取所有公共方法
//获取某个具体的成员变量时,把后面的s后缀去掉即可,()内填要获取成员的字符串随后可通过GetValue()和SetValue()方法直接操控值
//获取某个具体的成员方法时,除了和上面一样,若存在重载还需传第二个参数new Type[]{参数列表的type},获取完毕执行时用Invoke()方方法,静态方法参数传null,非静态第一个参数填要执行这个方法的对象,第二个参数new object[]{参数列表}
获取对象:
c#
//三步走:要得到哪个构造函数=》实例化上面得到的构造函数得到object对象=》as后获取最终对象
ConstructorInfo x = t.GetConstructor(new Type[]{});//获得无参构造
//若要获取有参构造,在{}内填写要获取构造函数的参数type
object obj = x.Invoke(null);//有参构造时传入参数即可,这一步为实例化获取对象
Test test = obj as Test;
//当然上面两步也可以简写为一步
Test obj = x.Invoke(null) as Test;
Activator类
Type
中获取对象的方法太麻烦了,所以有这个一步到位的快速实例化
c#
Test obj = Activator.CreateInstance(typeof(Test), 参数列表) as Test;
Assembly程序集类
加载程序集:
c#
//加载同一文件下
Assembly assembly = Assembly.Load("程序集名称");
//加载不同文件下
Assembly assembly = Assembly.LoadFrom("包含程序集的文件名称或路径");
Assembly assembly = Assembly.LoadFile("要加载文件的完全限定路径");
//assembly有GetType("类名")方法
有了程序集后就可以用Type
和Activator
部分的知识实例化对象了
特性[]
Java的注解,为元数据添加额外信息
自定义特性需要定义一个类去继承Attribute
类
特性相关方法
c#
Type t = typeof(Test);
t.IsDefined(typeof(特性名), false);//是否使用特性,第二个参数为是否搜索后代
object[] x = t.GetCustomAttributes(true);//获取所有特性
系统自带特性
- 限制使用范围
[AttributeUsage(AttributesTargets.Class, AllowMultiple = true, Inherited = true)]
:三个参数依次为能用的地方(若要加多个能用的地方,使用|
连接),是否允许多个特性用在一个目标上,特性能否被后代继承 - 过时
[Obsolete("提示信息", true)]
:第二个参数为true
时报错,false
时警告 - 条件编译
[Conditional("函数名")]
:只有define
过该函数,调用该函数时才会执行 - 调用者信息
[Caller]
:包括[CallerFilePath]
,[CallerLineNumber]
,[CallerMemberName]
,报错时报的更清楚 - 外包调用
[DllImport]
:标记的函数说明它在一个外部的DLL中定义
迭代器iterator
foreach
的底层实现,比方说foreach(int i in list)
,就会先通过GetEnumerator
方法获取IEnumerator
,再执行其中的MoveNext
方法,返回为true
则获取Current
赋值给i
自定义迭代器实现需继承IEnumberable
和IEnumerator
两个接口,并实现下面四个方法(示例)
c#
class Test:IEnumberable, IEnumerator {
public int position = -1;//模拟光标
private int[] list;
public Test() {
list = new int[]{1, 2, 3};
}
public IEnumerator GetEnumerator() {
Reset();
return this;
}
public object Current{
get {
return list[position];
}
}
public bool MoveNext() {
position++;
return position < list.Length;
}
public void Reset() {
position = -1;
}
}
或者除了上面基于基本原理的实现,还可以使用yield
语法糖实现,仅需实现一个方法(挺奇妙的)
c#
public IEnumerator GetEnumerator() {
for (int i = 0; i < list.Length; i++) {
yield return list[i];
}
}
其他语法
var
用于偷懒- 形如python的内嵌字符串
Console.WriteLine($"{name},{age}")
- 奇怪的
?
语法糖,比如int? a = null
是合法的(真的有正常人这么写吗),见到再查吧
完结撒花,谢谢你的阅读么么叽
参考或者说学习来源:b站唐老狮