c# 笔记 --自用
c# 笔记:
说法或者是叫法的区别 (主要是区别于 c/c++ 的说法)
不同的叫法:
函数 = 方法
成员变量!= 属性
(成员变量 就是变量 属性是get set 俩个特殊的函数)
索引器 就是c# 自己给重载了[]运算符,另外在c#中无法重载[]运算符
类型
值类型
整数类型
浮点类型
bool
char
enum
struct
元组类型
null
引用类型
class
interface
delegate
record
in、ref、out 参数修饰符
in 关键字会导致按引用传递参数,但确保未修改参数。 它让形参成为实参的别名,这必须是变量。只读
out 只写
ref 读写
in只能传参进来 ,out 只能通过函数调用修改实参。 ref 二者都可以。
void
空类型
var
隐式类型
特殊语法
表达式主题语法 expression-bodied
对于函数(方法)
void func() => cwtt (void)
用于简单计算返回值的函数
只读属性
public class Location
{
private string locationName;
public Location(string name)
{
locationName = name;
}
public string Name => locationName;
} // Name 是只读的
get set
public class Location
{
private string locationName;
public Location(string name) => Name = name;
public string Name
{
get => locationName;
set => locationName = value;
}
}
构造函数 的单个赋值
析构函数
索引器
总结就是
{// one line code ;
}
可以替换成 =>one line code ;
class 封装
c#只支持单一继承,但是一个类可以实现多个接口
构造函数 与类同名 设置默认值
索引器 :
就是一个自定义的this[] 函数 和cpp的重载[]很像。
静态成员:放在静态存储区 ,是唯一的。只要被使用会获得和
主程序相同的生命周期。基本上不会被GC。
不能使用非静态成员变量
唯一变量的申明、方便获取对象申明、常用的唯一方法申明
const : 必须被初始化,而且只能修饰变量。const 本身是一个常量
静态类:只能有静态成员 , 作为工具类使用。 不能被实例化
静态构造函数: 只有在第一次使用 会自动调用 , 初始化静态成员 ,就算在普通类里 面静态构造也是 只会在第一次使用会自动调用 。
拓展方法:为一个其他类拓展方法 必须是在一个静态类中
[public] static void funcName(this [拓展类] value) {} value 表示谁在调用这个方法(那个实例在调用)
注意:方法名和拓展方法重名后(也没有重载的话)调用原类中的方法
运算符重载:
一定是公共的静态方法 不能使用ref out
public static [returntyoe] operator [运算符}{}
public static Point operator +(Point p1,Pointp2) // 点和点相加 ·
{
return - - ;
}
&& || [] () . ?: = 不能重载
内部类和分布类:
内部类:
在类中在声明一个类 ,而且在实例化的时候必须逐层点出来。
分布类:partial class Student{} 可以有俩部分的类 同名 、而且变量不能重名
分布方法: 在一处声明 在一处实现
class 继承
不能多继承 , 子类拥有父类所有的方法和属性 但是会受到访问修饰符的影响
public 全访问权限
protected 自身 子类拥有访问权限
private 只有自身拥有访问权限
子类属性和父类重名 会覆盖父类的属性 不要这么用,不建议
里氏替换原则
父类出现的地方,子类都可以替代
父类容器装子类对象 ,子类对象包含父类的所有内容
实际上就是向上转型
is 是判断对象是否是指定类对象
if (player is Player) return bool
as 将一个对象转换为指定类对象
成功会返回指定类型对象,失败返回null
return object or null
继承中的构造函数:
先调用父类的构造函数,后子类
子类实例化时,默认调用的是父类的无参构造函数,
父类的无参构造函数被顶掉 ,会报错
通过Base调用父类的构造函数
:base()
object和 装箱拆箱
用object 装载任何 类进去
引用类型需要用 is as 转型
值类型 直接用()强制转型
object 存值类型 就是装箱
再把object转为值类型是拆箱
装箱:会把值类型用引用类型存储
栈内存会迁移到堆内存中
拆箱:把引用类型的值取出来
堆内存会迁移到栈内存中
装箱拆箱有很高的性能损耗、尽量少用
密封类:
被sealed 修饰的类 、无法被继承
selaed class className{ } 无法在往下被继承
多态
vob:
继承同一父类的子类们,在执行相同方法时有不同的表现
编译时多态 ---- 函数重载
运行时多态 ---- vob(virtual override base )、 抽象函数、接口
vob : 父类是 virtual(虚函数) 子类是 override(重写) 、子类可以使 用base保留父类的行为
抽象类和抽象方法
抽象类:
不能被实例化 ,可以包含抽象方法
一般是用于继承的
抽象方法
abstract 关键字 不能实现 不能私有 继承后必须被
override重写 (virtual可以不被重写,但是在任何类中都可以声明和定 义)
接口
interface : 可以包含成员变量但是不能初始化 get set 、只能包括方法、 属性、索引器、事件
成员不能被实现、成员不能是私有的、不能继承自类,但是可以继承自其他接口
类可以继承多个接口、但是必须实现接口中所有的成员
接口是抽象行为的基类
可以向上转型
接口可以继承多个接口
继承俩个接口 时有重名的函数
需要显式实现接口 [接口名.方法名]
继承类:是对象之间的继承,包括行为特征
继承接口:是方法之间的继承
就比如 游戏中的交互功能 可以通过交互接口来实现
比如 开门、交任务、对话等等不同的行为。可以被相关类继承
交互接口,然后自己实现接口,就可以实现相同的方法名称有不同的作用。
密封方法:
用sealed 修饰重写方法 ,让虚方法和抽象方法不能再被override
命名空间:
用来管理和组织代码的
using [命名空间]
[命名空间].funcName() ;
object 中的方法
static function
Euquals :判断俩个对象是否相等(值和引用都可以)
在判断引用类型相等时 是判断他们是否是同一个地址(内存空间)
ReferenceEqual() 专门用来比较引用类型 , 值类型的比较永远是false
成员方法:
GetType return Type 反射内容
MemberwiseClone 会获取拷贝对象,值类型会深拷贝 、但是引用类型是浅拷贝
虚方法: Euqals 可以重写这个方法定义自己的比较规则
GetHashCode 获得对象的哈希码、一般一个对象有一个唯一的编码
但是不同的对象的哈希码可能相同(哈希冲突)
可以重写哈希算法
ToString :返回当前对象代表的字符串
String
字符串的本质就是一个char数组
str.ToCharArray() 转化为char数组
str.Format(“{0}{1}”,1,2) 字符串拼接
str.IndexOf(“”) 正向查找字符串位置 ,没找到 return -1
str.LastIndexOf() 反向查找字符串位置,没找到 return -1
返回一个新的字符串
str.Remove() 移除指定位置后的字符 重载:开始位置、字符个数
str.Replace() 替换指定字符串
str.ToUpper() str.ToLower()大小写转换
str.Substring() 字符串截取
str.Split(‘,’) 字符串切割 以,分割 比如1,2,3,4 返回就是 1 2 3 4
StringBuilder
string 在每次重新赋值或者拼接时都会分配内存空间
stringBuilder 用于处理字符串的公共类
修改字符串而不创建新的对象,需要频繁改动的字符串可以使用提高性能
StringBuilder str = new StringBuilder(“”,[capacity]);
str.Capacity 容量
增删改查:
str.Append(); 增加
str.AppendFormat();
插入:
str.Insert();
删除
str.Remove();
清空
str.Clear()
查找
str[] ;
改
str[]= ‘’ ; StringBuilder 是可以的 String 这样写是不行的
替换
str.Replace(“”,””);
结构体和类的区别(c# 的结构体和c++差距比较大 )
结构体是值 在栈上 类是引用 在堆上
结构体只能实现封装 ,无法继承和多态
结构体是可以继承接口的
抽象类和接口的区别
相同点: 都可以被继承、都不能实例化、都可以包含方法申明、
子类必须实现未实现的方法
都可以向上转型 (里氏替代原则)
不同点: 抽象类中可以有构造函数 接口不能
抽象类只能被单一继承 ,接口可以继承多个
抽象类中可以有成员变量,接口不能
抽象类中可以声明 所有的方法
接口中能能声明没有实现的抽象方法
抽象类可以使用访问修饰符, 接口类默认是是public
表示对象的用抽象类 ,表示行为拓展的使用接口
容器 :String、 StringBuilder、 ArrayList
ArrayList List<>
增加
array.Add(); 里面可以存放任何类型的对象
array.AddRange(array2);
删除
array.Remove(); 移除指定元素
array.RemoveAt()移除指定位置
array.Clear(); 清空
查找
array[] ;
查看元素是否在ArrayList中存在
array.Contains();
array.IndexOf() //找元素位置
array.LastIndexOf() // 反向查找元素位置
改
array[] = --- ;
插入
array.Insert(index,element);
遍历
array.Count 长度
array.Capacity 容量
for index 遍历
foreach(var e in array) 迭代器遍历 (基于范围的for循环)
装箱拆箱 ArrayList 可能会发生装箱拆箱 、 造成性能损耗
int i = 1 ;
array[0] = i // a装箱
i = (int) array[0] // 拆箱
Stack 有Stack<T>
Stack stack = new Stack();
//压栈
stack.Push();
//出栈
stack.Pop();
//查看栈顶的内容
stack.Peek();
//是否存在
stack.Contains();
//改
没法改 ,只能清空
stack.Clear();
//长度
stack.Count
//用foreach 遍历 --从 顶到底
foreach(object item in stack)
{
}
stack.ToArray(); //转化成数组 从顶到底
//循环出栈
while(stack.Count >0)
{
object o = stack.Pop();
}
存在装箱拆箱
Queue 队列 是先进先出 有 Queue<T>
Queue queue = new Queue();
增加:
queue.Enqueue();
取出:
queue.Dequeue();
查: 队头元素
queue.Peek();
查看元素是否存在队列中
queue.Contains() ;
改: 无法改 只能清空
queue.Clear() ;
遍历
queue.Count
foreach 遍历
object[] array = queue.ToArray();
循环出列
while(queue.Count > 0 )
{
object e = queue.Dequeue() ;
}
Hashtable 哈希表 散列表
Hashtable hashtable = new Hashtable();
增加
hashtable.Add(1,”ewq”) ; key ,value
key 不能重复
删除:
hashtable.Remove() ; // 通过键去移除
hashtable.Clear() ;
查
hashtable[key] ; 查不到返回null
是否存在
hashtable.Contains(key)
hashtable.ContainsKey() 和上面一样
hashtable.ContainsValue(); 按值找
改
无法改key 只能通过 key 来改value
个数
Count
遍历
foreach (object e in hashtable.Keys)
{
hashtable[e] // value
}
object e in hashtable.Values
无法 for() 遍历
foreach (DictionaryEntry dir in hashtable)
{ dir.Key dir.Value }
IDictionaryEnumerator myEnumator = hashtable.GetEnumerator();
bool flag = myEnumator .MoveNext();
while(flag)
{
}
存在装箱拆箱的情况
泛型
class name<T>
interface name<T>
[returntype] func<T>()
泛型约束:
值类型 where T:struct
引用类型 ---:class
存在无参公共构造函数 --- :new()
用作泛型的类必须有无参公共构造函数
抽象类也不行
某个类本身或者其派生类 ---:类名
某个接口的派生类型(派生类和派生接口都行) --- :接口名
另一个泛型类型本身或者派生类型 ---:另一个泛型字母
T,U 填进去的东西 T是U的派生类型 或者 T和U是一个类型
约束可以组合使用 (不是所有都能一起使用) ,号分开
泛型容器:
List<>
List<int> list = new List<int>();
增加:
Add
AddRange() ;
删除
list.Remove();
RemoveAt();
Clear()
查
list[]
是否存在
Contains() ;
正向查找
IndexOf() false return -1
反向查找
LastIndexOf()
改
list[1] = 23 ;
list.Insert(Index,Value);
长度
Count
容量
Capacity
遍历
for index loop
foreach loop
Dictonary<,>
Dictonary<int.string>dic = new Dictonary<int.string>();
增加
Add()
移除
Remove();
清空
Clear();
查
dic[2]
是否存在
ContainsKey()
ContainsValue()
改
dic[2] = 123
遍历
Count
foreach loop Keys
foreach loop Value
foreachA(KeyValuePair<int,String> e in dic){}
顺序存储 链式存储
数据结构学过了,跳过
LinkedList<> 泛型双向链表
LinkedList<int> node = new LinkedList<int>();
增加:
node.AddLast(); 尾插
AddFirst(); 头插
移除
RemoveFirst()
RemoveLast()
移除指定节点
Remove(Value)
清空
Clear()
查找
First
Last
没法随便找
Find(Value);
在n节点操作
n = node.Find(20) ; // 先要找到这个节点
AddAfter(n,15)
AddBefore(n,11)
改
node.First.Value = 21 ;
node.Last.Value = 123 ;
遍历
foreach loop
while(head !=null){
node = node.next ;
} head next 遍历
泛型栈和队列 stack<> , Queue<>
委托和函数指针很类似
但是不同的点是 委托是一个真正的类 (所以用new来初始化)
函数指针只是一个函数的入口地址.可能委托是把函数指针封装了一个仿函数类
核心的东西是一样的
委托
委托是方法的容器,核心就是用来调用函数的
public delegate void delegateName(String name){}; (默认是公共的)
定义了委托 ,并没有使用
函数变量化 只不过里面存储的是函数
delegateName d = new delegateName(FuncName) // 建议用这种的
d = FuncName
调用:
d.Invoke(); // 用这种的
d();
使用:
1. 作为类的成员变量
2. 作为函数的参数
主要就是传递函数给类,然后在类里面可以进行一系列处理并且由类决定委托的调用时间和次序
多播委托(委托变量可以存储多个函数)
d = FuncName
d+= FuncName 多播委托
d-=FuncName 移除指定的函数 多次移除指定的函数不会报错
把多个函数绑定给一个委托 ,委托一调用 所有的函数都我把直接执行
委托没有初始值的时候 不能使用+= 如果初始化为null 可以使用 +=
完全清空了会直接报错的 , 所以通过委托调用函数的时候先做判空处理
if(d !=null){}
系统提供的委托
Action action = funName 无参无返回值的委托
Action<> 最多16参数 无返回值委托
Func<returnType>
泛型委托 Func<String> funcString = FuncName ;无参数 随意返回值类型委托
Func<参数,returnType> 最多16个参数 有返回值委托
事件 : 建立在委托上
public event 委托类型 事件名:
事件相比委托: 不能在类外部赋初始值(绑定函数),{但是可以在外部+= ,-= 实现多播绑定}
不能在类外部调用
事件只能作为成员存在于类和结构体中
class classname
{
public event Action myEvent ;
在类内部和委托一样
}
事件的作用:
防止外部随意置空委托
防止外部随意调用委托
事件相当于对委托进行了一次封装,让其更安全
多线程:
进程:一个应用程序就是一个进程 进程是操作系统的单位
线程: 运算调度的最小单位 包括在进程中是进程中的实际操作单位,一个进程可以并发多 个线程,主程序 运行在主线程里. 44
多线程:可以多条线程运行代码
线程类:Thread
Thread t =new Thread(); // 新线程的代码是封装在新函数中的 (形参是一个委托)
14:03
匿名函数:
Action a = delegate(参数列表){}
声明一个匿名函数放在委托里
a() 这样调用
Action<int,String> a = delegate(int a ,String b){}
a() 调用
Func<string>c = delegate() // return String
{
return “ “ ;
}
作为函数传递给形参是委托的 可以传一个匿名函数进去
作为返回值 t.Func()() 直接调用返回的委托函数
用法: 委托的传递和存储 传给函数 和函数返回
匿名函数的缺点
因为没有名字 添加到委托和事件后 不记录是无法移除的
无法指定的移除一个匿名函数
只能 赋值为 null
一般单播委托使用匿名函数
lambda表达式
匿名函数的简写 和匿名函数一样
语法: ()=>{}
无参无返回
Action a = ()=>{cw();} ; 调用 a()
(int value)=>{};
有返回值
Func<String,int> a = (String value)=>{return 1; }
注意这里的的String 是参数 ,int (最后一个)是返回值
闭包
lambda表达式 代码块内使用了代码块外部的变量就会形成闭包,因为外部变量已经进入了堆.其生命周期得到延长
内层的函数可以引用包含在它外层的变量即使外层函数的执行已经终止.
表达式内 引用了外部的变量 会导致 该变量闭包,
该变量生命周期改变,
注意:该变量提供的值并非变量创建时的值而是父函数范围内的最终值
C#协变 逆变
父类装子类 协变
子类装父类 逆变
用来修饰泛型
协变 out
逆变 in
用来泛型中修饰泛型字母
只有泛型接口和泛型委托能用
作用:
delegate T test<out T>() T 只能作为返回值(out 修饰的)
能判断 T的具体类型是否有父子关系,满足李氏替换原则
可以父类装子类,哪怕是泛型也可以
父类委托装子类委托
delegate void test<in T>() 这里的T只能用于参数(in)
子类装父类 泛型委托 就是 逆变
c# 多线程
1. 申明线程
Thread t = new Thread(funcName);
2.启动线程
t.Start(); // 启动线程
3.设置为后台线程
t.IsBackgroudn() = true ;
主线程结束后 后台线程也就结束了
(一般情况下都会设置成后台线程)
4.关闭 释放一个线程
不是死循环能正常结束就不用去管
是死循环,俩种方法
1. 死循环的bool的标志
2. 通过线程方法来中止线程
t.Abort(); t = null ;
Abort()不支持控制台的版本
5. 线程休眠
Thread.Sleep(毫秒); 在那个线程里执行就休眠那个线程
6. 线程之间共享数据
数据竞争(多线程操作同一个内存区域) 使用线程锁
lock(引用类型)
{
// code
}
执行完了后解锁
锁会影响执行效率
作用:可以用多线程处理一些复杂耗时的操作
寻路 网络通讯
预处理器指令
编译器是一种翻译程序 源语言程序->目标语言程序
#define
#undef 用这个定义
#if 用下面判断 执行 定义就会执行判断
#elif
#else
#endif
#error 编译期之前就报错了
反射:
程序集是由编译器编译得到的,供给进一步执行的中间文件
在window 中 有一般是dll 和exe
元数据:用来描述数据的数据,类中的函数变量等等信息就是程序的元数据
有关程序以及类型的数据被称为元数据,他们保存在程序集中
反射: 程序在运行时可以查看其他程序集和其他程序集的元数据
反射的作用:
可以在程序变异后获得信息,提高了程序的拓展性和灵活性
程序运行时,实例化对象,操作对象
程序运行时创建对象,用这些对象执行任务
Type类 (类的信息类)反射的基础 使用Type成员获取有关类型声明的信息
int a = 23;
Type type = a.GetType(); cw(type) // system.Int32
Type type = typeof(int)
通过类的名字获取类型
Type type = Type.GetType(“System.Int32”) 必须包括命名空间
通过三种方式得到的type 只在堆中只有一个 ,三个指向同一个
每一个类型的type都是唯一的,元数据是唯一的
得到类的程序集信息
type.Assembly ;
获取类中的所有公共成员:
using System.Reflection
Type t = typeof(Test) ; // 这里Test是一个类
MemberInfo[] info = t.GetMembers();// 公共成员在这里
获取类的公共构造函数并调用
ConstructorInfo[] = ctors = t. ();
得到无参构造函数
ConstructorInfo info = t.GetConstructors(new Type[0]) ;
Test obj = info.Invoke(null) as Test ;
得到有参构造函数
ConstructorInfo info = t.GetConstructors(new Type[]{typeof(int}) ;
obj = info.Invoke(new object[]{2})as Test ;
获取类中的公共成员变量
FieldInfo[] filedinfos = t.GetFields(); // 公共成员变量
指定名称的公共成员变量
InfoJ = t.GetField(“j”) j这个成员变量
通过反射获取和设置对象的值
InfoJ.GetValue(test);
InfoJ.SetValue(test,100)
获得类的公共成员方法
Type strType = typeof(string);
MethodInfo[]methods = strType.GetMethods();
MethodInfo method = strType.GetMethod(“Substring”,
new Type[]{typeof(int),typeof(int)}
); // int,int 的函数版本
Activtor 快速的把Type类实例化对象
Type test = typeof(Test) ;
// 调用的是无参构造函数
Test TestObj = Activator.CreateInstance(test) as Test;
// 调用有参构造
Test TestObj = Activator.CreateInstance(test,参数列表) as Test;
Assembly
加载dll文件
Assembly asembly = Assembly.Load(“程序集名称”);
Assembly asembly = Assembly.LoadFrom(“路径”);
asembly.GetTypes() ;
c# 特性
特性是一个类 为元数据添加额外方法
继承 Attribute
class MyAttribute :Attribute
{
public String info ;
public MyAttribute (String info)
{
this.info = info ;
}
}
[My(“”)] // 注意:这里的Attribute会被省略
class MyClass{}
[] 就是特性 为MyClass MyAttribute 添加额外信息
是否用类MyClass 用了 某个特性
Type t = typeof(MyClass);
if(t.IsDefined(typedof(MyAttribute),false)); //false 是否搜索继承链
只会检测这个类有没有特性 ,成员 不判断
获取Type 元数据中的所有特性
object[] array = t.GetCustomAttribute(true);
for loop array
通过给特性类加特性
给特性类限制其使用范围 AttrubuteUsage();
AttributeTargets.Class特性能用在那些地方
.Field 成员变量
AllowMultiple 是否允许多个特性实例用在同一个目标上
Inherited 特性是否能用在派生类和重写成员继承
系统自带的特性
1. Obsolete 过时特性
[Obsolete(“提示内容”),true] // 第二个参数 true 会保存,false 会警告
public void OldFunc(){}
2. 调用者信息特性
CallerFilePath 那个文件调用
CallerLineNumber 那一行调用
CallerMemberName 那个函数调用
3. 条件编译特性
调试代码上
[Condition(“Func”)] // 必须要有#define Func 才可以执行
4. 外部dll包特性
[DllImport(“路径”)]
public static extern int Add(int a,int b); // 和 c/c++ 的函数声明完全 一样
迭代器
是一种设计模式
用来遍历聚合对象的各个元素的同时不暴露内部的标识
:IEnumerable 继承这个接口 实现迭代器方法
foreach 遍历就不会报错 foreach 会调用
public IEnumerator GetEnumerator() 方法
IEnumberator :
object Current
MoveNext()
Reset()
foreach 执行顺序
先获取in 后面这个对象的IEnumberator
调用GetEnumerator()获取
执行IEnumberator 对象的MoveNext 方法
MoveNext 方法的返回值是true 就会得到Current
然后复制给 element
position = -1 // 光标初始化
GetEnumerator()
{
Reset(); // 先重置光标
return this;
}
MoveNext()
{
++ position ;
return position <list.Length 不超出范围就是合法的
}
current
{
get
{ return list[position] ; }
}
Reset()
{
position = -1; // 重置光标位置
}
yield return c#语法糖
继承IEnumerable,只需要实现 GetEnumerator()
GetEnumerator()
{
for i to list.Length
{yield return list[i]; }
}
用yield return 实现迭代器
和上面完全一样 ,只是具体的容器是使用的泛型
特殊语法:
1.var
var 就类似于c++里面的auto
var 必须初始化
var是可变类型
用在不确定类型
尽量少用
2.设置对象初始值 用{} 初始化公共成员变量和属性
Person p = new Person{};
实际上没p用
3.设置集合初始值 (类似于c++列表初始化)
int [] array = new int[]{1,2,3,4,5};
List<int>list = new List<int>(){1,2,3,4,5};
4.变量可以声明为自定义的匿名类型
var v = new{} ; 只能有匿名类型
5. 可空类型
值类型不能为空 int? c = null ; 使用要判空
安全的获得可空类型值
value.GetValueOrDefault(); 也可以指定值
6.object o = null
o?.ToString(); o 是否为空 不是空执行ToString 为空也不会报错
action?.Invoklel(); 容器和委托也可以
7.空合并操作符
int intI = intJ??100;
intJ 是否为null 不为空就赋值intJ 为空就赋值100
8.内插字符串
$
cw($”this is{i}”) i 是具体的变量名字
9. {} 可以省略 for if 但是只作用于下一句
在函数、 get 、set 中 只有一句的话 {} 可以用=> 来代替
而且不能写return