C#学习笔记——进阶知识点(肆)
一、简单数据结构类
1、ArrayList
ArrayList是C#封装好的类,本质是一个可以自动扩容的object类型的数组,提供了很多方法,如数组的增删改查
using System.Collections
ArrayList array = new ArrayList();
// 增
array.Add();
// 范围添加:把array2里的所有东西加到array后面
array.AddRange(array2);
// 插入
array.Insert(位置,元素);
// 删除(从左往右找)
array.remove(元素);
// 删除指定索引
array.removeAt(索引);
// 清除
array.Clear();
// 查看
array[索引]
// 查看元素是否存在
array.Contains(元素);
// 正向查找元素位置
array.IndexOf(元素);
// 反向查找元素位置
array.LastIndexOf(元素);
// 修改
array[索引] = 值;
// 长度、容量
array.Count;
array.Capacity;
2、Stack
Stack(栈)是C#封装好的类,本质也是object数组,但是有特殊的存储规则:先进后出
using System.Collections
Stack stack = new Stack();
// 压栈
stack.Push(元素);
// 弹栈
stack.Pop();
// 查看栈顶的内容
stack.Peek();
// 查看元素是否在栈中
stack.Contains(元素);
// 清空
stack.Clear();
3、Queue
Queue(队列)是C#封装好的类,本质也是object数组,但是有特殊的存储规则:先进先出
using System.Collections
Queue queue= new Queue();
// 入队
queue.Enqueue(元素);
// 出队
queue.Dequeue();
// 查看头部元素
queue.Peek();
// 查看元素是否在队列中
queue.Contains(元素);
// 清空
queue.Clear();
4、Hashtable
Hashtable(散列表)是基于键的哈希代码组织起来的键值对,主要作用是提高数据查询效率,使用键来访问集合中的元素
using System.Collections
Hashtable hashtable = new Hashtable();
// 增加(不能出现相同的键,值可以)
hashtable.Add(键,值);
// 删除
hashtable.Remove(键);
// 清空
hashtable.Clear();
// 查找
hashtable[键];
// 查看是否存在
hashtable.Contains(键);
hashtable.ContainKey(键);
hashtable.ContainValue(值);
// 修改
hashtable[键] = 值;
二、泛型
1、概念
泛型的核心思想是允许类或方法在定义时不指定具体的数据类型,而是在实际使用时指定
2、语法
- 泛型类
class 类名<泛型占位符>
- 泛型接口
interface 接口名<泛型占位符>
- 泛型函数
返回值 函数名<泛型占位符>(参数);
-
注意:
-
其中泛型占位符可以有多个,用逗号分隔
-
不确定泛型类型时,可以用default获取默认值
-
三、泛型约束
1、概念
可以使用泛型约束来限制在实例化泛型类型时,指定的某些类型。如果尝试使用指定约束不允许的类型实例化泛型类型,会产生编译时错误
2、语法
where 泛型字母 : 约束的类型
- 值类型
where 泛型字母 : struct
- 引用类型
where 泛型字母 : class
- 存在无参公共构造函数
where 泛型字母 : new()
- 是这个类本身或其派生类
where 泛型字母 : 类名
- 是这个接口的派生类型
where 泛型字母 : 接口名
- 该泛型类型是另一个泛型类型本身或者派生类型
where 泛型字母 : 另一个泛型字母
四、常用泛型数据结构类
1、List
List是C#封装好的类,本质是一个可以自动扩容的泛型类型的数组,提供了很多方法,如泛型数组的增删改查
using System.Collection.Generic
List<类型> list = new List<类型>();
// 增加
list.Add(元素);
// 范围添加
list.AddRange(另一个list);
// 插入
list.Insert(位置,元素);
// 删除
list.Remove(元素);
list.RemoveAt(索引);
list.Clear();
// 查找
list[索引]
// 查看元素是否存在
list.Contains(元素);
// 正向查找
list.IndexOf(元素);
// 反向寻找
list.LastIndexOf(元素);
// 修改
list[元素] = 值;
// 长度、容量
list.Count
list.Capacity
2、Dictionary
可理解为拥有泛型的Hashtable,也是基于键的哈希代码组织起来的键值对
using System.Collection.Generic
Dictionary<键类型,值类型> dictionary = new Dictionary<键类型,值类型>();
// 增加
dictionary.Add(键,值);
// 删除
dictionary.Remove(键);
// 清空
dictionary.Clear();
// 查找
dictionary[键]
// 查看元素是否存在
dictionary.ContainsKey(键);
dictionary.ContainsValue(值);
// 修改
dictionary[元素] = 值;
// 长度
list.Count
3、LinkedList
LinkedList是C#封装好的类,本质是一个可变类型的泛型双向链表
using System.Collection.Generic
LinkedList<int> linkedList = new LinkedList<int>();
// 往链表尾部加元素
linkedList.AddLast(元素);
// 往链表头部加元素
linkedList.AddFirst(元素);
// 在指定结点之后添加结点
linkedList.AddAfter(结点,元素);
// 在指定结点之前添加结点
linkedList.AddBefore(结点,元素);
// 移除头结点
linkedList.RemoveFirst(元素);
// 移除尾结点
linkedList.RemoveLast(元素);
// 移除指定结点
linkedList.Remove(元素);
// 清空
linkedList.Clear();
// 查找
LinkedListNode<int> first = linkedList.First;
LinkedListNode<int> last = linkedList.Last;
// 找到指定值结点
LinkedListNode<int> node = linkedList.Find(元素);
// 判断是否存在
linkedList.Contains(元素);
// 修改(结点.Value)
linkedList.First.Value = 10;
// 上一个结点
结点.Previous
// 下一个结点
结点.Next
4、泛型栈和队列
using System.Collections.Generic
Stack<类型>
Queue<类型>
五、顺序存储和链式存储
1、数据结构
数据结构是计算机存储、组织数据的方式和规则,是指相互之间存在的一种或多种关系的数据元素的集合。
2、线性表
线性表是一种数据结构,有n个具有相同特征的数据元素的有限序列
3、顺序存储
用一组地址连续的存储单元依次存储线性表的各个数据元素
如:数组、栈、队列、List、ArrayList
4、链式存储
用一组任意的存储单元存储线性表中的各个数据元素
如:单向链表、双向链表、循环链表
5、顺序存储与链式存储优缺点
- 增:链式优于顺序
- 删:链式优于顺序
- 查:顺序优于链式
- 改:顺序优于链式
六、委托
1、概念
委托是一种类型安全的函数指针,它允许将方法(函数)作为参数传递给其他方法。可以理解为表示函数的变量类型,可以存储和传递函数(方法)。
2、语法
- 声明
访问修饰符 delegate 返回值 委托名(参数列表);
- 使用
delegate void MyFun();
void Fun(){
...
}
// 方式一
MyFun f1 = new MyFun(Fun);
f1.Invoke();
// 方式二
MyFun f2 = Fun;
f2();
- 委托变量可以存储多个函数
delegate void MyFun();
void Fun(){
...
}
MyFun f1 = Fun;
f1 += Fun;
f1 -= Fun;
3、系统自带委托
using System
Action // 无参无返回值
Action<类型,类型...> // 可以带n(不超过16)个参数的委托,有参无返回值
Func<返回值类型> // 无参有返回值(泛型为返回值类型)
Func<参数类型,参数类型... ,返回值类型> // 可以带n(不超过16)个参数的委托,有参有返回值
4、补充知识点
当用有返回值的委托容器存储多个函数时:
Func<string> func = () => {
return 1; // 第一个函数
}
func += () =>{
return 2; // 第二个函数
}
func += () =>{
return 3; // 第三个函数
}
// 三个函数都会执行,但是返回值只能获取最后一个
Console.WriteLine(func()); // 3
如果想要获取到每一个函数执行后的返回值,使用方法GetInvocationList()可以返回一个委托数组
foreach (Func<string> del in func.GetInvocationList()){
Console.WriteLine(del());
}
七、事件
1、概念
事件是基于委托的存在,是委托的安全包裹,是一种特殊的变量类型
2、使用
委托怎么用,事件就怎么用,但是事件和委托不同的是,事件不能在类外部赋值和调用,但外部可以添加或删除(+=,-=)记录的函数。
访问修饰符 event 委托类型 事件名;
3、作用
- 防止外部随意置空委托
- 防止外部随意调用委托
- 事件相当于堆委托进行了一次封装,使用更加安全
八、匿名函数
1、概念
匿名函数就是没有名字的函数,主要配合委托和事件使用
2、语法
delegate (参数列表)
{
函数逻辑
};
九、lambda表达式
1、概念
可以理解为匿名函数的简写,都是配合委托和事件使用的
2、语法
参数类型可以省略
(参数列表) =>
{
函数逻辑
};
3、闭包
内层函数可以引用它外层函数的变量,即使外层函数执行已停止。
class Test
{
public event Action action;
public Test()
{
int value = 10;
action = () => {
Console.WriteLine(value); // 闭包
}
}
}
- 注意闭包陷阱:如果使用了循环创建闭包,那么内部函数使用该外部变量不是创建时的值,而是一起指向最终值。解决方法是尝试创建新变量
// for 循环的变量 i 是整个循环共享一个实例,闭包引用的是这个实例的 “引用”,而非每次循环的 “值快照”
class Test
{
public event Action action;
public Test()
{
for (int i=0;i<10;i++){
action += () => {
Console.WriteLine(i); // 闭包
}
}
}
}
// 解决方法:为每次循环创建独立的变量副本
for (int i=0;i<10;i++){
int index = i;
action += () => {
Console.WriteLine(index); // 闭包
}
}
十、排序
1、List自带排序
list.Sort();
2、自定义类排序
实现接口:Icomparable,补充函数CompareTo
public int CompareTo(类型 other);
返回值含义:
- 小于0:放入other的前面
- 等于0:保持位置不变
- 大于0:放在other的后面
3、传入委托或匿名函数
// 委托
list.sort(MySort);
static int MySort(类型 a, 类型 b){
...
}
// 匿名函数
list.Sort((a,b) => {return a.id > b.id ? 1 : -1});
十一、协变逆变
1、概念
- 协变:和谐的变化,自然的变化,里氏替换原则中,父类可以装子类,所以string变成object是和谐的。
- 逆变:逆常规的变化,不正常的变化,比如父类变子类,object变成string,感受是不和谐的。
核心思想:在类型安全的前提下,允许更灵活的泛型类型转换。
注意:逆变只是看起来像是子类装父类,但实际上还是遵循里氏替换原则的,下面会讲
2、语法
以委托为例:
- 协变:关键字
out,此时泛型只能做返回值
delegate T testOut<out T>();
- 逆变:关键字
in,此时泛型只能做参数
delegate void TestIn<in T>(T t);
3、举例
- 协变:
首先声明了一个接口IFoo,使用out协变,因此T只能做返回值,Foo实现接口并将泛型设置为string类型。
下面的使用中,IFoo<object>可以装载IFoo<string>,因为string是object的子类,而IFoo<父类> = IFoo<子类>这种形式遵循里氏替换原则,即父类装子类,因此感受是和谐的。
调用fooObj.GetName()时,实际调用的是new Foo().GetName(),返回的是string类型,但是fooObj要求返回的是object类型,而object可以装载string,因此转换是没问题的:object name = (object) new Foo().GetName();
// 声明
interface IFoo<out T>{
T GetName();
}
class Foo : IFoo<string>{
public string GetName(){
return GetType().Name;
}
}
// 使用
IFoo<string> fooStr = new Foo();
IFoo<object> fooObj = fooStr;
object name = fooObj.GetName();
- 逆变:
首先声明了一个接口IBar,使用in逆变,因此T只能做参数,Bar实现接口并将泛型设置为object类型。
下面的使用中,IBar<string>可以装载IBar<object>,而IBar<子类> = IBar<父类>这种形式看起来不遵循里氏替换原则,即看着像子类装父类,因此感受是不和谐的。
调用barStr.Print()时,实际调用的是new Bar().Print(),传入的是string类型,但是barObj要求传入的是object类型,而传入的string可以转换为object,因此转换是没问题的:new Bar().Print("Hello World"); // string隐式转换为object
// 声明
interface IBar<in T>{
void Print(T content);
}
class Bar : IBar<object>{
public void Print(object content){
Console.WriteLine(content);
}
}
// 使用
IBar<object> barObj = new Bar();
IBar<string> barStr = barObj;
barStr.Print("Hello World");
4、总结
-
协变:
- 和谐的变化,关键字
out,此时泛型只能做返回值(out也是输出的意思) - 使用时形式类似这样:
IFoo<父类> = IFoo<子类>
- 和谐的变化,关键字
-
逆变:
- 不和谐的变化,关键字
in,此时泛型只能做参数(in也是输入的意思) - 使用时形式类似这样:
IBar<子类> = IBar<父类>
- 不和谐的变化,关键字
-
核心: 协变逆变在类型安全前提下为泛型提供了灵活转换,两者都遵循里氏替换原则,逆变并不是打破了这个规则,只是表现形式不同
-
我的简单记忆:
- 协变是
out,out是输出,只能做返回值,返回值子类可强转成父类,因此父类可以装子类,是和谐的 - 逆变是
in,in是输入,只能做参数,参数传入的子类可以强转成父类,因此子类可以装父类,表现不和谐
- 协变是
十二、多线程
1、进程
进程是计算机的程序关于某数据集合上的一次运行活动,是系统进行资源分配的调度的基本单位。比如打开一个应用程序就是开启了一个进程。进程之间可以相互独立运行,也可以相互访问、操作。
2、线程
操作系统能够进行运算调度的最小单位,被包含在进程中,是进程中实际运作的单位。简单理解就是一个线程就像代码运行的一条管道。
3、多线程概述
通过代码开启新的线程,多个线程同时运行就叫多线程。
4、多线程语法
- 声明
using System.Threading;
Thread t = new Thread(MyThread);
static void MyThread(){
线程逻辑...
}
- 启动
t.Start();
- 设置为后台线程
- 当前台线程都结束时,后台线程也结束,即整个程序结束
- 后台线程不会让进程防止被终止掉(让进程可以正常关闭)
- 如果不设置后台线程,进程可能无法正常关闭
t.IsBackground = true;
- 关闭释放线程
// 方式一:如果线程使用死循环,并且由标识控制,可以更改这个bool标识让线程结束
isRuning = false;
// 方式二:终止线程
t.Abort();
t = null; // 置空
- 线程休眠
Thread.Sleep(1000); // 毫秒
5、多线程共享数据
如果多个线程同时访问一个内存区域,可能会产生冲突,可以通过加锁解决,语法lock(锁对象)
while(true){
lock(obj){ // 加锁
... 访问共同区域的代码
}
}
十三、预处理器指令
1、编译器
编译器是一种翻译程序,将源语言程序翻译为目标语言程序(如将C#源程序变成二进制的机器语言的程序)
2、预处理
预处理指令指导编译器在编译开始之前对信息进行预处理
3、常见预处理指令
- 定义一个符号
#define
- 取消define定义的符号
#undef
- if流程控制
#if
#elif
#else
#endif
- 警告和错误(一般配合#if使用)
#warning
#error
十四、反射
1、程序集(Assembly)
程序集是由编译器编译得到的,供进一步编译执行的中间产物。在Windows系统中,一般表现为.dll(库文件)或.exe(可执行文件)的格式。简单来说就是我们写的一个代码集合。
程序集是一个或多个管理代码和资源的逻辑单元。它们是C#和.NET程序结构的基本单位,也是部署和版本控制的最小单位。
2、元数据(metadata)
元数据就是用于描述数据的数据。简单来说就是程序中的类、函数、变量等信息就是程序的元数据,保存在程序集中
元数据是描述程序集内容的信息,包括类型定义、成员定义、引用的程序集等。元数据是自描述的,允许程序集在没有外部类型库的情况下进行类型检查和调用。
3、反射概念
程序运行时,可以查看它的程序集或自身的元数据。一个运行的程序查看本身或其他程序的元数据的行为就叫反射。
4、反射的作用
- 反射可以在程序编译后获取信息,提高了程序的拓展性和灵活性
- 程序运行时可以得到所有元数据,包括元数据的特性
- 程序运行时可以实例化对象,操作对象
5、反射的语法
(1)Type
概念:
Type是存储类信息的类,是反射功能的基础,是访问元数据的主要方式
语法:
- 获取Type:
// 1、万物之父object的GetType方法
int a = 1;
Type type = a.GetType();
// 2、通过typeof关键字
Type type2 = typeof(int);
// 3、通过类名(必须包含命名空间)
Type type3 = Type.GetType("System.Int32");
- 得到类的程序集信息:
Type t = typeof(Test);
t.Assembly
- 获取类中所有公共成员:
MemberInfo[] infos = t.GetMembers();
- 获取类的公共构造函数:
// 获取所有构造函数
ConstructorInfo[] ctors = t.GetConstructors();
// 获取指定构造函数:传入Type数组,数组内容是参数的Type
// 无参构造
ConstructorInfo ctor = t.GetConstructor(new Type[0]);
// 有参构造
ConstructorInfo ctor2 = t.GetConstructor(new Type[] { typeof(int) });
- 执行获取到的构造函数:
// 执行时传入object数组,表示传入的参数
// 无参构造函数
Test obj = ctor.Invoke(null) as Test;
// 有参构造函数
Test obj2 = ctor2.Invoke(new object[] { 2 }) as Test;
- 获取和使用类的公共成员变量:
// 获取所有公共成员变量
FieldInfo[] fieldInfos = t.GetFields();
// 获取指定公共成员变量
FieldInfo ageInfo = t.GetFiled("age");
// 使用(第一个参数传入对象)
Test test = new Test();
ageInfo.GetValue(test);
ageInfo.SetValue(test,18);
- 获取和使用类的公共成员方法:
// 获取类的所有方法
MethodInfo[] methods = t.GetMethods();
// 获取指定方法(指定方法名和参数,参数用Type数组)
MethodInfo subStr = t.GetMethod("SubStr", new Type[] { typeof(int), typeof(int) });
// 使用(使用object数组传参)
object result = subStr.Invoke("Hello World", new object[] { 5, 7 });
(2)Activator
概念:
用于快速实例化对象的类,可以将Type对象快捷实例化为对象
语法:
// 获取类的Type
Type testType = typeof(Test);
// 无参构造
Test t1 = Activator.CreateInstance(testType) as Test;
// 有参构造(第二个参数开始传入构造函数的参数)
Test t2 = Activator.CreateInstance(testType, 99) as Test;
(3)Assembly
概念:
程序集类,主要用于加载其他程序集,加载后才能用Type使用其他程序集中的信息。
如果使用不是自己的程序集的内容,需先加载程序集,如dll文件(库文件),库文件可以简单看作一种代码仓库,提供了一些可以直接用的变量、方法、类等等
语法:
- 加载程序集
Assembly assembly = Assembly.Load("程序集名称");
Assembly assembly2 = Assembly.LoadFrom("包含程序集清单的文件名称/路径");
Assembly assembly3 = Assembly.LoadFile("加载文件的路径");
- 获取程序集的Type
Type[] types = assembly.GetTypes();
十五、特性
1、概念
特性是一种允许我们向程序的程序集添加元数据的语言结构。是用于保存程序结构信息的某种特殊类型的类。
用于在运行时传递程序中各种元素(比如类、方法等)的行为信息的声明性标签。
简单来说可以用特性类为元数据添加额外信息,然后通过反射可以获取这些信息,进行一些特殊处理
2、自定义注解
继承基类Attribute
class MyAttribute : Attribute{
public string info; // 特性的成员,根据需求来写
public MyAttribute(string info){
this.info = info;
}
}
3、注解使用
语法: 其中参数列表为构造函数参数
[特性名(参数列表)]
可以使用的位置:
[MyAttribute("类")]
class MyClass{
[MyAttribute("成员变量")]
public int value;
[MyAttribute("成员方法")]
public void MyFunc( [MyAttribute("参数")] int a){
}
}
4、反射获取特性
MyClass mc = new MyClass();
Type t = mc.GetType();
// 判断是否使用了某个特性(参数1:特性的Type,参数2:是否搜索继承链,即是否搜索父类)
if (t.IsDefined(typeof(MyAttribute), false)){
Console.WriteLine("该类型使用了MyAttribute");
}
// 获取Type元数据的所有特性(参数:是否搜索继承链)
object[] array = t.GetCustomAttributes(true);
foreach (object a in array){
if (a is MyAttribute){
a as MyAttribute;
...
}
...
}
5、限制自定义特性的使用范围
通过为特性类添加特性AttributeUsage来实现,参数如下:
- AttributeTargets:特性的使用范围,能用在哪
- AllowMultiple:是否允许多个特性实例用在同一个目标上(能否重复在同一个地方使用)
- Inherited:特性是否能被派生类和重写成员继承
[AttributeUsage(AttributeTargets.Class | AttributeTargets.Struct,
AllowMultiple = true,
Inherited = true)]
public class MyAttribute : Attribute{
}
6、系统自带特性
- 过时特性
Obsolete[("过时信息", 是否报错)] // 参数1:信息提示,参数2:使用时,true报错,false警告
- 调用者信息特征
using System.Runtime.CompilerServices;
[CallerFilePath] // 指明哪个文件调用
[CallerLineNumber] // 指明哪一行调用
[CallerMemberName] // 指明哪个函数调用
实例:
public void SpeakCaller([CallerFilePath] string fileName = "",
[CallerLineNumber] int line = 0,
[CallerMemberName] string target = "")
{
Console.WriteLine(fileName); // 输出文件路径
Console.WriteLine(line); // 输出调用行数
Console.WriteLine(target); // 输出调用函数
}
- 条件编译特性
和#define配合使用,加上特性后,只有使用#define定义了这个名称才可以使用
using System.Diagnostics;
[Conditional("预编译符号名称")]
- 外部DLL包函数特性
用来标记非.Net(C#)函数,表明该函数在一个外部DLL中定义。一般用于调用C或者C++的DLL包写好的方法
using System.Runtime.InteropServices;
[DllImport("DLL包路径")]
public static extern int Add(int a,int b); //映射这个包路径的Add方法
十六、迭代器
1、概念
迭代器(iterator)有时又称光标(cursor),是程序设计的软件设计模式,迭代器模式提供一个方法顺序访问一个聚合对象的各个元素,而不暴露其内部的标识。
从表现效果看,是可以在容器对象上遍历访问的接口,设计人员无需关系内存分配的实现细节。
可以用foreach遍历的类,都是实现了迭代器的。
2、语法
接口:
using System.Collections;
IEnumerator,IEnumerable
foreach本质: 先获取in后面对象的IEnumerator(通过对象的GetEnumeratro方法),接着执行IEnumerator对象的MoveNext方法,如果返回值为true,就可以得到Current,然后赋值给item
foreach (int item in list){
... 处理item
}
3、使用yield return语法糖实现迭代器
所谓语法糖,主要是讲复杂的逻辑简单化,增加程序可读性。
实现IEnumerable接口后,使用yield return可以暂时返回值,保留当前状态,等到下次调用时再回来继续执行
class MyList : IEnumerable
{
private int[] list;
public MyList(){
list = new int[] { 1,2,3,4,5,6 };
}
public IEnumerator GetEnumerator(){
for (int i=0;i<list.Length;i++){
yield return list[i];
}
}
}
十七、特殊语法
1、var隐式类型
使用var可以表示任意类型的变量,不能作为类的成员,一般用于临时声明变量时,并且必须初始化。
2、设置对象初始值
new对象时后面接上大括号可以初始化公共成员变量和属性
Person p = new Person { sex = true, Age = 18, Name = "MrTang"};
3、设置集合初始值
声明集合对象时,在new完后可以接上大括号,初始化内部属性
int array = new int[] { 1, 2, 3, 4 };
List<int> list = new List<int>() { 1, 2, 3, 4 };
4、匿名类型
匿名类型是一个由编译器临时创建的类,用于存储一组值。匿名类型只能通过var关键字来引用。
var v = new { age = 10, money = 11, name = "MrTang" };
5、可空类型和Null传播器
- 可空类型
值类型在声明时加上?就可以赋null值,即可空类型
int? a = 3;
// 判断是否是空值
if (a.HasValue){
a.Value;
..
}
// 安全获取值,为null则返回参数值,如果不填参数则返回类型的默认值
a.GetValueOrDefault(100);
- Null传播器
引用类型在调用方法时在.前面加上?可以自动判空,如果对象为空则不执行(不报错)
object o = null;
Console.WriteLine(o?.ToString());
6、空合并操作符
语法:
如果左边值为null则返回右边值,否则返回左边值
左边值 ?? 右边值
实例:
int? v = null;
int i = v ?? 100;
// 等价于
int i = v == null ? 100 : v.value;
7、内插字符串
关键字$,用于字符串拼接
string name = "MrTang";
string str = $"好好学习,{name}";
8、单句逻辑简略写法
// 判断
if (true)
Console.WriteLine("if省略大括号");
// 循环
for (int i = 0;i < 10; i++)
Console.WriteLine("for省略大括号:" + i);
// 属性
public string Name{
get => "MrTang";
set => name = value;
}
// 函数
public int Add(int x, int y) => x + y;
十八、值类型与引用类型补充
1、分类
- 值类型:
- 无符号:byte,ushort,uint,ulong
- 有符号:sbyte,short,int,long
- 浮点数:float,double,decimal
- 特殊:char,bool
- 枚举:enum
- 结构体:struct
- 引用类型:
- string
- 数组
- class
- interface
- 委托
2、区别
值类型和引用类型的本质区别是,值类型的具体内容存在栈内存上,引用类型的具体内容存在堆内存上。
3、变量的生命周期
编程时大部分都是临时变量,当语句块执行结束时,没有被记录的对象将被回收或变成垃圾。
值类型会被系统自动回收,引用类型在栈上的存地址的空间被系统自动回收,堆中的具体内容变成垃圾,等待下一次GC回收
4、结构体中的值和引用
结构体本身是值类型,前提是没有作为其他类的成员。
在结构体中的值,栈中存储值的具体内容;在结构体中的引用,堆中存储引用的具体内容。
5、类中的值和引用
类本身是引用类型
在类中的值,堆中存储具体的值;在类中的引用,本身存储在堆上的对象实例内部,但它指向的具体数据存储在堆的另一个位置。