C#语法笔记
目录
目录
1. C#的最新版本应该是7.0,每一个版本都会有一个焦点特性
由于工作需要,最近要学C#,所以选了一门入门较快的书籍《C#图解教程》,虽然之前有C++和python的基础,但是C#还是有很多不同的特性,所以在此记录如下,以备自己日后查阅
第一章
1. C#的最新版本应该是7.0,每一个版本都会有一个焦点特性
版本 | 焦点特性 |
其他特性 |
7.0 | 语法 |
out变量 模式匹配 元组 解构 局部函数 数字分隔符 二进制文字 局部引用和引用返回 扩展异步返回类型 表达式的构造函数和finalizers Expression bodied getters and settersthrow表达式 |
6.0 | 语法 |
Compiler-as-a-service(Roslyn) 将静态类型成员导入命名空间 异常过滤器 在Catch和Finally中使用Await 自动属性初始化器 只读属性的默认值 Expression-bodied members Null-conditional operators(空条件运算符,简洁检查) 字符串插值 nameof operator字典初始化器 |
5.0 | 异步 |
Caller info attributes |
4.0 | 命名参数和可选参数 |
动态绑定 命名和可选参数 Generic co- and contravariance 嵌入式互操作类型(“NoPIA”) |
3.0 | LINQ |
隐式类型局部变量 对象和收集初始化器 自动实现的属性 匿名类型 扩展方法 查询表达式 Lambda表达式 表达树 部分方法 |
2.0 | 泛型 |
部分类型 匿名方法 迭代器 可空类型 Getter / setter单独可访问性 方法组转换(代表) Co- and Contra-variance for delegates 静态类 Delegate inference |
1.0 | C# | 基本框架 |
了解C#版本的发展对于学习C#也是有一定帮助的。
第二章
1. 格式字符串
Write语句和WriteLine语句的常规形式中可以有一个以上的参数。如果参数不止一个,参数用逗号分隔,第一个参数必须总是
字符串,称为格式字符串。格式字符串可以包含替代标记。
- 替代标记在格式字符串中标出位置,在输出串中该位置用一个值来替代
- 替代标记有一个整数即扩住它的大括号组成,其中整数就是替换值数字位置,跟着格式字符串的参数称为替换值,这些替换值是从0开始的
语法如下:
Console.WriteLine(格式字符串(含替代标记),替换值0,替换值1,……)
示例如下:
Console.WriteLine("Two sample integers are {0} and {1}",2,4);
//输出结果为:
Two sample integers are 2 and 4
格式化字符串还有很多内定的格式,如货币,百分比,时间等等,可参看具体的说明文档。
第三章
1.预定义类型
C#中一共有16种预定义类型,其中有三种非简单类型如下:
- string ,它是一个Unicode字符数组
- object, 它是所有其它类型的基类
- dynamic, 使用动态语言编写的程序时使用
所有类型关系如下:
2.用户定义类型
还有6种类型用户可以自己创建,如下
类类型 | class |
结构类型 | struct |
数组结果 | array |
枚举类型 | enum |
委托类型 | delegate |
接口类型 | interface |
类型通过类型声明创建,类型声明包含以下信息:
- 要创建的类型的种类
- 新类型的名称
- 对类型中每个成员的声明,array和delegate除外。
一旦声明了类型,就可以创建和使用这种类型对象,就像它们时预定义的类型一样。
3.自动初始化和多变量声明
一些变量如果在声明时没有初始化,那么会被自动设置为默认值,一些则不能。没有自动初始化的默认值的变量在程序给它赋值之前包含为定义值。
变量类型 | 存储位置 | 自动初始化 | 用途 |
本地变量 | 栈或者栈和堆 | 否 | 用于函数成员内部计算 |
类字段 | 堆 | 是 | 类的成员 |
结构字段 | 栈或堆 | 是 | 结构的成员 |
参数 | 栈 | 否 | 用于将值传入或传出的方法 |
数组元素 | 堆 | 是 | 数组的成员 |
C#种可以把多个变量声明在一条单独的声明语句中
- 多变量声明中的变量必须类型相同
- 变量名必须用逗号分隔,可以在变量名后包含初始化语句
int var3=7,var4,var5=3;
double var5, var8=89;
第四章
无
第五章
1.类型推断和var关键字
有时候编译器能从初始化右边的语句推断出类型的信息,考虑如下代码:
static void main()
{
int total=15;
MyExcellentClass mec=new MyExcellentClass();
}
在上面的代码中,第一个变量的声明,编译器能推断出15是int类型,第二条语句返回一个MyExcellentClass类型,所以在以上两种情况下,声明中包含显示的类型名是多余的,因此C#提供了关键字var,是的上面的语句可以改为:
static void main()
{
var total=15;
var mec=new MyExcellentClass();
}
使用var关键字又一些重要条件:
- 只能用于本地变量,不能用于字段
- 只能在变量声明中包含初始化时使用
- 一旦编译器推断出变量的类型,他就是固定且不能改变的
2.引用类型作为值参数和引用参数
对于一个引用类型对象,不管是将其作为值参数还是引用参数,我们都可以在方法成员被捕修改它的成员,若在方法内部设置型参本身,则会有2一些区别。
- 将引用类型对象作为值参数传递 如果在方法内创建一个新对象并赋值给型参,将切断型参和实参的关联,并且在方法结束之后,新对象也将不复存在
- 将引用对象作为引用参数传递 如果在方法内创建一个新对象并赋值给形参,在方法结束后该对象依然存在,并且是实参所引用的值。
下面依次展示两种情况:
class MyClass { public int Val=20;}
class Program
{
static void RefAsParameter(MyClass f1)
{
f1.Val=50;
Console.WriteLine("After member assignment: {0}",f1.Val);
f1=new MyClass();
Console.WriteLine("After new Object creation: {0}",f1.Val);
}
Static void main()
{
MyClass a1=new MyClass();
Console.WriteLine("Before method call: {0}", a1.Val);
RefAsParameter(a1);
Console.WriteLine("After method call: {0}",a1.Val);
}
}
输出如下:
Before method call: 20
After member assignment: 50
After new Object creation: 20
After method call: 50
之所以会出现这种情况是因为:
- 在方法开始时,实参和形参都指向堆中相同的对象
- 在为对象成员赋值之后,它们仍指向堆中相同的对象
- 当方法分配新的对象并赋值给实参时,实参仍指向原始对象,而形参指向新对象
- 在调用方法之后,实参指向原始对象,形参和新对象消失
可以用如下图来解释:
而当应用类型对象作为引用参数时,则发生了一些变化,代码如下:主要是对于静态方法的参数加了ref,
class MyClass { public int Val=20;}
class Program
{
static void RefAsParameter(ref MyClass f1)
{
f1.Val=50;
Console.WriteLine("After member assignment: {0}",f1.Val);
f1=new MyClass();
Console.WriteLine("After new Object creation: {0}",f1.Val);
}
Static void main()
{
MyClass a1=new MyClass();
Console.WriteLine("Before method call: {0}", a1.Val);
RefAsParameter(a1);
Console.WriteLine("After method call: {0}",a1.Val);
}
}
运行结果如下:
同样:注意以下几点
- 在方法调用时,形参和实参都指向堆中相同的对象
- 对成员值的修改会同时影响到形参和实参
- 当方法创建新的对象并赋值给形参时,形参和实参的引用都指向该对象
- 在方法结束后,实参指向在方法内创建的新对象
图解如下:
其实上述两种调用的本质差别在于,作为值传递时,实参和形参是两个对象,只是一开始形参和实参指向的位置相同,而作为引用参数传递时,传递的就是实参本身,整个过程都只有一个对象。
3.传出参数
输出参数用于从方法体内把数据传出到调用代码,它们的行为与引用参数特别类似,如同引用参数,输出参数有以下要求:
- 必须在声明和调用中都使用修饰符out
- 实参必须是变量,而不能是其它类型表达式,因为方法需要内存保存返回值
示例如下:
void MyMethod(out int value)
{
...
}
int y;
MyMethod(out y);
与引用参数类似,输出参数的形参担当实参的别名,实参和形参都是同一块位置的别名,但与引用参数不同在于:
- 在方法内部,输出参数在能够被读取之前必须被复制,这意味着参数的初始值是无关的,而且没有必要在方法调用之前为实参赋值
- 在方法返回之前,方法内部贯穿的任何路径都必须为所输出的参数赋一次值
如果方法中有任何执行路径试图在输出参数赋值之前读取他,编译起就会产生错误信息
public void Add2(out int value)
{
int x=value+2;//出错,在赋值之前无法读取
}
4.参数数组
参数数组允许领个或多个实参对应一个特殊的型参。主要内容为:
- 在一个参数列表中,只能有一个参数数组
- 若果有,它必须是参数的最后一个
- 由参数数组表示的所有参数都必须具有相同的类型
- 声明一个参数数组,需要在那数据类型前加params关键字
- 在数据类型后放置一组空的方括号
一个包含int型参数数组的例子如下:
void ListInts(params int[] values)
{
...
}
而方法的调用方式可以有:
- 一个逗号分隔的该数据类型元素列表,所有元素的类型一致且和参数类型相同
ListInts(10,20,30);
- 一个该数据类型原属的一位数组
int[] intArray={1,2,3};
ListInts(intArray);
在使用一个参数数组时,编译器如下运作:
- 接受实参列表,用他们在堆中创建并初始化一个数组
- 把数组的引用保存到栈中的形参里
- 如果对一个的形参数组的位置没有实参,编译器会创建一个有0个元素的数组来使用
5.命名参数
前面所提到的参数都是位置参数,也就是实参和形参的位置一一对应,但和python中的命名参数一样,C#也1命名参数,只要显示的指定参数的名字,就可以以任意顺序调用这些实参。
class Myclass
{
public int Calc(int a, int b, int c)
{
return (a+b)*c;
}
static void main()
{
MyClass mc=new MyClass();
int result=mc.Calc(c:2,a:4,b:3);
Console.WriteLine("{0}",result);
}
}
在调用参数时,你既可以适应位置参数,也可以使用命名参数,但如果这么做,那么位置参数必须先列出。
第六章
1.readonly修饰符
字段可以用readonly修饰符声明,3作用类似于将字段声明为const,一旦值被设定就不能改变。
- const字段只能在字段的声明语句中初始化,而readonly字段可以在下列2种情况设置他的值
1. 字段声明语句
2. 类的任何构造函数
- const字段的1必须在编译时决定,而readonly字段可以在运行时决定
- 和const不同,const的行为总是静态的,而对于readonly字段,以下两点时正确的
1.它可以是实例字段,也可以是静态字段
2.它在内存中有存储位置
class Shape
{
readonly double PI=3.1416;
readonly int Num;
//在构造函数在设定Num
public Shape(double side1,double side2)
{
Num=4;//表示一个矩形
}
public Shape(double side1,double side2)
{
Num=3;//表示一个三角形
}
}