C¶
.Net¶
dotnet --info //信息
dotnet new console --output ./Exp //新建项目
dotnet run --project Exp //运行项目
C#类型¶
C# 是一种面向对象的强类型语言,具有一个庞大的类型系统。C# 类型系统的特点是,一切类型(除指针类型)均继承自 object ( System.Object )类,即 object 类型是一切类型(除指针类型)的基类。
C# 的类型分为两种:值类型和引用类型(以及指针类型)。
System.ValueType 类(该类派生自 object 类)是所有值类型的直接或间接基类,其中结构类型 ( int 、自定义结构类型,等等)都直接派生自 System.ValueType 类,而枚举类型则直接派生自 System.Enum 类(该类派生自 System.ValueType 类)。
值类型不可被继承。
引用¶
引用类型对象实际上是两部分:一部分是对象真正的数据,开辟在托管堆上,另一部分是真正对象的引用,储存在内存栈或其他任何位置。
常用的引用类型包括 object 、 string ,或是程序员自己定义的类( class ),接口( interface ),委托( delegate ),事件( event )等等。
装箱与拆箱¶
object 是C#的一个关键字,表示类型 System.Object ,它是所有类型(除指针类型)的公共基类,是一个引用类型。因此,object类型可以指向所有类型的托管对象。
我们知道,值类型不存在引用,那么一个 object 类型该如何指向值类型呢?
这就是装箱(Boxing)与拆箱(Unboxing)的机制。
当我们把一个值类型强制转换为 object 类型时,会在托管堆上 new 出一个 object 对象,用来存储这个值类型,这样就把值类型转换成了 object 类型。这个过程叫做“装箱” 。
将一个装箱产生的 object 对象转回值类型的过程,就叫“拆箱”,这个过程会将储存在托管堆上的数据拿出来。
这样,通过装箱和拆箱,我们就实现了让每一个值类型都可以被转换为 object 类型。这种统一的类型系统为我们编程带来了巨大的便利。
但是,频繁的装箱和拆箱也会有较大的性能损失,所以尽量避免大量的装箱与拆箱操作。
int x;
object o = x;
int y = (int)o;
值类型进行赋值是将该值类型的对象进行复制,而引用类型的复制是复制引用,而非对象本身。
函数传参时传入int并不会改变函数外int对象的值,而传入class会。
GC¶
动态内存开辟在托管堆上。所谓托管堆,顾名思义,就是被托管了,开辟的空间资源不需要程序员手动回收,.NET提供了垃圾回收(GC)的机制完成GC。
类¶
访问修饰符¶
private : 这个字段只能够被本类所访问
public :这个字段可以被随意访问
protected :这个字段可以被本类及其派生类访问
internal :这个字段可以在本程序集内随意访问
protected internal :既可以被本来及其派生类访问,又可以在本程序集内随意访问
字段的默认值¶
C# 的字段可以定义默认值。若未指定默认值,则对于值类型默认值为 0 ,对于引用类型默认值为null 。
静态字段与常量¶
类可以含有静态字段,静态字段用 static 修饰。静态字段需要用“类名.字段名”的方式访问。
类内也可以定义常量,用 const 修饰,常量不允许被修改。与静态字段一样,常量也必须由“类名.字段名”的方式访问。
class Person
{ public string name;
private int age = 18;
public static int population;
public const string school = "THU"; }
属性¶
属性的访问器(大驼峰命名)
private int age;
public int Age {
get { Console.WriteLine("Get Age!");
return age; }
set { Console.WriteLine("Set Age!");
age = value >= 0 ? value : 0; } }
get 访问器
:当外部需要读取属性的值时,会调用 get 访问器。
set 访问器
:当外部要设置属性的值时,会调用 set 访问器, value 是要设置的值。
init 访问器(C# 9.0)
:只允许在对象构造期间设置属性的值。
简化函数体(=>)¶
如果函数体非常简短,可以使用 => 运算符:
class Adder
{ static public int Add(int x, int y) => x + y; }
委托¶
“委托”是一种引用类型,作用与函数指针类似。需要用 delegate 关键字定义一个委托类型。委托类型的定义格式与方法类似,只是在返回值类型前加上 delegate 关键字:
delegate int Operate(int x, int y);
public static int Add(int x, int y)
{ var res = x + y;
Console.WriteLine($"{x} + {y} = {res}");
return res; }
Operate Op = new Operate(Add);
该段代码定义了一个委托类型,名字叫 Operate 。该委托可以接收参数为 (int, int) ,返回类型为int 的方法。
与其他引用类型一样,我们需要用 new 关键字创建一个委托,并将一个方法赋给这个委托。
Op(2, 1);
Op?.Invoke(2, 1);
继承¶
public class Derived : Base { }
public sealed class Foo { } #密封类不可以被继承
事件¶
一般的多播委托存在一个问题:外部对象可以任意修改已发布的委托(如果该委托是一个公有成员),这就导致外部对象的订阅不再可靠。
例如,外部对象在订阅该委托时,直接将该委托置为 null ,这就直接导致其他订阅者的订阅失效,订阅因此不再可靠。
事件使用 event
关键字定义,其本质是一个特殊的多播委托。
事件只支持“订阅”与“取消订阅”两种操作,不允许直接对事件赋值,这保证了用户订阅的可靠性。
外部对象只能使用 += 、 -= 运算符对事件进行"订阅"、"取消订阅"。即事件只能放在 += 、 -= 运算符的左侧。
public event EventHandler onWarning; // ...
onWarning += Function1; // Correct
onWarning -= Function1; // Correct
onWarning = null; // Error !
onWarning = onWarning + Function2; // Error !
onWarning?.Invoke();
Lambda表达式¶
(参数列表) => { 函数体 }
当参数只有一个时,参数列表的括号可以不写。
当函数体只有一句时,或只有返回值时,可以简写,如:
() => 1
var getAddOne = new Func<int, int>(x => x + 1); #尖括号内指定输入值和返回值
partial类¶
C# 允许将类分开定义,可以使用 partial 关键字。
// in file 1:
public partial class A { }
// in file 2
public partial class A { }