由C++速通C# 本篇文章的核心在于通过比较C#与C的不同来快速上手C#。一.变量类型1.值类型int,double,float,bool等都与c相同值类型可以赋值为空null不过这里需要加上如int? i null;称为可空值类型。2.引用类型如class string interface delegate record等存储的是数据的引用两个变量可以控制同一对象引用类型可以引用一个空对象null。3.一般局部变量方法里的临时变量都是存放在栈区而引用类型一般都是存放在堆区。4.对于数组C#定义的方式与C不同与java相似如int[] a new int[10];也可以简写为int[] a [10];二维数组int[,] a { { },{ } }数组也属于引用类型。5.装箱将值类型转化为引用类型装箱的本质是在堆上创建一个对象把值类型的数据打包装进去并返回一个引用任何类型都直接或间接的派生自object类所以任何值类型都可以隐式转化为object类型装箱是在堆上创建了值类型的副本返回的是副本的引用无法通过装箱返回的引用来改变原始数据的值拆箱就是装箱的逆过程将引用类型再转化为值类型但是拆箱的结果必须是原类型否则会抛出异常拆箱的语法与强制类型转化相同装箱的语法与C引用类似。6.is与as如bool bobj is int;判断某个对象是否是指定类型并返回bool值一般用于引用转化装箱拆箱as尝试把对象转换为指定的引用类型或可空类型,成功返回该类型对象,失败返回nullas可用于引用转换和装箱转换,不能用于值类型转换(因此不可用作拆箱)is与as都不能用于自定义类型。注意与c不同的地方1.float类型的数据赋值时数据后面必须加上f如float a8.8;(错误直接无法编译通过) 应该为float a8.8F浮点数自动解释为double类型;2.c#存在decimal类型用于精确存储浮点数我们都知道使用float与double存储的浮点数是近似值demical类型可以以十进制的形式准确存储浮点数但其计算效率相对较低所以凡是涉及金钱、财务、税率等对精度要求极高的场景必须使用 decimal而在科学计算、图形渲染等对性能要求高且允许微小误差的场景则使用 double。3.c#中的自动类型推导是关键字var二.方法1.C#不允许全局方法。2.表达式主体如 public void show() Console.WriteLine(age);可用于简化一些简单的方法。3.对于C#方法的参数传递存在以下几个关键字ref类似于C的引用表示以引用方式传参一般用向方法内部传递变量向内传递一定要在方法外进行初始化in相当于const同样是引用传递以只读方式传参in关键字在传递时可以不加out用于输出参数同样可以看成是引用不过与ref不同的是它一般用于向方法外传递变量可以不在方法外初始化在方法内进行初始化由于次特性我们可以在传递参数的同时进行声明即test(out var f)使用这些关键字传递参数我们在调用方法传参时与方法的形参列表中都要在变量的前面加上。4.C#方法的可变参如int test(params int[] number)相当于接收了一个int类型的动态数组。需要注意的是这里如果存在多个参数那么参数数组必须放在最后一个参数的位置。常见方法1.打印/输出方法Console类的Write方法不换行与WriteLine方法自动换行占位输入1Console.WriteLine(${a}{b}{result}) (一般都使用第一种)左对齐与右对齐Console.WriteLine(${a,-10}{b}{result})左对齐需要加上-号后面数字表示预留空间(2)Console.WriteLine({0}{1}{2},a,b,result)2.输入方法Console类的ReadLine方法返回值为string类型一般需要通过int.parse等方法进行类型转化。如int result int.Parse(Console.ReadLine());2.数组方法GetLength方法传入0计算二维数组的行数/一维数组的元素个数1计算列数三.类1.与java相同C#的一个类一般放在一个单独的文件中。2.c#类内定义的静态成员变量可以直接在类内初始化而C需要在类内声明在类外初始化访问方式上c#访问静态变量与访问普通变量相同都使用 “.” 而C中需要使用类名加作用域操作符“::”。3.与C相同类内定义的静态成员方法只能使用静态成员变4.类的实例化方式与java相同都必须使用 Test t new Test ();也可以简写为Test t new ()原因在于C#自动实现底层内存的管理大部分变量类都是直接开辟到堆区的自动释放而C更为灵活由程序员自己控制堆区内存的开辟与释放。5.C#保留了栈上分配的能力主要通过struct来实现不同于CC#中的struct与class存在很大差别比如不能有默认的无参构造函数不能被继承等C#中的struct不是类而是值类型C#中的class与struct是没有默认的访问限定符的C#中的struct一般用于存放一组相关的数据成员也存在构造函数字段属性方法使用结构体必须初始化全部的成员变量无论有没有手动提供构造函数编译器都会为结构体提供一个默认构造函数。6.c#中不允许使用C样的public只能使用类似java的单独指定访问限定符。7.C#中的属性与C略有不同C#的属性包括字段后备字段与方法两部分与C相同为了提高函数的封装性C#同样需要将属性设置为私有但是与C不同的是我们写的private int age;只是属性的字段属性还包括get与set访问器本质是方法来控制对于私有字段的读写。如图class Test { private int _age; public int Age { get{ return _age; } set { _age value; } } }但是我们看到这样写的话稍微有些复杂所以我们需要使用自动属性public int Age{get;set;}编译器会自动的为我们生成字段但是这样与单单写一个public的字段没有什么区别所以为了代码的封装性我们一般写成public int Age{get;private set;}来防止外部随意修改但是对于一些一旦确定就无需修改的成员变量我们这样写可以防止内外修改却没办法防止类内修改所以对于这种情况我们需要一个新的访问器init它表示的是我们只能在构造函数与初始化阶段对其进行初始化这之后无论类内类外都无再修改8.我们实例化一个对象后可以使用对象的初始化语句进行快速初始化并且其属于初始化的一部分可以对于init的字段进行初始化同时注意对象的初始化语句运行是在构造函数之后的,如class Test { public int Age{get;init;} } //.... Test tnew(); { Age10; }9.constreadonlyconst与C相同一旦声明必须在声明同时初始化编译时期确定一个初始化无法改变readonly用于修饰字段我们可以在声明时不进行初始化在构造函数中对其进行初始化经过这次初始化后无法再进行修改是运行时常量一般对于类内的字段我们都将其设置为readonly。10.C#中的this不再是指针而是引用。11.由于C#的内存管理方式与C不同存在垃圾回收机制所以绝大部分情况下我们不需要手动的对资源进行回收释放即不需要手动写析构函数。四.语句1.C#的switch case语句为了安全考虑在每一个case后面必须加上break否则编译就无法通过除非这里的case1中不存在任何内容。2.C#的foreach语句如foreach(var v in arr)相较于C关键字变为了foreach中间变成了in3.lambda表达式与C相同C#也存在lambda表达式不过结构上存在不同基本模式是(输入参数) 表达式或语句块注意无参数() 不能省略单个参数括号 () 可以省略这是最简洁的写法多个参数必须使用括号 () 包裹多行语句块如果逻辑比较复杂需要用大括号 {} 包裹并显式使用 return。五.继承与多态1.C#不允许多重继承但C允许。2.C#中继承相较于C不需要写访问限定符。3.对于继承中如果父类存在构造函数子类需要调用父类的构造函数我们可以使用base关键字来调用父类的成员最常见的就是调用父类的构造函数base()。4.C#中存在object类它是所以类型的基类。5.sealed关键字用于阻止继承被sealed修饰的类无法被继承修饰的方法无法在子类中重写。6.C#中存在6种访问限定符publicprivateprotected与C基本相同internal可在当前程序集中访问protected internal可在当前类或派生类或者当前程序集的任何联系访问private internal 仅限当前程序集定义该成员的类或派生类可访问其中public与internal可以直接修饰顶级类型类型分为两种顶级与嵌套。7.与C类似C#中也存在虚方法与方法的重写覆盖与隐藏不同的是C#子类重写父类中的虚方法需要在子类重写方法前添加override关键字。8.C#中同样存在抽象类抽象类专门用于继承不可被实例化其实现不同于C如果我们想要实现一个抽象类需要将需要重写的父类方法的virtual关键字改为abstract关键字将其由虚方法变为抽象方法抽象方法没有函数体不需要像C一样写0存在抽象方法的类就变为了抽象类抽象类必须在类的最前面加上abstract关键字。9.C#中存在接口需要在前面加上interface关键字一般命名需要在名称前面加上一个大写的I接口 最核心的作用就是“定义契约”。它只规定了一组方法、属性或事件的签名即“能做什么”但不提供任何具体的实现即“怎么做”。任何类或结构体只要实现了这个接口就必须严格履行这份契约接口默认是public的一般不加访问限定符类实现接口的语法与继承相同。接口有一种类似泛型的思想比如我们想要使用同一个方法遍历几种不同的可枚举类型我们可以将方法的形参改为IEnumberable接收所以可枚举类型来实现C中需要模版才能实现的效果。10.C#中同样存在运算符重载运算符重载必须是public static的C#中二元运算符必须接收两个参数特别的C#存在自定义的类型转化我们可以自定义显示public static explicit operator与隐式public static implicit operator的类型转化。六.委托与事件如public delegate int MyDelegate(int a,int b);作用有些类似于C的functional或者函数指针委托本质是也是一个类需要delegate关键字可以使用一个方法或者成员方法对其进行初始化如MyDelegate delCalculator.Add;假设我们这里存在Calculator类类内存在Add方法与Sub方法我们就使用MyDelegate对Add成员方法进行了委托应该委托可以委托多个方法称为多播委托如我们再写delcalc.Sub()这里Add是静态方法Sub是普通方法所以Sub还需要依赖于对象calc是我们创建的一个对象委托多个方法我们如果调用的话会根据委托顺序依次调用各个方法我们也可以使用-号取消委托对于多播委托我们调用方法的返回值固定是最后一个方法的返回值。事件event为对象之间提供了一种通信方式,它们基于发布者-订阅者模型:当特定事情发生时,发布者会通知其他订阅者事件是基于委托的封装我们应该使用标准事件模型而非自定义事件如public event MyDelegate MyEvent;事件必须依赖于委托存在需要event关键字事件在使用时需要触发如MyEvent?.Invoke(int a,int b);七.泛型C#中的泛型类与泛型方法与C中的类模版与函数模版类似。Action泛型是一个泛型委托无返回值Func同样是一个泛型委托存在返回值Predicate谓词只接受一个参数返回值固定为bool。泛型约束在定义的泛型类或者泛型方法后面加上where T :struct(struct表示限制T为非空值类型)我们还可以使用class限制T为引用类型new()限制T必须存在public的无参构造函数与其他约束组合时必须放在最后基类名称限制T必须继承自该基类泛型集合类似于C容器ListT与C的vector类似添加成员方法Add删除方法Removecount方法与Csize相同都是动态数组Dictionarykey,value相当于C的unordered_mapQueueT与Cqueue相同StackT与C的stack相同。八.异步编程C#中的异步编程与C中的多线程存在相似却又有着很大不同C#中异步的目的与C多线程相同都是为了提高运行效率但是异步的核心目的是为了防止程序在遇到耗时操作时导致程序阻塞多线程的目的是为了实现并行C#的异步不一定是多线程其本质的并行轮流运行表现的像并发。C#中的异步是在遇到第一个 await 关键字之前的所有代码都在同一个线程运行当程序遇到await 一个耗时操作时当前线程会被立即释放去处理其他任务当这个耗时操作完成后程序需要运行后面的代码但不一定会回到原来的那个线程。C#异步编程的核心是async与await关键字async关键字的作用是声明一个方法为异步方法其方法体内包含awaitawait的作用是在先暂停这个方法立即释放这个线程去处理其他任务当等待完成后再在线程池中取一个线程去完成接下来的任务。C#中也存在多线程异步本质与多线程是不同的东西C中也存在类似异步的方法。如我们举一个经典例子假设我们想要执行点外卖-》等外卖-》吃外卖-》学习C#这一流程同步方法的代码如图static void OrderDelivery() { Console.WriteLine(ordering delivery); } static void WaitDelivery() { Console.WriteLine(waiting delivery...); sleep(5000); Console.WriteLine(delivery arrived); } static void EatDelivery() { Console.WriteLine(eat delivery...); sleep(5000); Console.WriteLine(finish eating); } static void LearnCsharp() { Console.WriteLine(learning C-sharp...); sleep(5000); Console.WriteLine(finish learning); } static async Task Main(string[] args) { var p Stopwatch.StartNew();//记录运行时间的方法 OrderDelivery(); WaitDelivery(); EatDelivery(); LearnCsharp(); p.Stop(); Console.WriteLine(p.Elapsed.TotalSeconds); }那么这个程序最后的运行时间就是20秒但是我们如果采用异步的方法就可以在等外卖与吃外卖的过程中学习C#提高程序运行的效率如图static void OrderDelivery() { Console.WriteLine(ordering delivery); } static async Task WaitDelivery() { Console.WriteLine(waiting delivery...); await Task.Delay(5000); Console.WriteLine(delivery arrived); } static async Task EatDelivery() { Console.WriteLine(eat delivery...); await Task.Delay(5000); Console.WriteLine(finish eating); } static async Task LearnCsharp() { Console.WriteLine(learning C-sharp...); await Task.Delay(10000); Console.WriteLine(finish learning); } static async Task Main(string[] args) { var p Stopwatch.StartNew(); OrderDelivery(); var waitingdelivery WaitDelivery(); var learingCsharp LearnCsharp(); await waitingdelivery; await EatDelivery(); await learingCsharp; //这里注意 await waitingdelivery与await WaitDelivery()的区别 //await WaitDelivery()会重新启动一个新的方法并等待其结束会发生阻塞 //而await waitingdelivery //只是等待原来的方法结束并且会释放这个线程不会发生阻塞 //WaitDelivery(); //LearnCsharp(); //await WaitDelivery(); //await EatDelivery(); //await LearnCsharp(); p.Stop(); Console.WriteLine(p.Elapsed.TotalSeconds); }这样程序的运行时间就减少为了10秒