- 一、第一个C#程序
- 二、基础类型
- 1、预定义类型
- 2、自定义类型
- 2.1、实例成员与静态成员
- 2.2、public关键字
- 3、类型转换
- 3.1、显示转换
- 3.2、隐式转换
- 4、值类型与引用类型
- 4.1、值类型
- 4.2、引用类型
- 4.3、null
- 4.4、存储开销
- 5、数值类型
- 5.1、数值字面量
- 5.2、数值后缀
- 5.3、整数溢出
- 5.4、特殊的float和double
- 5.5、double和decimal的区别
- 6、布尔类型
- 7、字符串和字符
- 7.1、字符类型
- 7.2、char类型转换
- 7.3、字符串类型
- 原意字符串
- 字符串插值
- 8、数组
- 8.1、数组的声明
- 8.2、数组的默认初始化值
- 9、变量和参数
- 9.1、栈和堆
- 9.2、参数
- 9.3、按值传递参数
- 9.4、ref关键字
- 9.5、out关键字
- out变量及丢弃变量
- 9.6、按引用传递参数
- 9.7、params修饰符
- 9.8、可选参数
- 9.9、命名参数
- 9.10、引用局部变量(c#7)
- 9.11、引用返回值(c#7)
- 9.12、var隐式类型局部变量
- 10、表达式和运算符
- 11、null运算符
- null合并运算符
- null条件运算符(c#6)
- 12、语句
- 12.1、if else 语句
- 12.2、switch语句
- 普通switch
- 带有模式的switch语句(C# 7)
- 12.3、迭代语句
- while语句
- do....while 语句
- for循环
- foreach循环
- 跳转语句
- break
- continue
- goto
- return
- throw
- 其他语句
- 13、命名空间
- 13.1、using指令
- 13.2、using static 指令(c#6)
- 13.3、命名空间的规则
- 名称范围
- 名称隐藏
- 重复的命名空间
- 嵌套的using指令
- 类型和命名空间别名
- 13.4、高级命名空间特性
- 外部别名
- 命名空间别名限定符
一、第一个C#程序
Hello World !!!
using System;
namespace Project{
class Test{
static void Main(){
//输出语句 在控制台打印hello world
Console.WriteLine("hello world");
}
}
}
二、基础类型
1、预定义类型
c#的预定义类型有:
值类型
- 数值
- 有符号整数(sbyte、short、int、long)
- 无符号整数(byte、ushort、uint、ulong)
- 实数(float、double、decimal)
- 逻辑值(bool)
- 字符(char)
引用类型
- 字符串(string)
- 对象(object)
C#的预定义类型又称为框架类型,它们都在System命名空间下。下面的两个语句仅在拼写上有所不同:
int num = 10;//两种写法等价
System.Int32 = 10;
2、自定义类型
using System;
namespace Variable
{
class Test1
{
int num1;//字段
public Test1(int num1) //构造器
{
this.num1 = num1;
}
public int add()//方法
{
return num1 + 10;
}
}
class Test2
{
static void Main()
{
//自定义类型Test1 使用new会进行对象的实例化 通过构造器进行值的初始化
Test1 test1 = new Test1(10);
//通过创建的实例来调用方法
Console.WriteLine(test1.add());//20
}
}
}
2.1、实例成员与静态成员
实例是new关键字对对象进行实例化,实例化后的对象拥有自己的实例成员,而静态成员通过static关键字进行修饰,是所有实例成员所共有的
using System;
namespace Variable
{
class Test1
{
//实例成员 num1
public int num1;
//静态成员 desc
public static string desc="aaa";
public Test1(int num1,string desc)
{
//静态成员不能通过this关键字来调用,因为是每个实例共享,而this代表当前实例
Test1.desc = desc;
this.num1 = num1;
}
public int add()
{
return num1 + 10;
}
}
class Test2
{
static void Main()
{
Console.WriteLine(Test1.desc);//aaa
Test1 test1 = new Test1(10,"我是test1");
Console.WriteLine(Test1.desc);//我是test1
Test1 test2 = new Test1(10, "我修改desc为test2");
Console.WriteLine(Test1.desc);//我修改desc为test2
}
}
}
2.2、public关键字
public关键字将成员公开给其他类。在上述示例中,如果Test1类中的desc字段标记为公有(public)的,如果没有标记那他就是私有的,通过Test1.的形式就访问不到了。
3、类型转换
3.1、显示转换
//num 为int类型
int num = 10;
//i是short类型 需要把int类型转为short类型
short i = (short)num;
显示转换可能会导致精度的缺失
例如
int num = 1000000;
//因为short只有2个字节 所以int转short时只能装16个字节
//1000000 转为二进制 为 1111 0100 0010 0100 0000
//去掉多的四位 0100 0010 0100 0000 转为十进制 是16960
short i = (short)num;
Console.WriteLine(i);//16960
显示转换只有在以下条件都满足时才能进行:
- 编译器不能保证转换总是成功。
- 信息在转换过程中有可能丢失。
3.2、隐式转换
int num = 1000000;
//long比int大两倍 所以隐式转换
long i = num;
Console.WriteLine(i);
隐式转换只有在以下条件都满足时才能进行:
- 编译器能确保转换总能成功。
- 没有信息在转换过程中丢失。
4、值类型与引用类型
4.1、值类型
值类型的变量或常量的内容仅仅是一个值。例如,内置的值类型int的内容是32位的数据。可以通过struct关键字定义自定义值类型
值类型的实例总是会进行实例复制
public struct Point
{
public int x;
public int y;
}
Point p1 = new Point();
p1.x = 9;
p1.y = 0;
Point p2 = p1;
p2.x= 7;
Console.WriteLine(p1.x);//9
Console.WriteLine(p1.y);//0
Console.WriteLine(p2.x);//7
Console.WriteLine(p2.y);//0
如图p1 和 p2会有各自的内存空间,互不影响
4.2、引用类型
引用类型比值类型复杂,它由两部分组成:对象和对象引用。引用类型变量或常量中的内容是一个含值对象的引用。以下示例将前面例子中的Point类型重新书写,令其成为一个类而非struct
给引用类型变量赋值只会复制引用,而不是对象实例。这允许不同变量指向同一个对象,而值类型通常不会出现这种情况。如果Point是一个类,那么若重复之前的示例,则对p1的操作就会影响到p2了:
public class Point
{
public int x;
public int y;
}
Point p1 = new Point();
p1.x = 9;
p1.y = 7;
Point p2 = p1;
p2.y = 0;
Console.WriteLine(p1.x);//9
Console.WriteLine(p1.y);//0
Console.WriteLine(p2.x);//9
Console.WriteLine(p2.y);//0
4.3、null
引用可以赋值为字面量null,表示它并不指向任何对象:
public class Point
{
public int x;
public int y;
}
Point p1 = new Point();
p1.x = 9;
p1.y = 7;
Point p2 = null;
值类型通常不能有null的取值
public struct Point
{
public int x;
public int y;
}
Point p1 = new Point();
p1.x = 9;
p1.y = 0;
//报错
/*
错误CS0037 无法将 null 转换为“Test2.Point”,因为后者是不可为 null 的值类型
*/
Point p2 = null;
C#中也有一种代表值类型为null的结构,称为可空(nullable)类型
4.4、存储开销
值类型实例占用的内存大小就是存储其字段所需的内存。
下面的例子需要占用8个字节的内存
public struct Point
{
public int x;//4byte
public int y;//4byte
}
CLR用整数倍字段的大小(最大到8字节)来分配内存地址。也就是说按照当前占用最大的字节的字段*字段的个数
例如:下面的Point 占用16个字节,根据long占用8个字节,然后两个字段,一共占用16个字节,第二个字段浪费7个字节的空间
这种行为可以通过指定StructLayout属性来重写
public struct Point
{
public long x;
public byte y;
}
引用类型要求为引用和对象单独分配存储空间。对象除占用了和字段一样的字节数外,还需要额外的管理空间开销。管理开销的精确值本质上属于.NET运行时实现的细节,但最少也需要8个字节来存储该对象的类型的键,以及一些诸如多线程锁的状态、是否可以被垃圾回收器固定等临时信息。根据.NET运行时是工作在32位抑或64位平台上,每一个对象的引用都需要额外的4到8个字节。
5、数值类型
5.1、数值字面量
整数类型字面量可以使用十进制或者十六进制表示。十六进制辅以0x前缀。例如:
int x = 127;
long y = 0x7F;
从C# 7开始,可以在数值字面量的任意位置加入下划线以方便阅读:
int million = 1_000_000;
C# 7还可以用0b前缀使用二进制表示数值:
var b = 0b1010_1011_1100_1101_1110_1111;
实数字面量可以用小数或指数表示,例如:
double d = 1.5;
double million = 1E06;
5.2、数值后缀
数值后缀显式定义了字面量的类型。后缀可以是下列小写或大写字母:
//数值后面不加F就会报错,因为浮点数默认都是double类型,不能隐式转为float类型
float i = 10.0F;
Console.WriteLine(i);
- double能表示所有可能的float值,因此float能隐式转换为double。反之则必须是显式转换。
- 所有整数类型可以隐式转换为浮点数类型。
- 将大的整数类型隐式转换为浮点类型会保留数值部分,但是有时会丢失精度。这是因为浮点类型虽然拥有比整数类型更大的数值,但是有时其精度却比整数类型要小。
- 所有的整数类型都能隐式转换为decimal类型。这是因为decimal可以表示所有可能的C#整数类型值。其他所有的数值类型转换为decimal或从decimal类型进行转换都必须是显式转换。
5.3、整数溢出
在进行整数的算术运算当中会出现整数溢出的情况,这种情况是悄悄发生的,超出位数就会丢弃
byte i = 255;
i++;//进行加一运算就会超出byte的最大值 1111 + 1 =1 0000 进儿变成0
Console.WriteLine(i);//0
整数运算溢出检查运算符
- checked运算符的作用是:在运行时当整数类型表达式或语句超过相应类型的算术限制时不再默默地溢出,而是抛出OverflowException。checked运算符可在有++、–、+、-(一元运算符和二元运算符)、*、/和整数类型间显式转换运算符的表达式中起作用。
- checked运算符对double和float类型没有作用(它们会溢出为特殊的“无限”值),对decimal类型也没有作用(这种类型总是会进行溢出检查)。
byte i = 255;
//Unhandled exception. System.OverflowException: Arithmetic operation resulted in an overflow.
//加上checked会抛出异常
checked { i++; };
Console.WriteLine(i);
5.4、特殊的float和double
不同于整数类型,浮点类型包含某些特定运算需要特殊对待的值。这些特殊的值是NaN(Not a Number,非数字)、+∞、-∞和-0。float和double类型包含表示NaN、+∞、-∞值的常量。其他的常量还有MaxValue、MinValue以及Epsilon。
5.5、double和decimal的区别
double类型在科学计算(例如计算空间坐标)时很有用。decimal类型在金融计算和计算那些“人为”的而非真实世界度量的结果时很有用。
下面是这两种类型的不同之处:
6、布尔类型
布尔类型,专门用来存储true或false,c#中用bool来定义,一个布尔值虽然只占一位,但是运行时却占一个字节,因为处理器处理的最小单位就是字节,为此避免数组占用大量内存,.NET Framework在System.Collections命令空间下提供了BitArray类,其中的每一个布尔值仅占用一位。
布尔类型一般都写在条件判断处,用 ! == != > < 来进行判断等等
7、字符串和字符
7.1、字符类型
字符类型用char关键字声明,表示一个Unicode字符并占用两个字节,char字面量应位于两个单引号之间。
转义字符指那些不能用字面量表示或解释的字符。转义字符由反斜线和一个表示特殊含义的字符组成。
例如
char newLine = '\n';
char backSlash = '\\';
7.2、char类型转换
从char类型到数值类型的隐式转换只在这个数值类型可以容纳无符号short类型时有效。对于其他的数值类型,则需要显式转换
char i = 'a';
short a = (short)i;
Console.WriteLine(a);
7.3、字符串类型
字符串类型用string关键字声明,值为不可变的Unicode字符序列。字符串字面量应位于两个双引号(")之间
string类型是引用类型而不是值类型。但是它的相等运算符却遵守值类型的语义。
string类型同样支持转义字符
例如
//此处加了制表符
string str = "hello\t world ";
Console.WriteLine(str);
//如果想要表示文件路径 则需要转义
string url = "D:\\test\\dir";
Console.WriteLine(url);//D:\test\dir
原意字符串
为了避免这种情况 c#引入了原意字符 @ 在字符串前加上@就会按照所写的显示出来
string str = @"hello\t world ";
Console.WriteLine(str);//hello\t world
string url = @"D:\\test\\dir";
Console.WriteLine(url);//D:\\test\\dir
注意:原意字符可以贯穿多行
原意字符串中需要用两个双引号来表示一个双引号字符
例如: string xml = @"<customer id="“123"”> ";
字符串插值
c#用$为前缀的字符串称为插值字符串。插值字符串可以在大括号内包含表达式。
int x = 10;
string str = $"hello world {x}";
Console.WriteLine(str);//hello world 10
大括号内可以是任意类型的合法C#表达式。C#会调用其ToString方法或等价方法将表达式转换为字符串。若要更改表达式的格式,可以使用冒号,并继以格式字符串
string s = $"255 in hex is {byte.MaxValue:X2}"; // X2 = 2-digit Hexadecimal
//"255 in hex is FF"
插值字符串只能是在单行内声明,除非使用原意字符串运算符。需要注意,$运算符必须在@运算符之前
8、数组
8.1、数组的声明
数组是固定数量的特定类型的变量集合(称为元素)。为了实现高效访问,数组中的元素总是存储在连续的内存块中。
//这就声明了一个char类型的数组
char[] ch= new char[5];
ch[0]='A';
Console.WriteLine(ch[0]);//A
8.2、数组的默认初始化值
创建数组时其元素总会用默认值初始化。类型的默认值是按位取0的内存表示的值。
例如
//int类型默认值为0
int[] arr = new int[10];
Console.WriteLine(arr[0]);//0
值类型和引用类型的区别
如果是值类型,会对类型中的字段进行默认赋值,如果是引用类型,则只会分配相应的空间。
例如
//结构体Point是值类型
public struct Point
{
public int x;
public int y;
}
Point[] p = new Point[10];
//对字段进行了初始化
Console.WriteLine(p[0].x);//0
//类 Point是引用类型
public class Point
{
public int x;
public int y;
}
Point[] p = new Point[10];
//仅仅创建了10个空引用
Console.WriteLine(p[0].x);//Unhandled exception. System.NullReferenceException: Object reference not set to an instance of an object.
简化数组初始化表达式
//简化了new关键字
char[] cha = {'a','b'};
//编译器会自动确认类型
var arr = new[]{1,100};
9、变量和参数
变量表示存储着可变值的存储位置。变量可以是局部变量、参数(value、ref或out),字段(实例或静态)以及数组元素。
9.1、栈和堆
栈和堆是存储变量和常量的地方。它们分别具有不同的生命周期语义。
栈
栈是存储局部变量和参数的内存块。逻辑上,栈会在函数进入和退出时增加或减少。
堆
堆是保存对象(例如引用类型的实例)的内存块。新创建的对象会分配在堆上并返回其引用。程序执行过程中,堆就被新创建的对象不断填充。.NET运行时的垃圾回收器会定期从堆上释放对象,因此应用程序不会内存不足。只要对象没有被“存活”的对象引用,它就可以被释放
值类型的实例(和对象的引用)就存储在变量声明的地方。如果声明为类的字段或数组的元素,则该实例会存储在堆上。
静态字段也会存储在堆上。与分配在堆上的对象(可以被垃圾回收)不同,这些变量一直存活直至应用程序域结束。
9.2、参数
方法可以有一连串的参数。在调用方法时必须为这些参数提供实际值。
9.3、按值传递参数
默认情况下,C#中的参数默认按值传递,这是最常用的方式。这意味着在将参数值传递给方法时将创建一份参数值的副本
static void print(int i) {
i = 100;
}
int x = 10;
print(x);
Console.WriteLine(x);//100
为 i 赋一个新的值并不会改变x的值,因为 i 和x分别存储在不同的内存位置中。
//此处会赋值一个引用
static void print(Point point) {
point.y = 100;
}
Point p = new Point();
print(p);
Console.WriteLine(p.y);//100
按值传递引用类型参数复制的是引用而非对象本身。
9.4、ref关键字
在C#中,若按引用传递参数则应使用ref参数修饰符。
static void print(ref int point) {
point = 100;
}
Point p = new Point();
int x = 10;
print(ref x);
Console.WriteLine(x);//100
9.5、out关键字
out参数和ref参数类似,但在以下几点上不同:
- 不需要在传入函数之前进行赋值。
- 必须在函数结束之前赋值。
static void goBack(ref int point,out int a,out int b)
{
point = 100;
a = point+1;
b = point -1;
}
int x = 10;
int a, b;
print(ref x);
goBack(ref x, out a, out b);
Console.WriteLine(a);//101
Console.WriteLine(b);//99
Console.WriteLine(x);//100
out变量及丢弃变量
从C# 7开始,允许在调用含有out参数的方法时直接声明变量。
static void goBack(ref int point,out int a,out int b)
{
point = 100;
a = point+1;
b = point -1;
}
int x = 10;
print(ref x);
//可以去掉声明a , b 的语句了
goBack(ref x, out a, out b);
Console.WriteLine(a);//101
Console.WriteLine(b);//99
Console.WriteLine(x);//100
当有多个out参数时,只关注我们想要的参数 就可以丢弃变量,丢弃符号是下划线(_)
static void goBack(ref int point,out int a,out int b)
{
point = 100;
a = point+1;
b = point -1;
}
int x = 10;
int a, b;
print(ref x);
//丢弃b变量
goBack(ref x, out a, out _);
Console.WriteLine(a);//101
//Console.WriteLine(b);//99
Console.WriteLine(x);//100
9.6、按引用传递参数
按引用传递参数是为现存变量的存储位置起了一个别名而不是创建一个新的存储位置。
class Test
{
static int x;
static void Main() {
print(out x);
}
static void print(out int y)
{
Console.WriteLine (x); //0
y = 1;
Console.WriteLine (x); // 1
}
}
9.7、params修饰符
params参数修饰符只能修饰方法中的最后一个参数,它能够使方法接受任意数量的指定类型参数。参数类型必须声明为数组。
static void print(ref int point,params int[] arr) {
point = 100;
for (int i = 0; i < arr.Length; i++)
{
Console.WriteLine(arr[i]);
}
}
int x = 10;
print(ref x);
print(ref x,1,2,3);//arr会把后面的123都接收到 也可以直接传入int[]
9.8、可选参数
从C# 4.0开始,方法、构造器和索引器中都可以声明可选参数。只要在参数声明中提供默认值,这个参数就是可选参数
static void chose(int i=10)
{
Console.WriteLine(i);
}
static void Main()
{
chose();//这里可以写参数也可以不写 不写的话就按默认的10 写的话就按写的
}
可选参数的默认值必须由常量表达式或者无参数的值类型构造器指定,可选参数不能标记为ref或者out。
若public方法对其他程序集可见,则在添加可选参数时双方均需重新编译,就像参数是必须提供的一样。
必填参数必须在可选参数方法声明和调用之前出现(params参数例外,它总是最后出现)。
9.9、命名参数
如果i用默认值,y用显式传递,那么此时就需要命名参数
static void chose(int i=10,int y=0)
{
Console.WriteLine(i+y);
}
static void Main()
{
chose(y:10);//只要传y的值用名字+:
}
9.10、引用局部变量(c#7)
C# 7添加了一个特性:即定义一个用于引用数组中某一个元素或对象中某一个字段的局部变量
int[] numbers = { 0, 1, 2, 3, 4 };
ref int numRef = ref numbers [2];
numRef *= 10;
Console.WriteLine (numRef); // 20
Console.WriteLine (numbers [2]); // 20
引用局部变量的目标只能是数组的元素、对象字段或者局部变量;而不能是属性。
9.11、引用返回值(c#7)
static string X = "Old Value";
static ref string GetX() => ref X;
static void Main()
{
ref string xRef = ref GetX();
xRef = "New Value";
Console.WriteLine (X);
}
9.12、var隐式类型局部变量
我们通常会在一步中完成变量的声明和初始化。如果编译器能够从初始化表达式中推断出变量的类型,就能够使用var关键字(C# 3.0引入)来代替类型声明
var i=11; //等价于 int i = 11;
i="hello";//报错 不是同一个类型
10、表达式和运算符
11、null运算符
C#提供了两个简化null处理的运算符:null合并运算符和null条件运算符。
null合并运算符
null合并运算符写作??。它的意思是“如果操作数不是null则结果为操作数,否则结果为一个默认的值。
string str = null;
str = str ?? "helow";//如果str是null就赋值为hello
Console.WriteLine(str);//hello
null条件运算符(c#6)
C# 6中引入了“? .”运算符,称为null条件运算符或者Elvis运算符(从Elvis表情符号而来)。该运算符可以像标准的“.”运算符那样访问成员以及调用方法。当运算符的左侧为null的时候,该表达式的运算结果也是null而不会抛出NullReferenceException异常。
跟js语法有点像
StringBuilder sb = null;
string str = sb?.ToString();
Console.WriteLine(str);//null
12、语句
12.1、if else 语句
int i=10;
if (i<5)
{
Console.WriteLine(i);
}else if(i>=10){
Console.WriteLine(i);
}
else
{
Console.WriteLine(i);
}
12.2、switch语句
switch语句可以根据变量可能的取值来转移程序的执行。switch语句可以拥有比嵌套if语句更加简洁的代码,因为switch语句仅仅需要一次表达式计算
普通switch
int i = 10;
switch (i)
{
case 0:
Console.WriteLine(0);
break;
case 1:
Console.WriteLine(1);
break ;
case 10:
goto case 1;//goto可以跳转
break;
default:
Console.WriteLine("没有匹配");
break;
}
每一个case子句结束时必须使用某种跳转指令显式指定下一个执行点(除非你的代码本身就是一个无限循环)。
这些跳转指令有:
- break(跳转到switch语句的最后)
- goto case x(跳转到另外一个case子句)
- goto default(跳转到default子句)
- 其他的跳转语句,例如return、throw、continue或者goto label
带有模式的switch语句(C# 7)
C# 7开始支持按类型switch
static void Main()
{
TellMeTheType (12);
TellMeTheType ("hello");
TellMeTheType (true);
}
static void TellMeTheType (object x) // object allows any type.
{
switch (x)
{
case int i:
Console.WriteLine ("It's an int! ");
Console.WriteLine ($"The square of {i} is {i * i}");
break;
case string s:
Console.WriteLine ("It's a string");
Console.WriteLine ($"The length of {s} is {s.Length}");
break;
default:
Console.WriteLine ("I don't know what x is");
break;
}
}
object类型允许其变量为任何类型。
还可以使用when关键字对case进行预测
switch (x)
{
case bool b when b == true: // 先判断类型 再用when判断是否为true
Console.WriteLine ("True! ");
break;
case bool b:
Console.WriteLine ("False! ");
break;
}
12.3、迭代语句
while语句
while循环在其bool表达式为true的情况下重复执行循环体中的代码。这个表达式在循环体执行之前进行检测。
int i = 0;
while(i<5){
i++;
}
Console.WriteLine(i);//5
do…while 语句
do-while循环在功能上不同于while循环的地方是它在语句块执行之后才检查表达式的值(保证语句块至少执行过一次)。
int i = 0;
do{
i++;
}while(i<5);
for循环
for循环就像一个有特殊子句的while循环。这些特殊子句用于初始化和迭代循环变量。for循环有以下三个子句
- 初始化子句:在循环之前执行,初始化一个或多个迭代变量。
- 条件子句:它是一个bool表达式,当其为true时,将执行循环体。
- 迭代子句:在每次语句块迭代之后执行,通常用于更新迭代变量
int i = 10;
for (int j = 0; j < 10; j++,i++,Console.WriteLine(i))
{
Console.WriteLine(j);
}
foreach循环
foreach语句遍历可枚举对象的每一个元素。大多数C#和.NET Framework中表示集合或元素列表的类型都是可枚举的。例如,数组和字符串都是可枚举的
foreach (char c in "beer")
Console.WriteLine (c);//b e e r
跳转语句
C#的跳转语句有break、continue、goto、return和throw。
break
break语句用于结束迭代或switch语句的执行
int i = 0;
while(i<5){
i++;
break;
}
Console.WriteLine(i);//1
continue
continue语句放弃循环体中其后的语句,继续下一轮迭代。
for (int i = 0; i < 10; i++)
{
if ((i % 2) == 0) // If i is even,
continue; // continue with next iteration
Console.Write (i + " ");//1 3 5 7 9
}
goto
goto语句将执行点转移到语句块中的指定标签处。
标签语句仅仅是代码块中的占位符,位于语句之前,用冒号后缀表示。
int i = 1;
startLoop:
if (i <= 5)
{
Console.Write (i + " ");
i++;
goto startLoop;
}
//1 2 3 4 5
return
return语句用于退出方法。如果这个方法有返回值,则必须返回方法指定返回类型的表达式。
static decimal AsPercentage (decimal d)
{
decimal p = d * 100m;
return p;
}
return语句能够出现在方法的任意位置(除finally块中)。
throw
throw语句抛出异常来表示有错误发生
if (w == null)
throw new ArgumentNullException (...);
其他语句
using语句用一种优雅的语法在finally块中调用实现了IDisposable接口对象的Dispose方法。
C#重载了using关键字,使它在不同上下文中有不同的含义。特别注意using指令和using语句是不同的。
lock语句是调用Mintor类型的Enter和Exit方法的简化写法。
13、命名空间
命名空间是一系列类型名称的领域。通常情况下,类型组织在分层的命名空间里,既避免了命名冲突又更容易查找。
命名空间是独立于程序集的。程序集是像.exe或者.dll一样的部署单元(参见第18章)。命名空间并不影响成员的public、internal、private的可见性。
namespace关键字为其中的类型定义了命名空间。
namespace Test{
class Class1{}
class Class2{}
}
命名空间中的“.”表明了嵌套命名空间的层次结构。
namespace Test.Demo{
class Class1{}
class Class2{}
}
//两者等价
namespace Test{
namespace Demo{
class Class1{}
class Class2{}
}
}
类型可以用完全限定名称(fully qualified name),也就是包含从外到内的所有命名空间的名称,来指定。例如,上述例子中,可以使用Test.Demo.Class1来指代Class1。
如果类型没有在任何命名空间中定义,则它存在于全局命名空间(global namespace)中。全局命名空间也包含了顶级命名空间,就像前面例子中的Test命名空间。
13.1、using指令
using指令用于导入命名空间。这是避免使用完全限定名称来指代某种类型的快捷方法。
using System;
Console.WriteLine("hello");//导入System命名空间就可以用Console了
13.2、using static 指令(c#6)
从C# 6开始,我们不仅可以导入命名空间还可以使用using static指令导入特定的类型。这样就可以类型接使用类型静态成员而不需要指定类型的名称了。
using static System.Console;
class Test
{
static void Main() {
WriteLine ("Hello"); //直接写方法就可调用
}
}
using static指令将类型的可访问的静态成员,包括字段、属性以及嵌套类型(全部导入进来。同时,该指令也支持导入枚举类型的成员。
13.3、命名空间的规则
名称范围
外层命名空间中声明的名称能够直接在内层命名空间中使用。
使用统一命名空间分层结构中不同分支的类型需要使用部分限定名称。
namespace Project {
class Class1 { }
namespace Project2
{
class Class3:Class1 { }//外层的不用加限定名
}
namespace Project1
{
class Class2 :Project2.Class3//Class2和Class3是同级 需要限定名
{ }
}
}
名称隐藏
如果相同类型名称同时出现在内层和外层命名空间中,则内层类型优先。如果要使用外层命名空间中的类型,必须使用它的完全限定名称。
namespace Project {
class Class1 { }
namespace Project1
{
class Class1 { }
class Test {
Class1 c1;
Project.Class1 c2;
}
}
}
重复的命名空间
只要命名空间内的类型名称不冲突就可以重复声明同一个命名空间。
namespace Outer.Middle.Inner
{
class Class1 {}
}
namespace Outer.Middle.Inner
{
class Class2 {}
}
嵌套的using指令
我们能够在命名空间中嵌套使用using指令,这样可以控制using指令在命名空间声明中的作用范围。
namespace N1
{
class Class1 {}
}
namespace N2
{
using N1;
class Class2 : Class1 {}//Class1在一个命名空间中可见,但是在另一个命名空间中不可见 用using
}
namespace N2
{
class Class3 : Class1 {} // Compile-time error
}
类型和命名空间别名
导入命名空间可能导致类型名称的冲突,因此可以只导入需要的特定类型而不是整个命名空间,并给它们创建别名。
namespace Project {
class Class1
{
public void print()
{
Console.WriteLine("我是Class1的print方法");
}
}
namespace Project2
{
using D = Project;//整个Project的命名空间起别名
class Class3 : D.Project1.Class3{ }
class Class4 { }
}
namespace Project1
{
using P = Project.Project2.Class3;//给某个类型起别名
class Class2 :P{ }
class Class3 { }
}
}
13.4、高级命名空间特性
外部别名
命名空间别名限定符
内层命名空间中的名称隐藏外层命名空间中的名称。但是,有时即使使用类型的完全限定名也无法解决冲突。
namespace N
{
class A
{
public class B {}
static void Main() { new A.B(); } // 他会实例化本部的B
}
}
namespace A
{
class B {}
}
要解决这样的冲突,可以使用如下的方式限定命名空间中的名称:
全局命名空间,即所有命名空间的根命名空间(由上下文关键字global指定)
一系列的外部别名“::”用于限定命名空间别名。下面的例子中,我们使用了全局命名空间(这通常出现在自动生成的代码中,以避免名称冲突)
namespace N
{
class A
{
static void Main()
{
System.Console.WriteLine (new A.B());
System.Console.WriteLine (new global::A.B());
}
public class B {}
}
}
namespace A
{
class B {}
}