C#本质论笔记 第1章 C#概述

alt

名词解释

  • CLI Common Language Infrastructure 公共语言基础结构
  • CIL Common Intermediate Language 公共中间语言
  • BCL Base Class Library 基础类库
  • overload 重载:指的是同一个类中有两个或多个名字相同但是参数不同的方法,(注:返回值不能区别函数是否重载),重载没有关键字
  • override 重写 过载:指子类对父类中虚函数或抽象函数的“覆盖”(这也就是有些书将过载翻译为覆盖的原因),但是这种“覆盖”和用new关键字来覆盖是有区别的。
  • new 覆盖:指的是不同类中(基类或派生类)有两个或多个返回类型、方法名、参数都相同,但是方法体不同的方法。但是这种覆盖是一种表面上的覆盖,所以也叫隐藏,被覆盖的父类方法是可以调用得到的。

HelloWorld

本书中的例子

1
2
3
4
5
6
7
class HelloWorld
{
static void Main()
{
System.Console.WriteLine("Hello World!");
}
}

其他例子

1
2
3
4
5
6
7
8
9
using System;

class Hello
{
static void Main()
{
Console.WriteLine("Hello World!");
}
}

VS IDE自动生成例子

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;

namespace HelloWorld
{
class Program
{
static void Main(string[] args)
{
Console.WriteLine("Hello World!");
}
}
}

语法基础

标识符大小写风格

Pacal风格 (PascalCase/Pascal case)

每个单词首字母大写,例如:ComponentModel, Configuration, and HttpFileCollection. 注意在 HttpFileCollection中,由于首字母缩写词HTTP的长度超过两个字母,所以仅首字母大写。

camel风格 (camelCase/camel Case)

除了第一个字母小写,其他约定与Pascal大小写放个一样,例如:quotient, firstName, httpFileCollection, ioStream, and theDreadPirateRoberts.

形参与实参

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
namespace ConsoleApplication
{
class Program
{
static void Main(string[] args)
{
int a = 5;
abc(a); //a就是实参
}

static int abc(int c) //c就是形参
{
return 5;
}
}
}

Main

  • Main 方法是 C# 控制台应用程序或窗口应用程序的入口点。 (库和服务不要求将 Main 方法作为入口点。) 应用程序启动时,Main 方法是第一个调用的方法。
  • C# 程序中只能有一个入口点。 如果您有多个类都包含 Main 方法,则必须使用 /main 编译器选项编译您的程序,以指定用作入口点的 Main 方法。

不带参数的Main

1
2
3
4
5
6
7
8
class Hello
{
static void Main()
{
System.Console.WriteLine("Hello World!");
System.Console.ReadKey();
}
}

带参数的Main

1
2
3
4
5
6
7
8
class Hello
{
static void Main(string[] args)
{
System.Console.WriteLine("Hello World!");
System.Console.ReadKey();
}
}

Main 概述

  • Main 方法是 .exe 程序的入口点,程序控制流在该处开始和结束。
  • Main 在类或结构内声明。 Main 必须是静态的,且不应该是 公用的。 (在前面的示例中,它接受默认访问级别 private。)但不要求封闭类或结构是静态的。
  • Main 的返回类型有两种:void 或 int。
  • 所声明的 Main 方法可以具有包含命令行实参的 string[] 形参,也可以不具有这样的形参。 使用 Visual Studio 创建 Windows 窗体应用程序时,可以手动添加形参,也可以使用 Environment 类获取命令行实参。 形参读取为从零开始编制索引的命令行实参。 与 C 和 C++ 不同,不会将程序名称视为第一个命令行实参。

Main 例子

1
2
3
4
5
6
7
8
9
10
11
12
13
class Hello
{
static void Main(string[] args)
{
System.Console.WriteLine("Hello World!");
System.Console.WriteLine(args.Length);
System.Console.WriteLine(System.Environment.CommandLine);
System.Console.WriteLine(System.Environment.CurrentDirectory);
System.Console.WriteLine(System.Environment.MachineName);
System.Console.WriteLine(System.Environment.UserName);
System.Console.ReadKey();
}
}

编译上面代码,在控制台输入 ==HelloWorld.exe== 输出结果

Hello World!
0
helloworld.exe
D:\WaProj\Essential C#5.0, 4th Edition\第一章
DESKTOP-D10TF3C
ihome

编译上面代码,在控制台输入 ==HelloWorld== 输出结果

Hello World!
0
helloworld
D:\WaProj\Essential C#5.0, 4th Edition\第一章
DESKTOP-D10TF3C
ihome

单行多行语句

一行包含多条语句

1
System.Console.WriteLine("1");System.Console.WriteLine("2");

输出结果

1
2

一条语句跨越多行

1
2
System.Console.WriteLine(
"Hello World!");

输出结果

Hello World!

错误示例

1
2
System.Console.WriteLine("Hello 
World!");

错误提示

HelloWorld.cs(5,31): error CS1010: 常量中有换行符
HelloWorld.cs(6,12): error CS1010: 常量中有换行符

缩进和空白

例子1

1
2
3
4
5
6
7
8
class Hello
{
static void Main()
{
System.Console.WriteLine("Hello World!");
System.Console.ReadKey();
}
}

例子2

1
class Hello{static void Main(){System.Console.WriteLine("Hello World!");System.Console.ReadKey();}}

上面两个例子输出结果相同,对编译器来说无差别。

变量声明与赋值

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
class Hello
{
static void Main()
{
string valerie;
//定义变量并赋值
string max = "Have fun storming the castle!";

valerie = "Think it will work?";

System.Console.WriteLine(max);
System.Console.WriteLine(valerie);

//重新赋值
max = "It would take a miracle.";
System.Console.WriteLine(max);

string boys,girls;
//多赋值操作
boys = girls = "We Are Young.";
System.Console.WriteLine(boys);
System.Console.WriteLine(girls);

System.Console.ReadKey();
}
}

输出结果

Have fun storming the castle!
Think it will work?
It would take a miracle.
We Are Young.
We Are Young.

高级主题:字符串不可变

所有string类型数据,都是不可变的(或者说不可修改的),例如:不可能将字符串“We Are Yong.”修改为“We Are Old.”。也就是说,不能修改变量最初引用的数据,只能重新赋值,让它指向内存中的新位置。

控制台输入输出

从控制台获取输入

使用System.Console.ReadLine()

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
class Hello
{
static void Main()
{
string firstName;
string lastName;

System.Console.Write("Enter your first name: ");
firstName = System.Console.ReadLine();

System.Console.Write("Enter your last name: ");
lastName = System.Console.ReadLine();

System.Console.WriteLine("Hello " + firstName + " " + lastName);
System.Console.ReadKey();
}
}

输出结果

Enter your first name: Jon
Enter your last name: Snow
Hello Jon Snow

高级主题:System.Console.Read()

System.Console.Read()方法返回的是与读取的字符值对应的证书,如果没有更多的字符可用,就返回-1。为了获取实际字符,需要先将证书转型为字符,代码如下:

代码示例 1

1
2
3
4
5
6
7
8
9
10
11
12
13
14
class Hello
{
static void Main()
{
int readValue;
char character;

readValue = System.Console.Read();
character = (char) readValue;
System.Console.WriteLine(character);

System.Console.ReadKey();
}
}

System.Console.Read() 从标准输入流读取下一个字符。

System.Console.ReadKey() 获取用户按下的下一个字符或功能键。 按下的键显示在控制台窗口中。

按回车键之前,System.Console.Read()方法不会返回输入,即使用户输入了多个字符。

输入 123,按回车,再输入 a,输出结果

123
1
a

代码示例 2

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
class Hello
{
static void Main()
{
int readValueFirst,readValueSecond,readValueThird;
char character;

readValueFirst = System.Console.Read();
character = (char) readValueFirst;
System.Console.WriteLine(character);
readValueSecond = System.Console.Read();
character = (char) readValueSecond;
System.Console.WriteLine(character);
readValueThird = System.Console.Read();
character = (char) readValueThird;
System.Console.WriteLine(character);

System.Console.ReadKey();
}
}

System.Console.Read() 从标准输入流读取下一个字符。

System.Console.ReadKey() 获取用户按下的下一个字符或功能键。 按下的键显示在控制台窗口中。

按回车键之前,System.Console.Read()方法不会返回输入,即使用户输入了多个字符。

输入 123,按回车,再输入 a,输出结果

123
1
2
3
a

上面两个示例,输入信息相同,由于代码不同因而输出结果不同。

输出到控制台

System.Console.Write() 输出后不添加换行符(当前行终止符)。
System.Console.WriteLine() 将参数内容(后跟当前行终止符)写入标准输出流,输出后,光标切换到下一行。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
class Hello
{
static void Main()
{
string firstName;
string lastName;

System.Console.Write("Enter your first name: ");
firstName = System.Console.ReadLine();

System.Console.Write("Enter your last name: ");
lastName = System.Console.ReadLine();

System.Console.WriteLine(
"Hello {0} {1}.", firstName, lastName);
System.Console.ReadKey();
}
}

示例代码中”Hello {0} {1}.”,标识了两个索引占位符,用于在字符串中插入数据。

输出结果

Enter your first name: Jon
Enter your last name: Snow
Hello Jon Snow.

交换索引占位符和对应变量

1
2
System.Console.WriteLine(
"Hello {1}, {0}.", firstName, lastName);

输出结果

Enter your first name: Jon
Enter your last name: Snow
Hello Snow, Jon.

代码注释

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
class Hello
{
static void Main()
{
string firstName; //存储名字的变量【单行注释】
string lastName; //存储姓氏的变量【单行注释】

System.Console.Write/*不换行输出【语句内部带分隔符注释】*/(
"Enter your first name: ");
firstName = System.Console.ReadLine();

System.Console.Write/*不换行输出【语句内部带分隔符注释】*/(
"Enter your last name: ");
lastName = System.Console.ReadLine();

/*
使用复合格式化在控制台显示问候语。
*/
System.Console.WriteLine(
"Hello {1}, {0}.", firstName, lastName);

System.Console.ReadKey();

//这是程序列表的结尾
}
}

目前观点

  • 不要使用注释,除非代码本身“一言难尽”。
  • 尽量编写清晰的代码,而不是通过注释澄清复杂算法。

CIL和ILDAASM (公共中间语言和IL反汇编)

C#编译器将C#代码转换成CIL代码而不是机器码。对于一个程序集(DLL文件或可执行文件),可以使用CIL反汇编程序将其析构成对应的CIL表示,从而查看其CIL代码。微软的反汇编程序文件:ILDASM(IL Disassembler),可以对程序或者类库执行反汇编,显示由C#编译器生成的CIL代码。

ildasm.exe存在与C:\Program Files (x86)\Microsoft SDKs\Windows\v10.0A\bin\NETFX 4.6.1 Tools类似位置

例子

ildasm /text hello.exe

输出结果

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
D:\WaProj\Essential C#5.0, 4th Edition\第一章>ildasm /text hello.exe

// Microsoft (R) .NET Framework IL Disassembler. Version 4.6.1055.0




// Metadata version: v4.0.30319
.assembly extern mscorlib
{
.publickeytoken = (B7 7A 5C 56 19 34 E0 89 ) // .z\V.4..
.ver 4:0:0:0
}
.assembly Hello
{
.custom instance void [mscorlib]System.Runtime.CompilerServices.CompilationRelaxationsAttribute::.ctor(int32) = ( 01 00 08 00 00 00 00 00 )
.custom instance void [mscorlib]System.Runtime.CompilerServices.RuntimeCompatibilityAttribute::.ctor() = ( 01 00 01 00 54 02 16 57 72 61 70 4E 6F 6E 45 78 // ....T..WrapNonEx
63 65 70 74 69 6F 6E 54 68 72 6F 77 73 01 ) // ceptionThrows.
.hash algorithm 0x00008004
.ver 0:0:0:0
}
.module Hello.exe
// MVID: {BDAC1292-8393-4BEB-9AD7-40DC171B0BF9}
.imagebase 0x00400000
.file alignment 0x00000200
.stackreserve 0x00100000
.subsystem 0x0003 // WINDOWS_CUI
.corflags 0x00000001 // ILONLY
// Image base: 0x02CF0000


// =============== CLASS MEMBERS DECLARATION ===================

.class private auto ansi beforefieldinit Hello
extends [mscorlib]System.Object
{
.method private hidebysig static void Main(string[] args) cil managed
{
.entrypoint
// 代码大小 19 (0x13)
.maxstack 8
IL_0000: nop
IL_0001: ldstr "Hello World!"
IL_0006: call void [mscorlib]System.Console::WriteLine(string)
IL_000b: nop
IL_000c: call valuetype [mscorlib]System.ConsoleKeyInfo [mscorlib]System.Console::ReadKey()
IL_0011: pop
IL_0012: ret
} // end of method Hello::Main

.method public hidebysig specialname rtspecialname
instance void .ctor() cil managed
{
// 代码大小 7 (0x7)
.maxstack 8
IL_0000: ldarg.0
IL_0001: call instance void [mscorlib]System.Object::.ctor()
IL_0006: ret
} // end of method Hello::.ctor

} // end of class Hello


// =============================================================

// *********** 反汇编完成 ***********************

/text 选项制定输出到命令控制台,而不是在ildasm的图形界面程序中显示。

常见.NET反汇编工具

为了减少程序被别人轻松反编译,可以考虑使用混淆器(obfuscator)产品。这些混淆器会打开IL代码,将代码加密成一种功能不变但更难于理解的形式。


结尾