-
Notifications
You must be signed in to change notification settings - Fork 0
/
Copy pathsearch.xml
182 lines (182 loc) · 342 KB
/
search.xml
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
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
<?xml version="1.0" encoding="utf-8"?>
<search>
<entry>
<title><![CDATA[Python 正则表达式]]></title>
<url>%2F2018%2F08%2F01%2FPython-%E6%AD%A3%E5%88%99%E8%A1%A8%E8%BE%BE%E5%BC%8F%2F</url>
<content type="text"><![CDATA[特殊符号和字符最简单正则表达式 不使用任何特殊符号,值匹配字符串本身 正则表达式 匹配的字符串 foo foo Python Python 使用择一匹配符号匹配多个正则表达式模式 正则表达式 匹配的字符串 at|home at、home bat|bet|bit bat、bet、bit 例子 1234567>>> import re>>> pattern = re.compile(r'bat|bit')>>> r1 = re.match(pattern,'bat bet bit')>>> r1<re.Match object; span=(0, 3), match='bat'>>>> print(r1.group())bat 匹配任意单个字符 正则表达式模式 匹配的字符串 f.o 匹配在字母“f”和“o”之间的任意一个字符;例如fao、f9o、f#o等 .. 任意两个字符 .end 匹配在字符串end之前的任意一个字 从字符串起始或者结尾或者单词边界匹配 正则表达式模式 匹配的字符串 ^From 任何以From作为起始的字符串 /bin/tcsh$ 任何以/bin/tcsh作为结尾的字符串 ^Subject:his 任何由单独的字符串Subject:hi构成的字符串 the 任何包含the的字符串 \bthe 任何以the开始的字符串 \bthe\b 仅仅匹配单词the \Bthe 任何包含但并不以the作为起始的字符串 匹配限定范围的任意字符 用[]来匹配方括号对中包含的任何字符 正则表达式模式 匹配的字符串 b[aeiu]t bat、bet、bit、but [cr][23][dp][o2] 一个包含四个字符的字符串,第一个字符是“c”或“r”,然后是“2”或“3”,后面是“d”或“p”,最后要么是“o”要么是“2”。例如,c2do、r3p2、r2a2、c3po等 限定范围和否定 方括号中两个符号中间用连字符(-)连接,用于指定一个字符的范围;例如,A-Z、a-z或者0-9分别用于表示大写字母、小写字母和数值数字。 另外,如果脱字符(A)紧跟在左方括号后面,这个符号就表示不匹配给定字符集中的任何一个字符。 正则表达式模式 匹配的字符串 Z.[0-9] 字母“z”后面跟着任何一个字符,然后跟着一个数字 [r-u][env-y][us] 字母“r”、“s”、“t”或者“u”后面跟着“e”、“n”、“v”、“w”、“x”或者“y”,然后跟着“u”或者“s” [^aeiou] 一个非元音字符(练习:为什么我们说“非元音”而不是“辅音”?) [^\t\n] 不匹配制表符或者\n [“-a] 在一个ASCII系统中,所有字符都位于“”和“a”之间,即34-97之间 使用闭包操作符实现存在性和频数匹配 本节介绍最常用的正则表达式符号,即特殊符号*、+和?,所有这些都可以用于匹配一个、多个或者没有出现的字符串模式。 星号或者星号操作符(*)将匹配其左边的正则表达式出现零次或者多次的情况(在计算机编程语言和编译原理中,该操作称为Kleene闭包)。 加号(+)操作符将匹配一次或者多次出现的正则表达式(也叫做正闭包操作符)。 问号(?)操作符将匹配零次或者一次出现的正则表达式。 正则表达式模式 匹配的字符串 [dn]ot? 字母“d”或者“n”,后面跟着一个“o”,然后是最多一个“t”,例如,do、no、dot、not 0?[1-9] 任何数值数字,它可能前置一个“0”,例如,匹配一系列数(表示从1~9月的数值),不管是一个还是两个数字 [0-9]{15,16} 匹配15或者16个数字(例如信用卡号码) </?[^>]+> 匹配全部有效的(和无效的)HTML标签 [KQRBNP][a-h][1-8]-[a-h][1-8] 在“长代数”标记法中,表示国际象棋合法的棋盘移动(仅移动,不包括吃子和将军)。即“K”、“Q”、“R”、“B”、“N”或“P”等字母后面加上“al”~“h8”之间的棋盘坐标。前面的坐标表示从哪里开始走棋,后面的坐标代表走到哪个位置(棋格)上 表示字符集的特殊字符 有一些特殊字符能够表示字符集。 与使用“0-9”这个范围表示十进制数相比,可以简单地使用\d表示匹配任何十进制数字。 另一个特殊字符(\w)能够用于表示全部字母数字的字符集,相当于[A-Za-z0-9]的缩写形式, \s可以用来表示空格字符。 这些特殊字符的大写版本表示不匹配;例如,\D表示任何非十进制数(与[0-9]相同),等等。 正则表达式模式 匹配的字符串 \w+-\d+ 一个由字母数字组成的字符串和一串由一个连字符分隔的数字 [A-Za-z]\w* 第一个字符是字母;其余字符(如果存在)可以是字母或者数字(几乎等价于Python中的有效标识符[参见练习]) \d{3}-\d{3}-\d{4} 美国电话号码的格式,前面是区号前缀,例如800-555-1212 \w+@\w+.com 以[email protected]格式表示的简单电子邮件地址 使用圆括号指定分组 顾名思义,就是对正则表达式中的部分内容用()圈起来,然后再用运用正则表达式方法。对应 re 模块函数 group() 和 groups()。 正则表达式模式 匹配的字符串 \d+(\.\d*)? 表示简单浮点数的字符串;也就是说,任何十进制数字,后面可以接一个小数点和零个或者多个十进制数字,例如“0.004”、“2”、“75.”等 (Mr?s?\.)?[A-Z][a-z]*[A-Za-z-]+ 名字和姓氏,以及对名字的限制(如果有,首字母必须大写,后续字母小写),全名前可以有可选的“Mr.”、“Mrs.”、“Ms.”或者“M.”作为称谓,以及灵活可选的姓氏,可以有多个单词、横线以及大写字母 扩展表示法 正则表达式模式 匹配的字符串 (?:\w+L.)* 以句点作为结尾的字符串,例如“google.”、“twitter.”、“facebook.”,但是这些配不会保存下来供后续的使用和数据检索 (?#comment) 此处并不做匹配,只是作为注释 (?=.com) 如果一个字符串后面跟着“.com”才做匹配操作,并不使用任何目标字符串 (?!.net) 如果一个字符串后面不是跟着“.net”才做匹配操作 (?<=800-) 如果字符串之前为“800-”才做匹配,假定为电话号码,同样,并不使用任何输入字符串 (?<!192\.168.) 如果一个字符串之前不是“192.168.”才做匹配操作,假定用于过滤掉一组C类IP地址 (?(1)y|x) 如果一个匹配组1(1)存在,就与y匹配;否则,就与x匹配 flag参数flag是匹配模式,取值可以使用按位或运算符’|’表示同时生效,比如re.I | re.M。 re.I(全拼:IGNORECASE): 忽略大小写(括号内是完整写法,下同) re.M(全拼:MULTILINE): 多行模式,改变’^’和’$’的行为(参见上图) re.S(全拼:DOTALL): 点任意匹配模式,改变’.’的行为 re.L(全拼:LOCALE): 使预定字符类 \w \W \b \B \s \S 取决于当前区域设定 re.U(全拼:UNICODE): 使预定字符类 \w \W \b \B \s \S \d \D 取决于unicode定义的字符属性 re.X(全拼:VERBOSE): 详细模式。这个模式下正则表达式可以是多行,忽略空白字符,并可以加入注释。 正则表达式和 Python 语言match()方法从字符串开头进行匹配 match()函数试图从字符串的起始部分对模式进行匹配。如果匹配成功,就返回一个匹配对象;如果匹配失败,就返回None,匹配对象的 group() 方法能够用于显示那个成功的匹配。 1234567891011121314151617181920>>> re.match('foo','foo').group()'foo'>>> re.match('fo.','foo').group()'foo'>>> re.match('fo.','food on the table').group()'foo'>>> re.match('fo.','ffood on the table').group()Traceback (most recent call last): File "<stdin>", line 1, in <module>AttributeError: 'NoneType' object has no attribute 'group'>>> re.match('.fo.','ffood on the table').group()'ffoo'>>> re.match('\w*fo.','ffood on the table').group()'ffoo'>>> re.match('\w*d.','ffood on the table').group()'ffood '>>> re.match('\w*d','ffood on the table').group()'ffood'>>> re.match('\w* on','ffood on the table').group()'ffood on' match() 只匹配以模式字符为起始的字符串,限制比较大。 search()在字符串中查找模式匹配 search() 的工作方式与match() 完全一致,不同之处在于search() 会用它的字符串参数,在任意位置对给定正则表达式模式搜索第一次出现的匹配情况。如果搜索到成功的匹配,就会返回一个匹配对象;否则,返回None。 12345678910>>> re.match('on','ffood on the table').group() # match() 匹配失败Traceback (most recent call last): File "<stdin>", line 1, in <module>AttributeError: 'NoneType' object has no attribute 'group'>>> re.search('on','ffood on the table').group() # search() 匹配成功'on'>>> re.search('on t','ffood on the table').group()'on t'>>> re.search('on t..','ffood on the table').group()'on the' 匹配多个字符串 使用择一匹配(|)符号 123456789101112131415>>> bt = 'bat|bet|bit' # 正则表达式模式:bat、bet、bit>>> m = re.match(bt,'bat') # 'bat' 匹配 成功>>> if m is not None:m.group()...'bat'>>> m = re.match(bt,'bwt') # 'bwt' 匹配 失败>>> if m is not None:m.group()...>>> m = re.match(bt,'He bit me!') # 无法匹配>>> if m is not None:m.group()...>>> m = re.search(bt,'He bit me!') # 通过搜索,找到 'bit' ,匹配成功>>> if m is not None:m.group()...'bit' 匹配任何单个字符 用点号 . 匹配任意单个字符(包括数字)除了 \n 或者空字符串。 12345678910111213141516>>> import re>>> anyend = '.end'>>> m = re.match(anyend,'bend') # 点号匹配 'b'>>> if m is not None: m.group()...'bend'>>> m = re.match(anyend,'end') # 匹配失败 空字符>>> if m is not None: m.group()...>>> m = re.match(anyend,'\nend') # 匹配失败 \n>>> if m is not None: m.group()...>>> m = re.search(anyend,'The end.') # 搜索方式匹配到空格字符 ' '>>> if m is not None: m.group()...' end' 匹配字符串中的小数点 在正则表达式中,小数点 . 代表匹配任何单个非空非\n字符;那如何匹配一个真正的小数点呢?Python中采用\.,对小数点进行转义的方式。 1234567891011121314>>> patt314 = '3.14'>>> pi_patt = '3\.14'>>> m = re.match(pi_patt, '3.14')>>> if m is not None: m.group()...'3.14'>>> m = re.match(patt314, '3.14')>>> if m is not None: m.group()...'3.14'>>> m = re.match(patt314, '3214')>>> if m is not None: m.group()...'3214' group() 和 groups() group()通常用于以普通方式显示所有的匹配部分,但也能用于获取各个匹配的子组。可以使用groups())方法来获取一个包含所有匹配子字符串的元组。 123456789>>> m=re.match('(\w\w\w)-(\d\d\d)','abc-123')>>> m.group() # 完整匹配'abc-123'>>> m.group(1) # 子组1'abc'>>> m.group(2) # 子组2'123'>>> m.groups() # 所有子组('abc', '123') 如下为一个简单的示例,该示例展示了不同的分组排列,这将使整个事情变得更加清晰。 12345678910111213141516171819202122232425262728293031323334#-------------------------------------->>> m=re.match('ab','ab') # 没有子组>>> m.group() # 完整匹配'ab'>>> m.groups() # 所有子组()#-------------------------------------->>> m=re.match('(ab)','ab') # 一个子组>>> m.group() # 完整匹配'ab'>>> m.group(1) # 子组1'ab'>>> m.groups() # 所有子组('ab',)#-------------------------------------->>> m=re.match('(a)(b)','ab') # 两个子组>>> m.group() # 完整匹配'ab'>>> m.group(1) # 子组1'a'>>> m.group(2) # 子组2'b'>>> m.groups() # 所有子组('a', 'b')#-------------------------------------->>> m=re.match('(a(b))','ab') # 两个子组>>> m.group() # 完整匹配'ab'>>> m.group(1) # 子组1'ab'>>> m.group(2) # 子组2'b'>>> m.groups() # 所有子组('ab', 'b') 匹配字符串的起始和结尾以及单词边界1234567891011121314151617181920212223>>> import re>>> m=re.search('^The','The end.') # 起始匹配>>> if m is not None: m.group()...'The'>>> m=re.search(r'dog$','bitethe dog') # 结尾匹配>>> if m is not None: m.group()...'dog'>>> m=re.search('^The','end. The') # 非起始,不匹配 注意大小写匹配>>> if m is not None: m.group()...>>> m=re.search(r'\bthe','bite the dog') # 在边界,匹配 注意大小写匹配>>> if m is not None: m.group()...'the'>>> m=re.search(r'\bthe','bitethe dog') # 非边界 不匹配>>> if m is not None: m.group()...>>> m=re.search(r'\Bthe','bitethe dog') # 非边界 匹配非边界>>> if m is not None: m.group()...'the' 使用findall()和finditer()查找每一次出现的位置 findall()查询字符串中某个正则表达式模式全部的非重复出现情况。这与search()在执行字符串搜索时类似,但与match()和search()的不同之处在于,findall()总是返回一个列表。如果findall()没有找到匹配的部分,就返回一个空列表,但如果匹配成功,列表将包含所有成功的匹配部分(从左向右按出现顺序排列)。 123456>>> re.findall('car','car')['car']>>> re.findall('car','scary')['car']>>> re.findall('car','scary the barcardi to the car')['car', 'car', 'car'] 附录Python中正则表达式的一些匹配规则(CSDN) Python中正则表达式的一些匹配规则(文字) 语法 解释 表达式 成功匹配对象 一般字符 匹配自身相对应的字符 abc abc | 表示左右表达式任意满足一种即可 abc|cba abc或cba . 匹配除换行符(\n)以外的任意单个字符 a.c abc、azc \ 转义字符,可以改变原字符的意思 a.c a.c \d 匹配任意1位数字:0~9 \dabc 1abc \D 匹配数字以外的字符 \Dabc aabc \w 表示全部字母数字的字符集,相当于[A-Za-z0-9] \w\w\w oX2 \W 匹配非单词字符 a\Wc a c \s 表示空格字符(\t,\n,\r,\f,\v) a\sc a c \S 匹配非空格字符 \S\Sc 1bc [] 字符集,对应位置上可以是字符集里的任意字符 a[def]c aec [^] 对字符集当中的内容进行取反 a[^def]c a2c [a-z] 指定一个范围字符集 a[A-Z]c aBc * 匹配前面的字符或者子表达式0次或多次 a*b aaab或b + 匹配前一个字符或子表达式一次或多次 a+b aaab或ab ? 匹配前一个字符或子表达式0次或1次重复 a?b ab或b {m} 匹配前一个字符或子表达式 a{3}b aaab {m,n} 匹配前一个字符或子表达式至少m次至n次(如省略m,则匹配0~n次;如省略n,则匹配m至无限次。) a{3,5}b和a{3,} aaaab和aaaaaab ^ 匹配字符串的开始,多行内容时匹配每一行的开始 ^abc abc $ 匹配字符串的结尾,多行内容时匹配每一行的结尾 abc& abc \A 匹配字符串开始位置,忽略多行模式 \Aabc abc \Z 匹配字符串结束位置,忽略多行模式 abc\Z abc \b 匹配一个单词的边界,模式必须位于单词的起始部分,不管单词位于行首或者位于字符串中间 hello \bworld hello world \B 匹配出现在一个单词中间的模式,即与 \b 相反 he\Bllo hello (…) 将被括起来的表达式作为一个分组,可以使用索引单独取出 (abc)d abcd (?P<name>…) 为该分组起一个名字,可以用索引或名字去除该分组 (?P<id>abc)d abcd \number 引用索引为number中的内容 (abc)d\1 abcdabc (?P=name) 引用该name分组中的内容 (?P<id>abc)d(?P=id) abcdabc (?:…) 分组的不捕获模式,计算索引时会跳过这个分组 (?:a)b(c)d\1 abcdc (?iLmsux) 分组中可以设置模式,iLmsux之中的每个字符代表一个模式,单独介绍 (?i)abc Abc (?#…) 注释,#后面的内容会被忽略 ab(?#注释)123 ab123 (?=…) 顺序肯定环视,表示所在位置右侧能够匹配括号内正则 a(?=\d) a1最后的结果得到a (?!…) 顺序否定环视,表示所在位置右侧不能匹配括号内正则 a(?!\w) a c最后的结果得到a (?<=…) 逆序肯定环视,表示所在位置左侧能够匹配括号内正则 1(?<=\w)a 1a (?<!…) 逆序否定环视,表示所在位置左侧不能匹配括号内正则 1 (?<!\w)a 1 a (?(id/name)yes|no) 如果前面的索引为id或者名字为name的分组匹配成功则匹配yes区域的表达式,否则匹配no区域的表达式,no可以省略 (\d)(?(1)\d|a) 32]]></content>
<tags>
<tag>Python</tag>
<tag>正则表达式</tag>
</tags>
</entry>
<entry>
<title><![CDATA[清理Visual Studio 2017旧版本离线安装文件]]></title>
<url>%2F2018%2F07%2F28%2F%E6%B8%85%E7%90%86Visual-Studio-2017%E6%97%A7%E7%89%88%E6%9C%AC%E7%A6%BB%E7%BA%BF%E5%AE%89%E8%A3%85%E6%96%87%E4%BB%B6%2F</url>
<content type="text"><![CDATA[说明Visual Studio 2017内容庞大,如果在线安装相当耗时,因此多数情况下采用离线安装方式。比如用下面的命令: vs_enterprise.exe --layout F:\Dev\VisualStudio\VS2017Ent --lang zh-CN 上面的命令将安装包下载到F:\Dev\VisualStudio\VS2017Ent目录下,用户可以使用本地文件离线安装 Visual Studio 2017。但是,当再次执行上面命令更新离线安装包后,旧版本的安装包不会自动删除,导致离线安装包越来越庞大,存在大量无用安装文件。 下面的代码用来清理旧版本离线安装文件。 C#语言。 程序为控制台应用程序。 Visual Studio 2017 测试通过。 具体代码 文件名:DelLegacyFolder.cs 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109using System;using System.Collections.Generic;using System.IO;namespace SampleCode{ class DelLegacyFolder { static void Main(string[] args) { try { //string dirPath = @"F:\Dev\VisualStudio\VS2017Ent"; string dirPath = @"."; List<string> dirs = new List<string>(Directory.EnumerateDirectories(dirPath)); List<string> olderFolder = new List<string>(); //不自行排序,使用系统自己的排序方式 //dirs.Sort(); int count = 0; for (int i = 0; i < dirs.Count - 1; i++) { string[] OlderFolderName = dirs[i].Split(','); string[] NewerFolderName = dirs[i + 1].Split(','); if ((OlderFolderName[0] == NewerFolderName[0]) && (OlderFolderName[1] != NewerFolderName[1]) && (OlderFolderName.Length == NewerFolderName.Length)) { bool ifNeedOperation = false; switch (OlderFolderName.Length) { case 2: ifNeedOperation = true; break; case 3: if ((OlderFolderName[2] == NewerFolderName[2])) ifNeedOperation = true; break; case 4: if ((OlderFolderName[2] == NewerFolderName[2]) && (OlderFolderName[3] == NewerFolderName[3])) ifNeedOperation = true; break; default: break; } if (ifNeedOperation) { string[] olderVersion = OlderFolderName[1].Substring(OlderFolderName[1].IndexOf("=") + 1).Split('.'); string[] newerVersion = NewerFolderName[1].Substring(NewerFolderName[1].IndexOf("=") + 1).Split('.'); for (int x = 0; x < olderVersion.Length; x++) { if (Int32.Parse(olderVersion[x]) < Int32.Parse(newerVersion[x])) { Console.ForegroundColor = ConsoleColor.Green; Console.WriteLine(dirs[i]); Console.ForegroundColor = ConsoleColor.White; Console.WriteLine(dirs[i + 1]); olderFolder.Add(dirs[i]); count += 1; i += 1; break; } else if (Int32.Parse(olderVersion[x]) > Int32.Parse(newerVersion[x])) { Console.ForegroundColor = ConsoleColor.White; Console.WriteLine(dirs[i]); Console.ForegroundColor = ConsoleColor.Green; Console.WriteLine(dirs[i + 1]); olderFolder.Add(dirs[i + 1]); count += 1; i += 1; break; } } } } } Console.ForegroundColor = ConsoleColor.White; Console.WriteLine("总共有 {0} 个旧文件夹,是否删除? y--删除 n--不删除", count); string yesDel = Console.ReadLine().Trim().ToUpper(); if (yesDel == "Y") { for (int i = 0; i < olderFolder.Count; i++) { Directory.Delete(olderFolder[i], true); } } } catch (UnauthorizedAccessException UAEx) { Console.WriteLine(UAEx.Message); } catch (PathTooLongException PathEx) { Console.WriteLine(PathEx.Message); } Console.WriteLine("执行完毕,按任意键退出。"); Console.ReadKey(); } }} 生成的可以执行文件放置在离线安装包文件目录(例如上面命令行所示的F:\Dev\VisualStudio\VS2017Ent)下面,执行程序可看到如下效果: 编译好的可执行文件: 点击下载]]></content>
<tags>
<tag>Visual Studio</tag>
</tags>
</entry>
<entry>
<title><![CDATA[VSCode 字体大小调整]]></title>
<url>%2F2018%2F07%2F13%2FVSCode%E5%AD%97%E4%BD%93%E5%A4%A7%E5%B0%8F%E8%B0%83%E6%95%B4%2F</url>
<content type="text"><![CDATA[VSCode 默认的菜单、左侧管理器等栏目字体是11px,感觉字体稍小,尤其在暗调主题下,难以识别。可以根据自己的需要修改默认配置文件,得到合适的显示效果。 修改样式文件修改权限 找到VSCode工作区配置文件,位于:安装目录\VSCode\resources\app\out\vs\workbench\workbench.main.css 对文件增加修改权限,具体:右键属性–>安全–>编辑(更改权限)–见下图 修改样式文件配置 打开workbench.main.css文件,搜索/替换所有11px为12px或者自己认为合适的值,这样比较简单,总共有超过30多处 另外一种方式:菜单->帮助->切换开发人员工具,找到需要修改的地方对应的样式表文件内容并修改 修改后遗症 VSCdoe 编辑器标题栏会显示[不受支持],不过不影响使用]]></content>
<tags>
<tag>VSCode</tag>
<tag>Visual Studio</tag>
<tag>Visual Studio Code</tag>
<tag>字体</tag>
</tags>
</entry>
<entry>
<title><![CDATA[Python 学习笔记]]></title>
<url>%2F2018%2F07%2F11%2FPython%E5%AD%A6%E4%B9%A0%E7%AC%94%E8%AE%B0%2F</url>
<content type="text"><![CDATA[基础事项变量命名 变量名尽量小写 类名首字母大写 括号与数据类型 括号 数据类型 例子 [] 列表 [123,’abc’,1.23] () 元组 (1,2,3,4)) {} 字典 {‘name’:’wa’,’age’:20} 其它 = 操作符代表赋值,例如:x = 10,执行结果,x 赋值为 10; == 操作符代表等于判断,例如:1 == 2,执行结果为 False。 Python 2 和 3 差异 print 在3中不是一个保留字,也不是一条语句,而是一个内置的函数调用。 python交互模式下清屏在Linux shell中,清屏操作是clear;在Win cmd中,清屏操作是cls。 在交互模式中使用python,如果要清屏,可以import os,通过os.system()来调用系统命令clear或者cls来实现清屏。 12>>> import os>>> os.system('clear') 但是此时shell中的状态是: 120>>> 首行会有一个0。这个0实际上是os.system()的返回值,0是成功,非零即error code(具体os.system()与命令返回值的对应关系可参考这里)。 可以存储这个返回值,不让其打印出来: 12>>> import os>>> t = os.system('clear') 这样就是真正的清屏了: 1>>> python下编译 .py 为 .pyc生成单个pyc文件:命令: 12python -m py_compile file.pypython -m py_compile /root/src/{file1,file2}.py 脚本: 12import py_compilepy_compile.compile('path') //path是包括.py文件名的路径 批量生成pyc文件:命令: 1python -m compileall /root/src/ 脚本: 12import compileallcompileall.compile_dir(r'H:\game') 生成pyc文件后删除原py文件,直接执行pyc文件(方法同py文件) Python语句和语法语句 语句 角色 例子 赋值 创建引用值 a,b,c = ‘good’,’bad’,’ugly’ 调用 执行函数 log.write(‘hello,world’) 打印调用 打印对象 print(“hello world”) if/elif/else 选择动作 if ‘print’ in text: print(text) for/else 序列迭代 for x in mylist: print(x) while/else 一般循环 while x>y: print(“hello”) pass 空占位符 while True: pass break 循环退出 while True: if exittest(): break continue 继续下一次循环 while True: if skiptest(): continue def 函数和方法 def f(a,b,c=1,*d): print(a+b+c+d[0]) return 函数返回结果 def f(a,b,c=1,*d): return a+b+c+d[0] yield 生成器表达式 def gen(n): for i in n:yield i * 2 global 命名空间,全局变量 x = ‘old’def function(x): global x,y,;x = ‘new’ nonlocal 局部变量(python3.0+) def outer(): x = ‘old’ def function(): nonlocalx;n=’new’ import 模块访问,导入 import sys from 模块属性访问 from sys import stdin class 创建对象 class Subclass(Superclass): staticData = [] def method(self): pass try/except/finally 捕捉异常 try: action()except: print(‘action error’) raise 触发异常 raise EbdSearch(location) assert 调试检查 assert x>y,’x too small’ with/as 环境管理器 with open(‘data’) as myfile: process(myfile) del 删除引用 del data[k]del data[i:j]del obj.attrdel variable 语法 与其他语言的比较 1234if ( x > y) { x = 1; y = 2;} 上面可能是 c、c++、Java 或者其他语言语句。下面是与之等价的 Python 语言语句: 123if x > y: # 可以没有 (),也可以有 x = 1 y = 2 数学操作符 优先级从高到低 操作符 操作 例子 求值为 ** 指数 2**3 8 % 取模/取余数 22%8 6 // 整除/商数取整 22//8 2 / 除法 22/8 2.75 - 减法 5-2 3 + 加法 2+2 4 增强赋值操作符 针对+、-、*、/和%,都有增强的赋值操作符,如下表: 增强的赋值语句 等价的赋值语句 spam += 1 spam = spam+1 spam -= 1 spam = spam-1 spam /= 1 spam = spam/1 spam %= 1 spam = spam%1 转义操作符 转义字符 描述 \(在行尾时) 续行符 \\ 反斜杠符号 \‘ 单引号 \“ 双引号 \a 响铃 \b 退格(Backspace) \e 转义 \000 空 \n 换行 \v 纵向制表符 \t 横向制表符 \r 回车 \f 换页 \oyy 八进制数,yy代表的字符,例如:\o12代表换行 \xyy 十六进制数,yy代表的字符,例如:\x0a代表换行 \other 其它的字符以普通格式输出 比较运算符 表达式 描述 x == y x等于y x !=y x不等于y x < y x小于y x >= y x大于或等于y x is y x和y是同一个对象 x is not y x和y是不同的对象 x in y x是容器(如序列)y的成员 x not in y x不是容器(如序列)y的成员 x not in y 等同于 not x in y,前者更符合常识,与其他语言一致 True 和 False 注意,首字母大写。在Python中任何对象都可判断其布尔值,除了0、0.0、''、None和所有空的序列与集合(列表,字典,集合)布尔值为False之外,其它的都为True,我们可以使用函数bool()进行判别: 12345678910>>> bool('-1')True>>> bool(-1)True>>> bool(0)False>>> bool('')False>>> bool(None)False in 和 not in 操作符12345678>>> 'bat' in ['cat','bat','rat','elephant']True>>> 'dog' in ['cat','bat','rat','elephant']False>>> 'dog' not in ['cat','bat','rat','elephant']True>>> not 'dog' in ['cat','bat','rat','elephant']True 条件判断123456789>>> x = 6>>> if x > 10:... print('>10')... elif x == 10:... print('=10')... else:... print('<10')...<10 循环for 循环1234567>>> for c in 'spam':... print(c.upper())...SPAM 嵌套循环123456789>>> for i in range(1,10):... for j in range(1,10):... print('{} X {} = {}'.format(i,j,i*j))...1 X 1 = 11 X 2 = 21 X 3 = 3...9 X 9 = 81 while 循环123456789>>> x=4>>> while x>0:... print('spam!' * x)... x-=1...spam!spam!spam!spam!spam!spam!spam!spam!spam!spam! break 和 continue123456789101112>>> x=5>>> while x > 0:... x -= 1... if x == 4:... continue... elif x == 1:... break... else:... print('spam!' * x)...spam!spam!spam!spam!spam! 局部和全局作用域局部变量不能在全局作用域内使用123456def spam(): eggs = 222spam()print(eggs) 输出结果 1234Traceback (most recent call last): File "g:/WaProj/PyHello/test.py", line 20, in <module> print(eggs)NameError: name 'eggs' is not defined 局部作用域不能使用其他局部作用域内的变量 例子 123456789101112def spam(): eggs = 99 bacon() print(eggs)def bacon(): ham = 101 eggs = 0spam() 输出结果 199 全局变量可以在局部作用域中读取 例子,局部作用域中不赋值变量,则使用全局变量 1234567def spam(): print(eggs)eggs = 42spam()print(eggs) 输出结果 124242 例子,局部作用域中赋值变量,则使用局部变量 12345678def spam(): eggs = 11 print(eggs)eggs = 42spam()print(eggs) 输出结果 121142 例子,局部作用域中先使用变量,之后定义(赋值)变量,则会出错 12345678def spam(): print(eggs) eggs = 11eggs = 42spam()print(eggs) 输出结果 123456Traceback (most recent call last): File "g:/WaProj/PyHello/test.py", line 7, in <module> spam() File "g:/WaProj/PyHello/test.py", line 2, in spam print(eggs)UnboundLocalError: local variable 'eggs' referenced before assignment 因为 spam 函数中有针对 eggs 的赋值语句,因此 Python 认为 eggs 是局部变量。但 print(eggs)的执行在 eggs 赋值之前,局部变量 eggs 并不存在,因此出错。 global 语句 例子,在函数内用 global 修饰符指定使用全局变量 1234567def spam(): global eggs eggs = 'spam'eggs = 'global'spam()print(eggs) 输出结果 1spam 异常处理 try 例子1,用 try 和 except 语句处理异常 12345678910def spam(divideBy): return 42 / divideBytry: print(spam(2)) print(spam(12)) print(spam(0)) print(spam(1))except ZeroDivisionError: print('Error: Invalid argument.') 输出结果 12321.03.5Error: Invalid argument. 例子2,用 try 和 except 语句处理异常 12345678910def spam(divideBy): try: return 42 / divideBy except ZeroDivisionError: print('Error:无效参数')print(spam(2))print(spam(12))print(spam(0))print(spam(1)) 输出结果 1234521.03.5Error:无效参数None42.0 字符串字符串格式化 表达式形式 % 12>>> '%s,bbb,%s' % ('aaa','ccc') #格式化表达式'aaa,bbb,ccc' 字符串方法调用 str.format() 12345678910>>> '{0},bbb,{1}'.format('aaa','ccc') #格式化方法(2.6,3.0)'aaa,bbb,bbb'>>> '{1},bbb,{0}'.format('aaa','ccc') # 位置变化'ccc,bbb,aaa'>>> '{},bbb,{}'.format('aaa','ccc')'aaa,bbb,ccc'>>> '{},bbb,{c:.2f}'.format('aaa',c=2) #:f 小数表示为定点数'aaa,bbb,2.00'>>> '{},bbb,{c:b}'.format('aaa',c=55) #:b 整数表示为二进制数'aaa,bbb,110111' f 字符串 - 在字符串前加上 f 123>>> a='Python'>>> f'Hello {a}''Hello Python' 模板字符串 123>>> from string import Template>>> Template('Hello $who and $terminal').substitute(who='Python',terminal='Cmder')'Hello Python and Cmder' 联合与拆分 联合 join() 1234>>> '/'.join(['','usr','bin','env'])'/usr/bin/env'>>> '/'.join(('','usr','bin','env'))'/usr/bin/env' 拆分 split 12>>> '/usr/bin/env'.split('/')['', 'usr', 'bin', 'env'] 切片123456789101112131415>>> s='12345654321'>>> s[0:1]'1'>>> s[0:2]'12'>>> s[1:2]'2'>>> s[1:3]'23'>>> s[-1]'1'>>> s[-5:]'54321'>>> s[:-5]'123456' 列表创建数值列表 range()1234>>> range(1,5)range(1, 5)>>> list(range(1,5))[1, 2, 3, 4] 遍历列表1234567891011121314>>> for v in range(1,5):... print(v)...1234>>> r=[]>>> for v in range(1,5):... r.append(v**2)...>>> print(r)[1, 4, 9, 16] 列表解析12>>> [v**2 for v in range(1,5)][1, 4, 9, 16] 列表简单计算统计12345678910111213>>> r=0>>> for v in range(1,5):... r+=v...>>> r10>>> sum(range(1,5))10>>> min(range(1,5))1>>> max(range(1,5))4 多重赋值 一般方式 1234>>> cat = ['fat','black','looud']>>> size = cat[0]>>> color = cat[1]>>> disposition = cat[2] 多重赋值方式 12>>> cat = ['fat','black','looud']>>> size,color,disposition = cat 用 del 删除列表值1234567>>> spam=['cat','bat','rat','elephant']>>> del spam[2]>>> spam['cat', 'bat', 'elephant']>>> del spam[2]>>> spam['cat', 'bat'] 用 remove 方法删除列表值1234>>> cat = ['fat','black','looud']>>> cat.remove('black')>>> cat['fat', 'looud'] 如果该值在列表中多次出现,只删除第一个。 元组 tuple元组与列表几乎一样,区别在于:1.元组用 (),列表用[]。2.元组和字符串一样,不可改变。 123456789>>> eggs=('hello',42,0.5)>>> eggs('hello', 42, 0.5)>>> eggs[0]'hello'>>> eggs[1:3](42, 0.5)>>> len(eggs)3 如果元组中只有一个值,需要在该值后面跟上一个逗号,表明这是一个元组,比如: 1234>>> type(('hello',))<class 'tuple'>>>> type(('hello'))<class 'str'> 用 list() 和 tuple() 函数转换类型如同 str(12) 将返回 ‘12’ 类似,list() 和 tupel() 返回列表和元组类型。 12345678>>> list(('hello',42,0.5))['hello', 42, 0.5]>>> tuple(['hello',42,0.5])('hello', 42, 0.5)>>> list('hello')['h', 'e', 'l', 'l', 'o']>>> tuple('hello')('h', 'e', 'l', 'l', 'o') 引用与 copy() 、 deepcopy()引用 对字符串和数值进行引用赋值时,直接复制的内容,即在内从中重新分配了内存空间,用来存放新的对象: 12345678>>> spam='a'>>> cheese=11>>> cheese=spam>>> spam=100>>> spam100>>> cheese'a' 列表引用赋值时,指向的是对象所在的内存地址,因此指向的内容是一致的,如下所示: 123456789101112>>> spam=[0,1,2,3,4,5]>>> cheese=spam>>> cheese[1]='hi'>>> spam[0, 'hi', 2, 3, 4, 5]>>> cheese[0, 'hi', 2, 3, 4, 5]>>> spam[3]='ah'>>> spam[0, 'hi', 2, 'ah', 4, 5]>>> cheese[0, 'hi', 2, 'ah', 4, 5] coyp() 和 deepcopy() copy() 不是简单引用,而是分配新的内存地址给新变量并赋值 123456789>>> spam = list('ABCD')>>> spam['A', 'B', 'C', 'D']>>> cheese = copy.copy(spam)>>> cheese[1] = 22>>> cheese['A', 22, 'C', 'D']>>> spam['A', 'B', 'C', 'D'] deepcopy()深拷贝,相对于copy()的浅拷贝(shallow copy),如果要复制的列表中包含了列表,就用copy.deepcopy()。 字典创建字典1234567891011121314151617# 常见方式>>> d={'name':'wa','age':20}>>> d{'name': 'wa', 'age': 20}# 另一种创建方式>>> d={}>>> d['name']='wa'>>> d['age']=20>>> d{'name': 'wa', 'age': 20}# dict() 函数>>> dict([('name','wa'),('age',20)]) # []可以{'name': 'wa', 'age': 20}>>> dict({('name','wa'),('age',20)}) # {}也可以{'age': 20, 'name': 'wa'} 字典使用123456>>> d={'name':'wa','age':20}>>> d['name']'wa'>>> d['name'] + ' ' + str(d['age']) + ' 岁了!''wa 20 岁了!'>>> 遍历键–值 键值对 items() 12345678910111213>>> d={'name':'wz','first':'z','last':'w'}>>> for k,v in d.items(): # items()... print(' Key: ' + k)... print('Value: ' + v+'\n')... Key: nameValue: wz Key: firstValue: z Key: lastValue: w 遍历所有 键 keys() 123456>>> for k in d.keys(): # 遍历所有 键 keys()... print(' Key: ' + k.title())... Key: Name Key: First Key: Last 遍历所有 值 values() 123456>>> for v in d.values(): # 遍历所有 值 values()... print(v.title())...WzZW 检查字典中的键或值 检查键或值 123456789>>> d={'name':'wz','first':'z','last':'w'}>>> 'name' in d.keys()True>>> 'w' in d.values()True>>> 'first' in d.values()False>>> 'first' not in d.values()True 嵌套 if 判断 12345678910>>> n=['first']>>> for k in d.keys():... print(k.title())... if k in n: # 嵌套 if 判断... print(' z')...NameFirst zLast get() 方法123456>>> d={'name':'wz','first':'z','last':'w'}>>> d.get('last','wang')'w'>>> d.get('all','wang')'wang'>>> setdefault() 方法123456>>> d={'name':'wz','first':'z','last':'w'}>>> d.setdefault('all','wazz')'wazz'>>> d{'name': 'wz', 'first': 'z', 'last': 'w', 'all': 'wazz'}>>> 漂亮打印 如果在程序中导入 pprint 模块,就可以用 pprint() 和 pformat() 函数来用漂亮的格式打印字典内容。下面例子对比了 print() 和 pprint(): 普通打印 123456789>>> message = 'It was a bright cold day in April, and the clocks were striking thirteen.'>>> count = {}>>>>>> for character in message:... count.setdefault(character, 0)... count[character] = count[character] + 1...>>> print(count){'I': 1, 't': 6, ' ': 13, 'w': 2, 'a': 4, 's': 3, 'b': 1, 'r': 5, 'i': 6, 'g': 2, 'h': 3, 'c': 3, 'o': 2, 'l': 3, 'd': 3, 'y': 1, 'n': 4, 'A': 1, 'p': 1, ',': 1, 'e': 5, 'k': 2, '.': 1} 漂亮打印 pprint() 在屏幕上输出结果,pformat() 得到漂亮打印的文本字符串。 1234567891011121314151617181920212223242526272829303132>>> import pprint>>> message = 'It was a bright cold day in April, and the clocks were striking thirteen.'>>> count = {}>>>>>> for character in message:... count.setdefault(character, 0)... count[character] = count[character] + 1...>>> pprint.pprint(count){' ': 13, ',': 1, '.': 1, 'A': 1, 'I': 1, 'a': 4, 'b': 1, 'c': 3, 'd': 3, 'e': 5, 'g': 2, 'h': 3, 'i': 6, 'k': 2, 'l': 3, 'n': 4, 'o': 2, 'p': 1, 'r': 5, 's': 3, 't': 6, 'w': 2, 'y': 1} 函数简单函数12345>>> def func():... print('简单函数!')...>>> func()简单函数! 位置实参和关键字实参1234567>>> def func(para):... print('简单函数!' + para)...>>> func('这是参数') # 位置实参简单函数!这是参数>>> func(para='这是参数') # 关键字实参简单函数!这是参数 带默认值参数12345>>> def func(para='默认参数值'): # 默认值... print('简单函数!' + para)...>>> func() # 使用默认值简单函数!默认参数值 任意数量实参12345678910>>> def func(*para): # 任意数量实参... print('任意数量!')... print(para)...>>> func('1')任意数量!('1',)>>> func('1','2')任意数量!('1', '2') 模块导入模块是扩展名为.py的文件,函数存在于模块中,使用函数之前需要导入。 test.py 文件内容 函数与函数之间空两行 123456def func2(para='默认参数值'): print('简单函数!' + para)def func(*para): print(para) 导入整个模块1234567>>> import test>>> test.func('a') # 必须指定前缀('a',)>>> test.func('a','b','c') # 必须指定前缀('a', 'b', 'c')>>> test.func2() # 必须指定前缀简单函数!默认参数值 导入指定函数12345678910111213>>> from test import func>>> func('a','b') # 不需要指定前缀('a', 'b')>>> func2 # 没有导入 func2 ,出错Traceback (most recent call last): File "<stdin>", line 1, in <module>NameError: name 'func2' is not defined>>> from test import func,func2>>> func('a','b') # 可以不指定前缀('a', 'b')>>> func2() # 可以不指定前缀简单函数!默认参数值 导入所有函数1>>> from test import * 导入类与导入函数格式一致。 类 类是模块内的属性 创建类 test.py 文件内容 12345678910111213141516class Dog(): def __init__(self, name, age): self.name = name self.age = age def sleep(self): print(self.name.title() + " 正在睡觉...") def playgame(self): print(self.name.title() + " 游戏中...") def getage(self): print(self.name.title() + " " + str(self.age) + " 岁了!") def setage(self, age): self.age = age 创建和操作实例12345678910111213141516171819202122232425>>> import test>>> mydog=test.Dog('huahua',2) # 创建实例>>> mydog.name # 访问属性'huahua'>>> mydog.age2>>> mydog.sleep() # 调用方法Huahua 正在睡觉...>>> mydog.playgame()Huahua 游戏中...>>> mydog.getage()Huahua 2 岁了!>>> mydog.age=3 # 直接修改实例的属性值>>> mydog.age3>>> mydog.color="black" # 在实例内产生全新的属性,之前在类中并没有定义过>>> mydog.color'black'>>> mydog.getage()Huahua 3 岁了!>>> mydog.setage(5) # 调用方法修改属性值>>> mydog.age5>>> mydog.getage()Huahua 5 岁了! 类继承 超类列在子类开头的括号中。 含有继承的类成为子类,子类所继承的类就是超类。 子类与超类 test.py 文件增加继承类内容 1234567891011121314151617181920212223class Dog(): # 超类 def __init__(self, name, age): self.name = name self.age = age def sleep(self): print(self.name.title() + " 正在睡觉...") def playgame(self): print(self.name.title() + " 游戏中...") def getage(self): print(self.name.title() + " " + str(self.age) + " 岁了!") def setage(self, age): self.age = ageclass LittleDog(Dog): # 创建继承类(子类:LittleDog,超类:Dog) def setage(self, age): # 重写父类方法 if 0 < age <= 3: self.age = age else: print('不合适的小狗年龄!') 测试继承类 12345678910111213>>> import test>>> mydog=test.LittleDog('huahua',2)>>> mydog.name'huahua'>>> mydog.age2>>> mydog.getage()Huahua 2 岁了!>>> mydog.setage(3)>>> mydog.getage()Huahua 3 岁了!>>> mydog.setage(6)不合适的小狗年龄! 继承在Python 2.7 和 3 之间的差异 Python 3中的继承 12345678class Dog(): def __init__(self, name, age): ...class LittleDog(Dog): def __init__(self, name, age): super().__init__(name,age) ... Python 2.7中的继承 12345678class Dog(object): def __init__(self, name, age): ...class LittleDog(Dog): def __init__(self, name, age): super(LittleDog,self).__init__(name,age) ... 类截获Python运算符截获Python运算符,即运算符重载,让用类写成的对象,可以截获并相应在内置类型上的运算:加法、切片、打印和点号运算符等。 运算符重载例子 当心的实例构造时,会调用init(self是新的ThirdClass对象)。 当ThirdClass实例出现在 + 表达式中时,则会调用add。 当打印一个对象的时候(从技术上讲,当通过 str 内置函数或者其 Python 内部的等价形式来将其转换为打印字符串的时候),运行str。 1234567891011121314151617181920212223242526272829303132333435>>> class FirstClass: # Define a class object... def setdata(self, value): # Define class methods... self.data = value # self is the instance... def display(self):... print(self.data) # self.data: per instance...>>> class SecondClass(FirstClass): # Inherits setdata... def display(self): # Changes display... print('Current value = "%s"' % self.data)...>>> class ThirdClass(SecondClass): # Inherit from SecondClass... def __init__(self, value): # On "ThirdClass(value)"... self.data = value... def __add__(self, other): # On "self + other"... return ThirdClass(self.data + other)... def __str__(self): # On "print(self)", "str()"... return '[ThirdClass: %s]' % self.data... def mul(self, other): # In-place change: named ... self.data *= other...>>> a = ThirdClass('abc') # __init__ called>>> a.display() # Inherited method calledCurrent value = "abc">>> print(a) # __str__: returns display string[ThirdClass: abc]>>> b = a + 'xyz' # __add__: makes a new instance>>> b.display() # b has all ThirdClass methods Current value = "abcxyz">>> print(b) # __str__: returns display string[ThirdClass: abcxyz]>>> a.mul(3) # mul: changes instance in-place>>> print(a)[ThirdClass: abcabcabc] self Python 中的 self 相当于 C++ 或 Java 中的 this,在 Python 中 self 必须明确写出来。 self 不一定必须写成 self,可以起任何其他名字(比如:a,b,this等),鉴于其总是指向对象本身,因此习惯上将其命名为 self。 self 指向创建的实例本身(是实例,而不是类)。参考下面代码: 12345678910111213>>> class Test:... def prt(self):... print(self)... print(self.__class__)...>>> x = Test()>>> x.prt()<__main__.Test object at 0x000001F4E8C8FCF8> # x 实例地址<class '__main__.Test'>>>> y = Test()>>> y.prt()<__main__.Test object at 0x000001F4E8C8FC88> # y 实例地址<class '__main__.Test'> Python 正则表达式内容太多,独立发布,参见 Python 正则表达式。 例子程序猜数字12345678910111213141516171819import randomrnum = random.randint(1, 20)print('猜测1~20之间的一个数。')for guessNum in range(1, 7): print('请输入猜测数字:') guess = int(input()) if guess < rnum: print('小了') elif guess > rnum: print('大了') else: breakif guess == rnum: print('干得好!你用了 ' + str(guessNum) + '次机会猜对了结果。')else: print('很遗憾,正确的数字是' + str(rnum)) Collatz 序列123456789101112131415def collatz(number): if number % 2 == 0: print(number//2) return number//2 elif number % 2 == 1: print(3*number+1) return(3*number+1)iInput = input('请输入一个整数:')try: iReturn = collatz(int(iInput)) while iReturn != 1: iReturn = collatz(int(iReturn))except: print('输入的不是整数!') 打印列表内容123456789def plst(lst): splst = "'" for i in range(1, len(lst)): splst += lst[i-1]+', ' splst += 'and ' + lst[i] + "'" print(splst)l = ['dog', 'cat', 'tiger', 'elephant']plst(l) 结尾]]></content>
<tags>
<tag>Python</tag>
</tags>
</entry>
<entry>
<title><![CDATA[CSharp本质论笔记 第8章 值类型]]></title>
<url>%2F2018%2F05%2F12%2FCSharp%E6%9C%AC%E8%B4%A8%E8%AE%BA%E7%AC%94%E8%AE%B0-%E7%AC%AC8%E7%AB%A0-%E5%80%BC%E7%B1%BB%E5%9E%8B%2F</url>
<content type="text"><![CDATA[结构初学者主题再次介绍值类型和引用类型,温故知新。 值类型值类型直接包含值,换言之,变量引用的位置就是值在内存中实际存储的位置。因此,将一个原始变量的值赋值给另一个变量,会在新变量的位置创建原始变量值的一个内存副本。 由于值类型需要有一个内训副本,所以定义时通常不需要让他们消耗太多内存(通常应该小于16字节)。 引用类型与值类型不同,引用类型的变量的值是对一个对象实例的引用(通常是内存地址),要去那个位置找到对象实例的数据。]]></content>
<tags>
<tag>C#</tag>
<tag>C#本质论</tag>
<tag>Essential C#</tag>
</tags>
</entry>
<entry>
<title><![CDATA[VSCdoe+插件 使用配置记录]]></title>
<url>%2F2018%2F01%2F03%2FVSCdoe-%E6%8F%92%E4%BB%B6-%E4%BD%BF%E7%94%A8%E9%85%8D%E7%BD%AE%E8%AE%B0%E5%BD%95%2F</url>
<content type="text"><![CDATA[HTML 中 CSS Class 智能提示1.安装插件:HTML CSS Support 2.设置中添加以下代码: 12345"editor.parameterHints": true,"editor.quickSuggestions": { "other": true, "comments": true, "strings": true 查看 HTML 样式在 CSS 中的定义安装插件:CSS Peek,当你在 HTML 文件中右键单击选择器时,选择“ Go to Definition 和 Peek definition ”选项,它便会给你发送样式设置的 CSS 代码。 不同代码文件 Tab 键设定设置中添加以下代码: 123456789101112131415"[html]": { "editor.tabSize": 4},"[css]": { "editor.tabSize": 4},"[javascript]": { // 按 "Tab" 时插入空格。该设置在 `editor.detectIndentation` 启用时根据文件内容进行重写。 "editor.insertSpaces": true, // 一个制表符等于的空格数。该设置在 `editor.detectIndentation` 启用时根据文件内容进行重写。 "editor.tabSize": 2},"[typescript]": { "editor.tabSize": 2} 标题栏显示文件全路径名设置中添加以下代码: 1"window.title": "${activeEditorLong}" 文件自动保存设置中添加以下代码: 1"files.autoSave": "onFocusChange" 菜单/侧边栏等字体大小调整VSCode 默认的菜单、左侧管理器等栏目字体是11px,感觉字体稍小,尤其在暗调主题下,难以识别。可以根据自己的需要修改默认配置文件,得到合适的显示效果。 修改样式文件修改权限 找到VSCode工作区配置文件,位于:安装目录\VSCode\resources\app\out\vs\workbench\workbench.main.css 对文件增加修改权限,具体:右键属性–>安全–>编辑(更改权限)–见下图 修改样式文件配置 打开workbench.main.css文件,搜索/替换所有11px为12px或者自己认为合适的值,这样比较简单,总共有超过30多处 另外一种方式:菜单->帮助->切换开发人员工具,找到需要修改的地方对应的样式表文件内容并修改 修改后遗症 VSCdoe 编辑器标题栏会显示[不受支持],不过不影响使用]]></content>
<tags>
<tag>VSCode</tag>
<tag>插件</tag>
<tag>Visual Studio</tag>
<tag>Visual Studio Code</tag>
</tags>
</entry>
<entry>
<title><![CDATA[C#本质论笔记 第7章 接口]]></title>
<url>%2F2017%2F12%2F28%2FCSharp%E6%9C%AC%E8%B4%A8%E8%AE%BA%E7%AC%94%E8%AE%B0-%E7%AC%AC7%E7%AB%A0-%E6%8E%A5%E5%8F%A3%2F</url>
<content type="text"><![CDATA[接口概述与 类 一样,在接口中可以定义一个和多个方法、属性 、索引指示器和事件。但与类不同的是,接口中仅仅是它们的声明,并不提供实现。因此接口是函数成员声明的集合。如果类或结构从一个接口派生,则这个类或结构负责实现该接口中所声明的所有成员。一个接口可以从多个接口继承,而一个类或结构可以实现多个接口。由于 C# 语言不支持多继承,因此,如果某个类需要继承多个类的行为时,只能使用多个接口加以说明。 对接口的理解: 接口只有声明,没有实现,由继承该接口的类或者结构实现。 C#的类只支持单继承,采用接口变相实现了多继承。 接口更多的是一种规范约束,要求继承该接口的类或者结构必须对接口成员进行实现。 接口声明接口声明是一种类型声明,它定义了一种新的接口类型。接口声明格式如下: 属性 接口修饰符 interface 接口名:基接口{接口体} 其中,关键字interface、接口名和接口体时必须的,其它项是可选的。接口修饰符可以是new、public、protected、nternal和private。例子: 1234567public interface IExample { //所有接口成员都不能包括实现 string this [int index] { get; set; } //索引指示器声明 event EventHandler E; //事件声明 void F (int value); //方法声明 string P { get; set; } //属性声明} 声明接口时,需注意以下内容: 接口成员只能是方法、属性、索引指示器和事件,不能是常量、域、操作符、构造函数或析构函数,不能包含任何静态成员。 接口成员声明不能包含任何修饰符,接口成员默认访问方式是public。 接口的继承类似于类的继承性,接口也有继承性。派生接口继承了基接口中的函数成员说明。接口允许多继承,一个派生接口可以没有基接口,也可以有多个基接口。在接口声明的冒号后列出被继承的接口名字,多个接口名之间用分号分割。例子: 123456789101112131415using System;interface IControl { void Paint ();}interface ITextBox : IControl //继承了接口Icontrol的方法aint(){ void SetText (string text);}interface IListBox : IControl //继承了接口Icontrol的方法Paint(){ void SetItems (string[] items);}interface IComboBox : ITextBox, IListBox { //可以声明新方法} 上面的例子中,接口ITextBox和IListBox都从接口IControl中继承,也就继承了接口 IControl的Paint方法。接口IComboBox从接口ITextBox和IListBox中继承,因此它应该继承了接口ITextBox的SetText方法和IListBox的SetItems方法,还有IControl的Paint方法。 类对接口的实现前面已经说过,接口定义不包括函数成员的实现部分。继承该接口的类或结构应实现这些函数成员。这里主要讲述通过类来实现接口。类实现接口的本质是,用接口规定类应实现那些函数成员。用类来实现接口时,接口的名称必须包含在类声明中的基类列表中。 加入定义一个描述个人情况的类Person,从类Person可以派生出其它类,例如:工人类、公务员类、医生类等。这些类有一些共有的方法和属性,例如工资属性。一般希望所有派生类访问工资属性时用同样变量名。该属性定义在类Person中不合适,因为有些人无工资,如小孩。如定义一个类作为基类,包含工资属性,但C#不支持多继承。可行的办法是使用接口,在接口中声明工资属性。工人类、公务员类、医生类等都必须实现该接口,也就保证了它们访问工资属性时用同样变量名。例子如下: 1234567891011121314151617181920212223242526272829303132333435363738394041424344454647484950515253using System;public interface I_Salary //接口{ decimal Salary //属性声明 { get; set; }}public class Person { private string P_name = "张三"; //P_name是私有字段 private int P_age = 12; //P_age是私有字段 public void Display () //类的方法声明,显示姓名和年龄 { Console.WriteLine ("姓名:{0},年龄:{1}", P_name, P_age); } public string Name //定义属性Name { get { return P_name; } set { P_name = value; } } public int Age //定义属性Age { get { return P_age; } set { P_age = value; } }}public class Employee : Person, I_Salary //Person类是基类,I_Salary是接口{ //不同程序员完成工人类、医生类等,定义工资变量名称可能不同 private decimal salary; public new void Display () { base.Display (); Console.WriteLine ("薪金:{0} ", salary); } //工人类、医生类等都要实现属性Salary,保证使用的工资属性同名 public decimal Salary { get { return salary; } set { salary = value; } }}public class Test { public static void Main () { Employee S = new Employee (); S.Name = "田七"; //修改属性Name S.Age = 20; //修改属性Age S.Salary = 2000; //修改属性Salary S.Display (); }}/* 输出姓名:田七,年龄:20薪金:2000*/ 抽象类和接口比较 抽象类 接口 相同点 可以被继承 可以被继承 可以有方法的声明 可以有方法的声明 不可以被实例化 不可以被实例化 子类必须实现基类的方法除非子类是抽象类 子类必须实现基类的方法除非子类是抽象类 以下为不同点 一个类只能直接继承一个类(包括抽象类) 一个类可以直接继承多个接口 是从一系列相关对象中抽象出来的概念, 因此反映的是事物的内部共性; 是为了满足外部调用而定义的一个功能约定, 因此反映的是事物的外部特性 是一个不完整的类,需要进一步细化 是一个行为规范。 抽象基类可以定义字段、属性、方法实现。 接口只能定义属性、索引器、事件、和方法声明,不能包含字段。 更多的是定义在一系列紧密相关的类间 大多数是关系疏松但都实现某一功能的类中 只能作用于引用类型 可以作用于值类型和引用类型 抽象类既可以定义规则,还可能提供已实现的成员。 只能定义抽象规则 结尾]]></content>
<tags>
<tag>C#</tag>
<tag>C#本质论</tag>
<tag>Essential C#</tag>
</tags>
</entry>
<entry>
<title><![CDATA[C#本质论笔记 第6章 继承]]></title>
<url>%2F2017%2F12%2F26%2FCSharp%E6%9C%AC%E8%B4%A8%E8%AE%BA%E7%AC%94%E8%AE%B0-%E7%AC%AC6%E7%AB%A0-%E7%BB%A7%E6%89%BF%2F</url>
<content type="text"><![CDATA[通过继承,可以创建重用、扩展和修改在其他类中定义的行为的新类。继承主要实现重用代码,节省开发时间。C#中的继承符合下列规则: 继承是可传递的。如果C从B中派生,B又从A中派生,那么C不仅继承了B中声明的成员,同样也继承了A中的成员。Object类作为所有类的基类。 派生类应当是对基类的扩展。派生类可以添加新的成员,但不能除去已经继承的成员的定义。 构造函数和析构函数不能被继承。除此之外的其它成员,不论对它们定义了怎样的访问方式,都能被继承。基类中成员的访问方式只能决定派生类能否访问它们。 派生类如果定义了与继承而来的成员同名的新成员,就可以覆盖已继承的成员。但这并不因为这派生类删除了这些成员,只是不能再访问这些成员。 类可以定义虚文法、虚属性以及虚索引指示器,它的派生类能够重载这些成员,从而实现类可以展示出多态性。 derived 派生派生类只能有一个直接基类。 但是,继承是可传递的。 如果 ClassC 派生自 ClassB,并且 ClassB 派生自 ClassA,则 ClassC 会继承在 ClassB 和 ClassA 中声明的成员。 从概念上讲,派生类是基类的专门化。例如,如果有一个基类 Animal,则可以有一个名为 Mammal 的派生类,以及另一个名为 Reptile 的派生类。Mammal 是 Animal,Reptile 也是 Animal,但每个派生类表示基类的不同专门化。 定义要从其他类派生的类时,派生类会隐式获得基类的所有成员(除了其构造函数和终结器)。派生类因而可以重用基类中的代码,而无需重新实现。在派生类中,可以添加更多成员。通过这种方法,派生类可扩展基类的功能。 从一个类派生出另一个类 PdaItem类派生出Contact类(一个类继承自另一个类 Contact类继承自PdaItem类) 123456789101112public class PdaItem{ public string Name { get; set; } public DateTime LastUpdated { get; set; }}// Define the Contact class as inheriting the PdaItem classpublic class Contact : PdaItem{ public string Address { get; set; } public string Phone { get; set; }} 使用继承的属性 123456789public class Program{ public static void Main() { Contact contact = new Contact(); contact.Name = "Inigo Montoya"; // ... }} Contact类自身没有定义Name属性,但仍然可以使用继承自 PdaItem 类的Name属性,并把它作为 Contact自身的一部分使用。除此之外,从 Contact 派生的其他任何类也会继承 PdaItem 类(以及 PdaItem的父类)的成员。这个继承链条 基类和派生类之间的转换 派生类型的值可以直接赋值给基类型的值,不需要添加转型操作符,不会引发异常。 反之则不成立。从基类型转换为派生类型,要求执行显式转换,并且在运行时可能会失败。 123456789101112131415public class Program{ public static void Main() { // Derived types can be implicitly converted to // base types Contact contact = new Contact(); PdaItem item = contact; // ... // Base types must be cast explicitly to derived types contact = (Contact)item; // ... }} private访问修饰符派生类继承了除构造器和析构器之外的所有基类成员。但是,继承并不意味着一定能够访问。私有成员只能在声明他们的那个类中才能访问,派生类不能访问基类的private成员(除了一个例外,派生类同时是基类的一个嵌套类)。 123456789101112131415161718public class PdaItem { private string _Name; // ...}public class Contact : PdaItem { // ...}public class Program { public static void Main () { Contact contact = new Contact (); // ERROR: 'PdaItem. _Name' is inaccessible // due to its protection level //contact._Name = "Inigo Montoya"; //uncomment this line and it will not compile }} protected访问修饰符受保护成员在其所在的类中可由派生类实例访问。 只有在通过派生类类型进行访问时,基类的受保护成员在派生类中才是可访问的。A protected member of a base class is accessible in a derived class only if the access occurs through the derived class type. 1234567891011121314151617181920class A{ protected int x = 123;}class B : A{ static void Main() { A a = new A(); B b = new B(); // Error CS1540, because x can only be accessed by // classes derived from A. // a.x = 10; // OK, because this class derives from A. b.x = 10; }} 语句 a.x = 10 生成错误,因为它是在静态方法 Main 中生成的,而不是类 B 的实例。The statement a.x = 10 generates an error because it is made within the static method Main, and not an instance of class B. 无法保护结构成员,因为无法继承结构。 在此示例中,DerivedPoint 类是从 Point 派生的。In this example, the class DerivedPoint is derived from Point. 因此,可以从派生类直接访问基类的受保护成员。Therefore, you can access the protected members of the base class directly from the derived class. 12345678910111213141516171819class Point{ protected int x; protected int y;}class DerivedPoint: Point{ static void Main() { DerivedPoint dpoint = new DerivedPoint(); // Direct access to protected members: dpoint.x = 10; dpoint.y = 15; Console.WriteLine("x = {0}, y = {1}", dpoint.x, dpoint.y); }}// Output: x = 10, y = 15 如果将 x 和 y 的访问级别更改为 private,编译器将发出错误消息: 'Point.y' is inaccessible due to its protection level. 'Point.x' is inaccessible due to its protection level. 单继承 多继承 聚合(Aggregation)C++支持多继承,C#只支持单继承。在极少数需要多继承类结构的时候,一般的解决方案是使用聚合(Aggregation);换言之,不是一个类从另外一个类继承,而是一个类包含另一个类的实例。 用聚合解决单继承问题 123456789101112131415161718192021222324public class PdaItem { // ...}public class Person { public string FirstName { get; set; } public string LastName { get; set; } // ...}public class Contact : PdaItem { private Person InternalPerson { get; set; } public string FirstName { get { return InternalPerson.FirstName; } set { InternalPerson.FirstName = value; } } public string LastName { get { return InternalPerson.LastName; } set { InternalPerson.LastName = value; } } // ...} 方法有点怪异,不够合理,有点多余。 ヾ(゚∀゚ゞ)ヾ(。 ̄□ ̄)ツ゜゜゜ 密封类sealed 修饰符可阻止其他类继承自该类。在下面的示例中,类 B 继承自类 A,但没有类可以继承自类 B。 12class A {}sealed class B : A {} System.String 类型就是用sealed修饰符禁止了派生。下面的代码编译不通过: 1234567891011using System;public class Program { public static void Main () { }}public class MyClass : String { }/* 输出错误 CS0509 “MyClass”: 无法从密封类型“string”派生*/ override 替代 覆盖 重写overload 重载:指的是同一个类中有两个或多个名字相同但是参数不同的方法,(注:返回值不能区别函数是否重载),重载没有关键字。 override 替代 覆盖 重写:指子类对父类中虚函数或抽象函数的“覆盖”(这也就是有些书将过载翻译为覆盖的原因),但是这种“覆盖”和用new关键字来覆盖是有区别的。另外,override 需要和 virtual 对应(配合、配对)使用。override 似乎提高了派生类成员的优先级,例如:用基类声明类型而用派生类实例化的对象,具有override 修饰符的派生类成员会强制替代基类成员,而 ‘new’ 修饰符则无次优先级,还会执行基类成员。 new 替代:显式隐藏从基类继承的成员。隐藏继承的成员时,该成员的派生版本将替换基类版本。如果基类和派生类中有同名成员,而不使用 new ,则会产生编译警告,好像没有更多的差异了。 基类除了构造器和析构器之外,所有成员都可以在派生类中继承。 virtualvirtual 关键字用于修改方法、属性、索引器或事件声明,并使它们可以在派生类中被重写。例如,此方法可被任何继承它的类替代: 1234public virtual double Area(){ return x * y;} 虚拟成员的实现可由派生类中的替代成员更改。有关如何使用 virtual 关键字的详细信息,请参阅使用 Override 和 New 关键字进行版本控制和了解何时使用 Override 和 New 关键字。 以下示例显示了虚拟属性: 12345678910111213141516171819202122232425262728293031323334using System;class MyBaseClass { // virtual auto-implemented property. Overrides can only // provide specialized behavior if they implement get and set accessors. public virtual string Name { get; set; } // ordinary virtual property with backing field private int num; public virtual int Number { get { return num; } set { num = value; } }}class MyDerivedClass : MyBaseClass { private string name; // Override auto-implemented property with ordinary property // to provide specialized accessor behavior. public override string Name { get { return name; } set { if (value != String.Empty) { name = value; } else { name = "Unknown"; } } }} 下面示例中,Shape 类包含 x、y 两个坐标和 Area() 虚拟方法。不同的形状类(如 Circle、Cylinder 和 Sphere)继承 Shape 类,并为每个图形计算表面积。每个派生类都有各自的 Area() 替代实现。 请注意,继承的类 Circle、Sphere 和 Cylinder 均使用初始化基类的构造函数(参考 构造器链),base 用法参考 base,如下面的声明中所示。 1public Cylinder(double r, double h): base(r, h) {} 根据与方法关联的对象,下面的程序通过调用 Area() 方法的相应实现来计算并显示每个对象的相应区域。 12345678910111213141516171819202122232425262728293031323334353637383940414243444546474849505152535455565758using System;class TestClass { public class Shape { public const double PI = Math.PI; protected double x, y; public Shape () { } public Shape (double x, double y) { this.x = x; this.y = y; } public virtual double Area () { return x * y; } } public class Circle : Shape { public Circle (double r) : base (r, 0) { } public override double Area () { return PI * x * x; } } class Sphere : Shape { public Sphere (double r) : base (r, 0) { } public override double Area () { return 4 * PI * x * x; } } class Cylinder : Shape { public Cylinder (double r, double h) : base (r, h) { } public override double Area () { return 2 * PI * x * x + 2 * PI * x * y; } } static void Main () { double r = 3.0, h = 5.0; Shape c = new Circle (r); Shape s = new Sphere (r); Shape l = new Cylinder (r, h); // Display results: Console.WriteLine ("Area of Circle = {0:F2}", c.Area ()); Console.WriteLine ("Area of Sphere = {0:F2}", s.Area ()); Console.WriteLine ("Area of Cylinder = {0:F2}", l.Area ()); }}/* Output: Area of Circle = 28.27 Area of Sphere = 113.10 Area of Cylinder = 150.80*/ 对成员进行重载,会造成“运行时”调用最深的或者说派生的最远的实现,示例如下: 1234567891011121314151617181920212223242526272829303132333435363738394041424344using System;public class Program { public static void Main () { Contact contact; PdaItem item; contact = new Contact (); item = contact; // Set the name via PdaItem variable item.Name = "Inigo Montoya"; // Display that FirstName & LastName // properties were set. Console.WriteLine ("{0} {1}", contact.FirstName, contact.LastName); }}public class PdaItem { public virtual string Name { get; set; } // ...}public class Contact : PdaItem { public override string Name { get { return FirstName + " " + LastName; } set { string[] names = value.Split (' '); // Error handling not shown. FirstName = names[0]; LastName = names[1]; } } public string FirstName { get; set; } public string LastName { get; set; } // ...} 上例代码中,调用 item.Name,而item被声明为一个PdaItem。但是,contact的 FirstName 和 LastName 还是会被处理。这里的规则是:“运行时”遇到虚方法时,它会调用虚成员派生得最远的重写。在上例中,代码实例化一个Contact并调用Contact.Name,因为Contact包含了Name派生的最远的实现。 虚方法不应包含关键代码,因为如果派生类重写了它,那些代码就永远得不到调用。创建类时,必须谨慎选择是否允许重写方法,因为控制不了派生的实现。 new前面例子中,如果重写子类方法却没有使用override关键字,编译器会报告警告信息,如下所示: warning CS0114: “Contact.Name”隐藏继承的成员“PdaItem.Name”。若要使当前成员重写该实现,请添加关键字 override。否则,添加关键字 new。 override 修饰符用于扩展基类方法,而 new 修饰符则用于隐藏该方法。 在用作声明修饰符时,new 关键字可以显式隐藏从基类继承的成员。隐藏继承的成员时,该成员的派生版本将替换基类版本。虽然可以不使用 new 修饰符来隐藏成员,但将收到编译器警告。 如果使用 new 来显式隐藏成员,将禁止此警告。如果不使用 new ,则执行基类的成员,而忽略派生类的成员。 在此示例中,基类 BaseClass 和派生类 DerivedClass 使用相同的字段名 x,从而隐藏了继承字段的值。另外还演示了如何使用完全限定名访问基类的隐藏成员。 12345678910111213141516171819202122232425262728using System;public class BaseClass { public static int x = 55; public static int y = 22;}public class DerivedClass : BaseClass { // Hide field 'x'. new public static int x = 100; static void Main () { // Display the new value of x: Console.WriteLine (x); // Display the hidden value of x: Console.WriteLine (BaseClass.x); // Display the unhidden member y: Console.WriteLine (y); }}/*Output:1005522*/ 使用new关键字后,具有 BaseClass 类型的变量继续访问 BaseClass 的成员,而具有 DerivedClass 类型的变量首先继续访问 DerivedClass 中的成员,然后再考虑从 BaseClass 继承的成员。 示例:无修饰符 无同名成员12345678910111213141516171819202122232425262728293031using System;class BaseClass { public void Method1 () { Console.WriteLine ("Base - Method1"); }}class DerivedClass : BaseClass { public void Method2 () { Console.WriteLine ("Derived - Method2"); }}class Program { static void Main (string[] args) { BaseClass bc = new BaseClass (); DerivedClass dc = new DerivedClass (); BaseClass bcdc = new DerivedClass (); bc.Method1 (); dc.Method1 (); dc.Method2 (); bcdc.Method1 (); } // Output: // Base - Method1 // Base - Method1 // Derived - Method2 // Base - Method1} 示例:无修饰符 有同名成员1234567891011121314151617181920212223242526272829303132333435363738using System;class BaseClass { public void Method1 () { Console.WriteLine ("Base - Method1"); } public void Method2 () { Console.WriteLine ("Base - Method2"); }}class DerivedClass : BaseClass { public void Method2 () { Console.WriteLine ("Derived - Method2"); }}class Program { static void Main (string[] args) { BaseClass bc = new BaseClass (); DerivedClass dc = new DerivedClass (); BaseClass bcdc = new DerivedClass (); bc.Method1 (); bc.Method2 (); dc.Method1 (); dc.Method2 (); bcdc.Method1 (); bcdc.Method2 (); } // Output: // Base - Method1 // Base - Method2 // Base - Method1 // Derived - Method2 // Base - Method1 // Base - Method2} 你将看到在 BaseClass 中添加 Method2 方法将引发警告。警告显示 DerivedClass 中的 Method2 方法隐藏了 BaseClass 中的 Method2 方法。如果希望获得该结果,则建议使用 Method2 定义中的 new 关键字。或者,可重命名 Method2 方法之一来消除警告。 示例:new 修饰符 有同名成员1234567891011121314151617181920212223242526272829303132333435363738using System;class BaseClass { public void Method1 () { Console.WriteLine ("Base - Method1"); } public void Method2 () { Console.WriteLine ("Base - Method2"); }}class DerivedClass : BaseClass { public new void Method2 () { Console.WriteLine ("Derived - Method2"); }}class Program { static void Main (string[] args) { BaseClass bc = new BaseClass (); DerivedClass dc = new DerivedClass (); BaseClass bcdc = new DerivedClass (); bc.Method1 (); bc.Method2 (); dc.Method1 (); dc.Method2 (); bcdc.Method1 (); bcdc.Method2 (); } // Output: // Base - Method1 // Base - Method2 // Base - Method1 // Derived - Method2 // Base - Method1 // Base - Method2} 输出结果与上例不使用 new 修饰符是一样的,只是不再有警告。就CIL来说,new修饰符对编译器生成的代码没有任何影响。从 C# 的角度看,它唯一的作用就是移除编译器警告。 示例:virtual override 修饰符1234567891011121314151617181920212223242526272829303132333435363738394041using System;class BaseClass { public virtual void Method1 () { Console.WriteLine ("Base - Method1"); } public void Method2 () { Console.WriteLine ("Base - Method2"); }}class DerivedClass : BaseClass { public override void Method1 () { Console.WriteLine ("Derived - Method1"); } public new void Method2 () { Console.WriteLine ("Derived - Method2"); }}class Program { static void Main (string[] args) { BaseClass bc = new BaseClass (); DerivedClass dc = new DerivedClass (); BaseClass bcdc = new DerivedClass (); bc.Method1 (); bc.Method2 (); dc.Method1 (); dc.Method2 (); bcdc.Method1 (); bcdc.Method2 (); } // Output: // Base - Method1 // Base - Method2 // Derived - Method1 // Derived - Method2 // Derived - Method1 // Base - Method2} 使用 override 修饰符可使 bcdc 访问 DerivedClass 中定义的 Method1 方法。通常,这是继承层次结构中所需的行为。让具有从派生类创建的值的对象使用派生类中定义的方法。可使用 override 扩展基类方法实现该行为。 sealed 修饰符类使用 sealed 修饰符是禁止从该类继承。类似的,虚成员也可以密封。一般很少这样用,除非迫切需要这种限制。 1234567891011121314class A { public virtual void Method () { }}class B : A { public override sealed void Method () { }}class C : B { // ERROR: Cannot override sealed members //public override void Method() //{ //}} base 成员base 关键字用于从派生类中访问基类的成员: 调用基类上已被其他方法重写的方法。Call a method on the base class that has been overridden by another method. 指定创建派生类实例时应调用的基类构造函数。 在本例中,基类 Person 和派生类 Employee 都有一个名为 Getinfo 的方法通过使用 base 关键字,可以从派生类中调用基类的 Getinfo 方法。 1234567891011121314151617181920212223242526272829303132using System;public class Person { protected string ssn = "444-55-6666"; protected string name = "John L. Malgraine"; public virtual void GetInfo () { Console.WriteLine ("Name: {0}", name); Console.WriteLine ("SSN: {0}", ssn); }}class Employee : Person { public string id = "ABC567EFG"; public override void GetInfo () { // Calling the base class GetInfo method: base.GetInfo (); Console.WriteLine ("Employee ID: {0}", id); }}class TestClass { static void Main () { Employee E = new Employee (); E.GetInfo (); }}/*OutputName: John L. MalgraineSSN: 444-55-6666Employee ID: ABC567EFG*/ 本示例显示如何指定在创建派生类实例时调用的基类构造函数。 123456789101112131415161718192021222324252627282930313233343536using System;public class BaseClass { int num; public BaseClass () { Console.WriteLine ("in BaseClass()"); } public BaseClass (int i) { num = i; Console.WriteLine ("in BaseClass(int i)"); } public int GetNum () { return num; }}public class DerivedClass : BaseClass { // This constructor will call BaseClass.BaseClass() public DerivedClass () : base () { } // This constructor will call BaseClass.BaseClass(int i) public DerivedClass (int i) : base (i) { } static void Main () { DerivedClass md = new DerivedClass (); DerivedClass md1 = new DerivedClass (1); }}/*Output:in BaseClass()in BaseClass(int i)*/ abstract 抽象使用 abstract 关键字可以创建不完整且必须在派生类中实现的类和 class 成员。 通过在类定义前面放置关键字 abstract,可以将类声明为抽象类。 抽象类是仅供派生的类。 抽象类无法实例化,只能实例化从它派生的类。 抽象成员应当被重写,所以自动成为虚成员(但不能用virtual关键字显示声明)。 抽象成员不能声明为私有,私有的话派生类看不到它们。 理解: abstract 抽象:显式 ⬇ 强制要求所有派生类来提供实现。 override 替代:显式 ⬆ 强制重写基类成员,。 1234public abstract class A{ // Class members here.} 抽象类不能实例化。抽象类的用途是提供一个可供多个派生类共享的通用基类定义。例如,类库可以定义一个抽象类,将其用作多个类库函数的参数,并要求使用该库的程序员通过创建派生类来提供自己的类实现。 抽象类也可以定义抽象方法。方法是将关键字 abstract 添加到方法的返回类型的前面。 1234public abstract class A{ public abstract void DoWork(int i);} 例子:抽象类不能实例化 123456789101112131415public abstract class PdaItem { public PdaItem (string name) { Name = name; } public virtual string Name { get; set; }}public class Program { public static void Main () { PdaItem item; // ERROR: Cannot create an instance of the abstract class //item = new PdaItem("Inigo Montoya"); //uncomment this line and it will not compile }} 例子:抽象成员。抽象成员没有实现的方法或者属性,其作用是强制所有派生类提供实现。 12345678910111213141516171819202122232425262728293031323334353637383940414243444546474849505152535455565758596061using System;// Define an abstract classpublic abstract class PdaItem { public PdaItem (string name) { Name = name; } public virtual string Name { get; set; } public abstract string GetSummary ();}public class Contact : PdaItem { public Contact (string name) : base (name) { } public override string Name { get { return FirstName + " " + LastName; } set { string[] names = value.Split (' '); // Error handling not shown. FirstName = names[0]; LastName = names[1]; } } public string FirstName { get; set; } public string LastName { get; set; } public string Address { get; set; } public override string GetSummary () { return string.Format ( "FirstName: {0}\n" + "LastName: {1}\n" + "Address: {2}", FirstName, LastName, Address); } // ...}public class Appointment : PdaItem { public Appointment (string name): base (name) { Name = name; } public DateTime StartDateTime { get; set; } public DateTime EndDateTime { get; set; } public string Location { get; set; } // ... public override string GetSummary () { return string.Format ( "Subject: {0}" + Environment.NewLine + "Start: {1}" + Environment.NewLine + "End: {2}" + Environment.NewLine + "Location: {3}", Name, StartDateTime, EndDateTime, Location); }} 抽象方法没有实现,所以方法定义后面是分号,而不是常规的方法块。抽象类的派生类必须实现所有抽象方法。当抽象类从基类继承虚方法时,抽象类可以使用抽象方法(abstract method )重写该虚方法。例如: 123456789101112131415161718192021// compile with: /target:librarypublic class D{ public virtual void DoWork(int i) { // Original implementation. }}public abstract class E : D{ public abstract override void DoWork(int i);}public class F : E{ public override void DoWork(int i) { // New implementation. }} 如果将 virtual 方法声明为 abstract,则该方法对于从抽象类继承的所有类而言仍然是虚方法。继承抽象方法的类无法访问方法的原始实现,因此在上一示例中,类 F 上的 DoWork 无法调用类 D 上的 DoWork。通过这种方式,抽象类可强制派生类向虚拟方法提供新的方法实现。 多态性 Polymorphism多态性常被视为自封装和继承之后,面向对象的编程的第三个支柱。Polymorphism(多态性)是一个希腊词,指“多种形态”,多态性具有两个截然不同的方面: 在运行时,在方法参数和集合或数组等位置,派生类的对象可以作为基类的对象处理。At run time, objects of a derived class may be treated as objects of a base class in places such as method parameters and collections or arrays. 发生此情况时,该对象的声明类型不再与运行时类型相同。When this occurs, the object’s declared type is no longer identical to its run-time type. 基类可以定义并实现虚方法,派生类可以重写这些方法,即派生类提供自己的定义和实现。在运行时,客户端代码调用该方法,CLR 查找对象的运行时类型,并调用虚方法的重写方法。因此,你可以在源代码中调用基类的方法,但执行该方法的派生类版本。 虚方法允许你以统一方式处理多组相关的对象。例如,假定你有一个绘图应用程序,允许用户在绘图图面上创建各种形状。你在编译时不知道用户将创建哪些特定类型的形状。但应用程序必须跟踪创建的所有类型的形状,并且必须更新这些形状以响应用户鼠标操作。你可以使用多态性通过两个基本步骤解决这一问题: 创建一个类层次结构,其中每个特定形状类均派生自一个公共基类。 使用虚方法通过对基类方法的单个调用来调用任何派生类上的相应方法。 首先,创建一个名为 Rectangle Shape 的基类,并创建一些派生类,例如 Triangle Circle、 和 。为 Draw Shape 类提供一个名为 的虚方法,并在每个派生类中重写该方法以绘制该类表示的特定形状。创建一个 List<Shape> 对象,并向该对象添加 Circle、Triangle 和 Rectangle。若要更新绘图图面,请使用 foreach 循环对该列表进行循环访问,并对其中的每个 Shape 对象调用 Draw 方法。虽然列表中的每个对象都具有声明类型 Shape ,但调用的将是运行时类型(该方法在每个派生类中的重写版本)。 1234567891011121314151617181920212223242526272829303132333435363738394041424344454647484950515253545556575859606162636465666768697071using System;using System.Collections.Generic;public class Shape { // A few example members public int X { get; private set; } public int Y { get; private set; } public int Height { get; set; } public int Width { get; set; } // Virtual method public virtual void Draw () { Console.WriteLine ("Performing base class drawing tasks"); }}class Circle : Shape { public override void Draw () { // Code to draw a circle... Console.WriteLine ("Drawing a circle"); base.Draw (); }}class Rectangle : Shape { public override void Draw () { // Code to draw a rectangle... Console.WriteLine ("Drawing a rectangle"); base.Draw (); }}class Triangle : Shape { public override void Draw () { // Code to draw a triangle... Console.WriteLine ("Drawing a triangle"); base.Draw (); }}class Program { static void Main (string[] args) { // Polymorphism at work #1: a Rectangle, Triangle and Circle // can all be used whereever a Shape is expected. No cast is // required because an implicit conversion exists from a derived // class to its base class. var shapes = new List<Shape> { new Rectangle (), new Triangle (), new Circle () }; // Polymorphism at work #2: the virtual method Draw is // invoked on each of the derived classes, not the base class. foreach (var shape in shapes) { shape.Draw (); } // Keep the console open in debug mode. Console.WriteLine ("Press any key to exit."); Console.ReadKey (); }}/* Output: Drawing a rectangle Performing base class drawing tasks Drawing a triangle Performing base class drawing tasks Drawing a circle Performing base class drawing tasks */ 在 C# 中,每个类型都是多态的,因为包括用户定义类型在内的所有类型都继承自 Object。 虚成员 Virtual Members当派生类从基类继承时,它会获得基类的所有方法、字段、属性和事件。派生类的设计器可以选择是否: 重写基类中的虚拟成员 继承最接近的基类方法而不重写它 定义隐藏基类实现的成员的新非虚实现 仅当基类成员声明为 virtual 或 abstract 时,派生类才能重写基类成员。派生成员必须使用 override 关键字显式指示该方法将参与虚调用。以下代码提供了一个示例:The following code provides an example: 12345678910111213141516public class BaseClass{ public virtual void DoWork() { } public virtual int WorkProperty { get { return 0; } }}public class DerivedClass : BaseClass{ public override void DoWork() { } public override int WorkProperty { get { return 0; } }} 字段不能是虚拟的,只有方法、属性、事件和索引器才可以是虚拟的。当派生类重写某个虚拟成员时,即使该派生类的实例被当作基类的实例访问,也会调用该成员。以下代码提供了一个示例: 12345DerivedClass B = new DerivedClass();B.DoWork(); // Calls the new method.BaseClass A = (BaseClass)B;A.DoWork(); // Also calls the new method. 虚方法和属性允许派生类扩展基类,而无需使用方法的基类实现。有关详细信息,请参阅使用 Override 和 New 关键字进行版本控制。接口提供另一种方式来定义将实现留给派生类的方法或方法集。有关详细信息,请参阅接口。 使用新成员隐藏基类成员 Hiding Base Class Members with New Members如果希望派生成员具有与基类中的成员相同的名称,但又不希望派生成员参与虚调用,则可以使用 new 关键字。new 关键字放置在要替换的类成员的返回类型之前。以下代码提供了一个示例: 12345678910111213141516171819public class BaseClass{ public void DoWork() { WorkField++; } public int WorkField; public int WorkProperty { get { return 0; } }}public class DerivedClass : BaseClass{ public new void DoWork() { WorkField++; } public new int WorkField; public new int WorkProperty { get { return 0; } }} 通过将派生类的实例强制转换为基类的实例,仍然可以从客户端代码访问隐藏的基类成员。例如: 12345DerivedClass B = new DerivedClass();B.DoWork(); // Calls the new method.BaseClass A = (BaseClass)B;A.DoWork(); // Calls the old method. 阻止派生类重写虚拟成员 Preventing Derived Classes from Overriding Virtual Members无论在虚拟成员和最初声明虚拟成员的类之间已声明了多少个类,虚拟成员永远都是虚拟的。 如果类 A 声明了一个虚拟成员,类 B 从 A 派生,类 C 从类 B 派生,则类 C 继承该虚拟成员,并且可以选择重写它,而不管类 B 是否为该成员声明了重写。以下代码提供了一个示例: 12345678public class A{ public virtual void DoWork() { }}public class B : A{ public override void DoWork() { }} 派生类可以通过将重写声明为 sealed 来停止虚拟继承。这需要在类成员声明中的 overridesealed 关键字前面放置 关键字。以下代码提供了一个示例: 1234public class C : B{ public sealed override void DoWork() { }} 在上一示例中,方法 DoWork 对从 C 派生的任何类都不再是虚方法。它对 C 的实例仍是虚拟的,即使它们转换为类型 B 或类型 A。使用 new 关键字可以将密封方法替换为派生类,如下方示例所示: 1234public class D : C{ public new void DoWork() { }} 在此情况下,如果在 D 中使用类型为 D 的变量调用 DoWork,被调用的将是新的 。如果使用类型为 C、B 或 A 的变量访问 D 的实例,对 DoWork 的调用将遵循虚拟继承的规则,即把这些调用传送到类 C 的实现。 从派生类访问基类虚拟成员 Accessing Base Class Virtual Members from Derived Classes已替换或重写某个方法或属性的派生类仍然可以使用基关键字访问基类的该方法或属性。以下代码提供了一个示例: 1234567891011121314public class Base{ public virtual void DoWork() {/*...*/ }}public class Derived : Base{ public override void DoWork() { //Perform Derived's work here //... // Call DoWork on base class base.DoWork(); }} 有关详细信息,请参阅 base。 System.Object 基类System.Object在.Net中是所有类型的基类,任何类型都直接或间接地继承自System.Object。没有指定基类的类型都默认继承于System.Object。 由于所有的类型都继承于System.Object。因此,所有的类型都具有下面这些特性: 方法名 说明 GetType() 获取对象的类型. ToString() 获取对象的字符串信息,默认返回对象带命名空间的全名。 public virtual bool Equals(Object obj); 确定指定的对象是否等于当前对象。 public static bool Equals(Object objA,Object objB); 确定指定的对象实例是否被视为相等。 public static bool ReferenceEquals(Object objA,Object objB); 确定指定的 Object 实例是否是相同的实例。 GetHashCode() 获取对象的值的散列码。 Finalize() 在垃圾回收时,进行资源管理。 MemberwiseClone() 对象实例的浅拷贝。 is检查对象是否与给定类型兼容,或(从 C# 7 开始)针对某个模式测试表达式。 类型兼容性测试Testing for type compatibilityis 关键字在运行时评估类型兼容性。它确定对象实例或表达式结果是否可转换为指定类型。语法如下: 1expr is type 其中 expr 是计算结果为某个类型的实例的表达式,而 type 是 expr 结果要转换到的类型的名称。如果 expr 非空,并且通过计算表达式得出的对象可转换为 type,则 is 语句为 true;否则返回 false。 例如,以下代码确定 obj 是否可转换为 Person 类型的实例: 123if (obj is Person) { // Do something if obj is a Person.} 如果满足以下条件,则 is 语句为 true: expr 是与 type 具有相同类型的一个实例。 expr 是派生自 type 的类型的一个实例。换言之,expr 结果可以向上转换为 type 的一个实例。 expr 具有属于 type 的一个基类的编译时类型,expr 还具有属于 type 或派生自 type 的运行时类型。变量的编译时类型是其声明中定义的变量类型。变量的运行时类型是分配给该变量的实例类型。 expr 是实现 type 接口的类型的一个实例。 下例表明,对于所有这些转换,is 表达式的计算结果都为 true。 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051using System;public class Class1 : IFormatProvider{ public object GetFormat(Type t) { if (t.Equals(this.GetType())) return this; return null; }}public class Class2 : Class1{ public int Value { get; set; }}public class Example{ public static void Main() { var cl1 = new Class1(); Console.WriteLine(cl1 is IFormatProvider); Console.WriteLine(cl1 is Object); Console.WriteLine(cl1 is Class1); Console.WriteLine(cl1 is Class2); Console.WriteLine(); var cl2 = new Class2(); Console.WriteLine(cl2 is IFormatProvider); Console.WriteLine(cl2 is Class2); Console.WriteLine(cl2 is Class1); Console.WriteLine(); Class1 cl = cl2; Console.WriteLine(cl is Class1); Console.WriteLine(cl is Class2); }}// The example displays the following output:// True// True// True// False//// True// True// True//// True// True 从 C# 7 开始,可以使用类型模式的模式匹配来编写代码,代码使用 is 语句更为简洁。 利用 is 的模式匹配 Pattern matching with is从 C# 7 开始,is 和 switch 语句支持模式匹配。is 关键字支持以下模式: 类型模式,用于测试表达式是否可转换为指定类型,如果可以,则将其转换为该类型的一个变量。 常量模式,用于测试表达式计算结果是否为指定的常数值。[Constant pattern] var 模式,始终成功的匹配,可将表达式的值绑定到新局部变量。[var pattern] 类型模式 Type pattern使用类型模式执行模式匹配时,is 会测试表达式是否可转换为指定类型,如果可以,则将其转换为该类型的一个变量。它是 is 语句的直接扩展,可执行简单的类型计算和转换。is 类型模式的一般形式为: 1expr is type varname 其中 expr 是计算结果为某个类型的实例的表达式,type 是 expr 结果要转换到的类型的名称,varname 是 expr 结果要转换到的对象(如果 is 测试为 true)。 如果以下任一条件成立,则 is 表达式为 true: expr 是与 type 具有相同类型的一个实例。 expr 是派生自 type 的类型的一个实例。换言之,expr 结果可以向上转换为 type 的一个实例。 expr 具有属于 type 的一个基类的编译时类型,expr 还具有属于 type 或派生自 type 的运行时类型。变量的编译时类型是其声明中定义的变量类型。变量的运行时类型是分配给该变量的实例类型。 expr 是实现 type 接口的类型的一个实例。 如果 exp 为 true,且 is 与 if 语句一起使用,则会分配 varname,并且其仅在 if 语句中具有局部范围。 下列示例使用 is 类型模式为类型的 IComparable.CompareTo(Object) 方法提供实现。 12345678910111213141516using System;public class Employee : IComparable{ public String Name { get; set; } public int Id { get; set; } public int CompareTo(Object o) { if (o is Employee e) { return Name.CompareTo(e.Name); } throw new ArgumentException("o is not an Employee object."); }} 如果没有模式匹配,则可能按以下方式编写此代码。使用类型模式匹配无需测试转换结果是否为 null,从而生成更紧凑易读的代码。 1234567891011121314151617using System;public class Employee : IComparable{ public String Name { get; set; } public int Id { get; set; } public int CompareTo(Object o) { var e = o as Employee; if (e == null) { throw new ArgumentException("o is not an Employee object."); } return Name.CompareTo(e.Name); }} 常量模式 Constant pattern使用常量模式执行模式匹配时,is 会测试表达式结果是否等于指定常量。在 C# 6 和更低版本中,switch 语句支持常量模式从。C# 7 开始,is 语句也支持常量模式。语法为: 1expr is constant 其中 expr 是要计算的表达式,constant 是要测试的值。 constant 可以是以下任何常数表达式: 一个文本值。A literal value. 已声明 const 变量的名称。The name of a declared const variable. 一个枚举常量。An enumeration constant. 常数表达式的计算方式如下: 如果 expr 和 constant 均为整型类型,则 C# 相等运算符确定表示式是否返回 true(即,是否为 expr == constant)。 否则,由对静态 Object.Equals(expr, constant) 方法的调用来确定表达式的值。 下例同时使用了类型模式和常量模式来测试对象是否为 Dice 实例,如果是,则确定骰子的值是否为 6。 1234567891011121314151617181920212223242526272829303132333435using System;public class Dice{ Random rnd = new Random(); public Dice() { } public int Roll() { return rnd.Next(1, 7); }}class Program{ static void Main(string[] args) { var d1 = new Dice(); ShowValue(d1); } private static void ShowValue(object o) { const int HIGH_ROLL = 6; if (o is Dice d && d.Roll() is HIGH_ROLL) Console.WriteLine($"The value is {HIGH_ROLL}!"); else Console.WriteLine($"The dice roll is not a {HIGH_ROLL}!"); }}// The example displays output like the following:// The value is 6! var 模式 var pattern具有 var 模式的模式匹配始终成功。 1expr is var varname 其中,expr 的值始终分配给名为 varname 的局部变量。varname 是一个与 expr 具有相同类型的静态变量。下例使用 var 模式向名为 obj 的变量分配表达式。然后,显示 obj 的值和类型。 1234567891011121314151617181920212223242526272829303132333435363738394041424344454647using System;class Program{ static void Main() { object[] items = { new Book("The Tempest"), new Person("John") }; foreach (var item in items) { if (item is var obj) Console.WriteLine($"Type: {obj.GetType().Name}, Value: {obj}"); } }}class Book{ public Book(string title) { Title = title; } public string Title { get; set; } public override string ToString() { return Title; }}class Person{ public Person(string name) { Name = name; } public string Name { get; set; } public override string ToString() { return Name; }}// The example displays the following output:// Type: Book, Value: The Tempest// Type: Person, Value: John 请注意,如果 expr 为 null,则 is 表达式仍为 true 并向 varname 分配 null。 as可以使用 as 运算符在符合的引用类型或可以为 null 的类型之间执行某些类型的转换。以下代码显示一个示例。 1234567891011121314151617181920212223using System;class csrefKeywordsOperators { class Base { public override string ToString () { return "Base"; } } class Derived : Base { } class Program { static void Main () { Derived d = new Derived (); Base b = d as Base; if (b != null) { Console.WriteLine (b.ToString ()); } } }} as 运算符类似于转换运算。但是,如果无法进行转换,则 as 会返回 null,而不是引发异常。请看下面的示例: 1expression as type 该代码等效于以下表达式,但 expression 变量仅进行一次计算。 1expression is type ? (type)expression : (type)null 请注意,as 运算符仅执行引用转换、可以为 null 的转换和装箱转换。as 运算符无法执行其他转换,例如用户定义的转换,应使用转换表达式执行此转换。 1234567891011121314151617181920212223242526272829303132333435using System;class ClassA { }class ClassB { }class MainClass { static void Main () { object[] objArray = new object[6]; objArray[0] = new ClassA (); objArray[1] = new ClassB (); objArray[2] = "hello"; objArray[3] = 123; objArray[4] = 123.4; objArray[5] = null; for (int i = 0; i < objArray.Length; ++i) { string s = objArray[i] as string; Console.Write ("{0}:", i); if (s != null) { Console.WriteLine ("'" + s + "'"); } else { Console.WriteLine ("not a string"); } } }}/*Output:0:not a string1:not a string2:'hello'3:not a string4:not a string5:not a string*/ 结尾]]></content>
<tags>
<tag>C#</tag>
<tag>C#本质论</tag>
<tag>Essential C#</tag>
</tags>
</entry>
<entry>
<title><![CDATA[C#本质论笔记 第5章 类]]></title>
<url>%2F2017%2F12%2F22%2FCSharp%E6%9C%AC%E8%B4%A8%E8%AE%BA%E7%AC%94%E8%AE%B0-%E7%AC%AC5%E7%AB%A0-%E7%B1%BB%2F</url>
<content type="text"><![CDATA[类的定义和实例化类和对象都能关联数据,将类想象成模具,将对象想象成根据模具浇筑出来的零件,可以更好理解这一点。 使用类,可以通过组合其他类型的变量、方法和事件创建自己的自定义类型。A class is a construct that enables you to create your own custom types by grouping together variables of other types, methods and events. 类好比是蓝图。A class is like a blueprint. 它定义类型的数据和行为。It defines the data and behavior of a type. 如果类未声明为静态,客户端代码就可以通过创建分配给变量的_对象_或_实例_来使用该类。If the class is not declared as static, client code can use it by creating objects or instances which are assigned to a variable. 变量会一直保留在内存中,直至对变量的所有引用超出范围为止。The variable remains in memory until all references to it go out of scope. 超出范围时,CLR 将对其进行标记,以便用于垃圾回收。At that time, the CLR marks it as eligible for garbage collection. 如果类声明为静态,则内存中只有一个副本,且客户端代码只能通过类本身,而不是实例变量来访问它。If the class is declared as static, then only one copy exists in memory and client code can only access it through the class itself, not an instance variable. 有关详细信息,请参阅静态类和静态类成员。For more information, see Static Classes and Static Class Members. 与结构不同,类支持_继承_,这是面向对象的编程的一个基本特点。Unlike structs, classes support inheritance, a fundamental characteristic of object-oriented programming. 有关详细信息,请参阅继承。 例子,包含类的定义、实例化、字段声明、字段访问和方法: 1234567891011121314151617181920212223242526272829using System;public class Program { public static void Main () { Employee employee1 = new Employee (); Employee employee2; employee2 = new Employee (); employee1.FirstName = "Inigo"; employee1.LastName = "Montoya"; employee1.Salary = "Too Little"; IncreaseSalary (employee1); Console.WriteLine ("{0}: {1}", employee1.GetName (), employee1.Salary); } static void IncreaseSalary (Employee employee) { employee.Salary = "Enough to survive on"; }}class Employee { public string FirstName; public string LastName; public string Salary = "Not enough"; public string GetName () { return FirstName + " " + LastName; }} <> this 关键字this 关键字指代类的当前实例。在此示例中,this 用于限定类似名称隐藏的 Employee 类成员、name 和 alias。In this example, this is used to qualify the Employee class members, name and alias, which are hidden by similar names. 它还用于将某个对象传递给属于其他类的方法 CalcTax。 1234567891011121314151617181920212223242526272829303132333435363738394041424344454647using System;class Employee { private string name; private string alias; private decimal salary = 3000.00m; // Constructor: public Employee (string name, string alias) { // Use this to qualify the fields, name and alias: // 如果不用 this 指定类成员 name,那么 name = name; 只是自己给自己赋值, // 最终,类成员 name 未被赋值,为 null this.name = name; this.alias = alias; } // Printing method: public void printEmployee () { Console.WriteLine ("Name: {0}\nAlias: {1}", name, alias); // Passing the object to the CalcTax method by using this: Console.WriteLine ("Taxes: {0:C}", Tax.CalcTax (this)); } public decimal Salary { get { return salary; } }}class Tax { public static decimal CalcTax (Employee E) { return 0.08m * E.Salary; }}class MainClass { static void Main () { // Create objects: Employee E1 = new Employee ("Mingda Pan", "mpan"); // Display results: E1.printEmployee (); }}/*Output: Name: Mingda Pan Alias: mpan Taxes: $240.00 */ 高级主题:存储和载入文件将数据持久化存储到文件 示例:首先,实例化一个FileStream对象,将它与一个以员工的全名命名的文件对应起来。FileMode.Create参数指明,如果对应的文件不存在就创建一个;如果文件存在,就覆盖它。接着创建一个StreamWriter类。StreamWriter类负责将文本写入FileStream。数据是用WriteLine()方法写入的,就像向控制台写入一样。 1234567891011121314151617181920212223242526272829303132333435363738394041424344454647484950using System;// IO namespaceusing System.IO;class Employee { public string FirstName; public string LastName; public string Salary; public void Save () { DataStorage.Store (this); }}class DataStorage { // Save an employee object to a file // named with the Employee name. // Error handling not shown. public static void Store (Employee employee) { // Instantiate a FileStream using FirstNameLastName.dat // for the filename. FileMode.Create will force // a new file to be created or override an // existing file. FileStream stream = new FileStream ( employee.FirstName + employee.LastName + ".dat", FileMode.Create); // Create a StreamWriter object for writing text // into the FileStream StreamWriter writer = new StreamWriter (stream); // Write all the data associated with the employee. writer.WriteLine (employee.FirstName); writer.WriteLine (employee.LastName); writer.WriteLine (employee.Salary); // Close the StreamWriter and its Stream. writer.Close (); // Automatically closes the stream stream.Close(); }}class SaveDataToFile { static void Main (string[] args) { Employee employee = new Employee (); employee.FirstName = "Jon"; employee.LastName = "Snow"; employee.Salary = "$6000.00"; employee.Save (); }} 写入操作完成后,FileStream和StreamWriter都需要关闭,避免它们在等待垃圾回收器运行期间,处于“不确定性打开”的状态。上述代码没有任何错误处理机制,如果引发异常,两个Close()方法都不会执行。 从文件中获取数据读取数据与存储数据相反,它使用StreamReader而不是StreamWriter。同样的,一旦数据读取完毕,就要在FileStream和StreamReader上调用Close()方法。 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101using System;using System.IO;public class Program { public static void Main () { Employee employee1; Employee employee2 = new Employee (); employee2.SetName ("Inigo", "Montoya"); employee2.Save (); // Modify employee2 after saving. IncreaseSalary (employee2); // Load employee1 from the saved version of employee2 employee1 = DataStorage.Load ("Inigo", "Montoya"); Console.WriteLine ( "{0}: {1}", employee1.GetName (), employee1.Salary); } static void IncreaseSalary (Employee employee) { employee.Salary = "Enough to survive on"; }}class Employee { public string FirstName; public string LastName; public string Salary; public string GetName () { return FirstName + " " + LastName; } public void SetName (string newFirstName, string newLastName) { this.FirstName = newFirstName; this.LastName = newLastName; Console.WriteLine ("Name changed to '{0}'", this.GetName ()); } public void Save () { DataStorage.Store (this); }}class DataStorage { // Save an employee object to a file // named with the Employee name. // Error handling not shown. public static void Store (Employee employee) { // Instantiate a FileStream using FirstNameLastName.dat // for the filename. FileMode.Create will force // a new file to be created or override an // existing file. FileStream stream = new FileStream ( employee.FirstName + employee.LastName + ".dat", FileMode.Create); // Create a StreamWriter object for writing text // into the FileStream StreamWriter writer = new StreamWriter (stream); // Write all the data associated with the employee. writer.WriteLine (employee.FirstName); writer.WriteLine (employee.LastName); writer.WriteLine (employee.Salary); // Close the StreamWriter and its Stream. writer.Close (); // Automatically closes the stream stream.Close (); } public static Employee Load (string firstName, string lastName) { Employee employee = new Employee (); // Instantiate a FileStream using FirstNameLastName.dat // for the filename. FileMode.Open will open // an existing file or else report an error. FileStream stream = new FileStream ( firstName + lastName + ".dat", FileMode.Open); // Create a SteamReader for reading text from the file. StreamReader reader = new StreamReader (stream); // Read each line from the file and place it into // the associated property. employee.FirstName = reader.ReadLine (); employee.LastName = reader.ReadLine (); employee.Salary = reader.ReadLine (); // Close the StreamReader and its Stream. reader.Close (); // Automatically closes the stream stream.Close (); return employee; }} 访问修饰符 Access Modifiers访问修饰符是关键字,用于指定成员或类型已声明的可访问性。Access modifiers are keywords used to specify the declared accessibility of a member or a type. 本部分介绍四个访问修饰符:This section introduces the four access modifiers: 公用 public 受保护 protected 内部 internal 专用 private 可以使用访问修饰符指定以下六个可访问性级别:The following six accessibility levels can be specified using the access modifiers: public:访问不受限制。public: Access is not restricted. protected:访问限于包含类或派生自包含类的类型。protected: Access is limited to the containing class or types derived from the containing class. internal:访问限于当前程序集。internal: Access is limited to the current assembly. protected internal: 访问仅限于当前程序集或从包含类派生的类型。protected internal: Access is limited to the current assembly or types derived from the containing class. private:访问限于包含类。private: Access is limited to the containing type. private protected: 访问被限制为包含的类或从包含当前程序集中的类派生的类型。 示例:使用private访问修饰符。下例为了隐藏Password字段,禁止从它包容类的外部访问,使用private访问修饰符替代public,这样就无法从Program类中访问Password字段了。 12345678910111213141516171819202122232425262728293031323334public class Program { public static void Main () { Employee employee = new Employee (); employee.FirstName = "Inigo"; employee.LastName = "Montoya"; // Password is private, so it cannot be // accessed from outside the class. // Console.WriteLine( // ("Password = {0}", employee.Password); } // ...}class Employee { public string FirstName; public string LastName; public string Salary; private string Password; private bool IsAuthenticated; public bool Logon (string password) { if (Password == password) { IsAuthenticated = true; } return IsAuthenticated; } public bool GetIsAuthenticated () { return IsAuthenticated; } // ...} 属性属性是一种成员,它提供灵活的机制来读取、写入或计算私有字段的值。A property is a member that provides a flexible mechanism to read, write, or compute the value of a private field. 属性可用作公共数据成员,但它们实际上是称为访问器的特殊方法。Properties can be used as if they are public data members, but they are actually special methods called accessors. 这使得可以轻松访问数据,还有助于提高方法的安全性和灵活性。 属性结合了字段和方法的多个方面。Properties combine aspects of both fields and methods. 对于对象的用户来说,属性似乎是一个字段,访问属性需要相同的语法。To the user of an object, a property appears to be a field, accessing the property requires the same syntax. 对于类的实现者来说,属性是一两个代码块,表示 get 访问器和/或 set 访问器。To the implementer of a class, a property is one or two code blocks, representing a get accessor and/or a set accessor. 读取属性时,执行 get 访问器的代码块;向属性赋予新值时,执行 set 访问器的代码块。The code block for the get accessor is executed when the property is read; the code block for the set accessor is executed when the property is assigned a new value. 将不带 set 访问器的属性视为只读。A property without a set accessor is considered read-only. 将不带 get 访问器的属性视为只写。A property without a get accessor is considered write-only. 将具有以上两个访问器的属性视为读写。A property that has both accessors is read-write. 与字段不同,属性不会被归类为变量。Unlike fields, properties are not classified as variables. 因此,不能将属性作为 ref 或 out 参数传递。 示例Example此示例演示实例、静态和只读属性。This example demonstrates instance, static, and read-only properties. 它接收通过键盘键入的员工姓名,按 1 递增 NumberOfEmployees,并显示员工姓名和编号。 12345678910111213141516171819202122232425262728293031323334353637public class Employee { public static int NumberOfEmployees; private static int counter; private string name; // A read-write instance property: public string Name { get { return name; } set { name = value; } } // A read-only static property: public static int Counter { get { return counter; } } // A Constructor: public Employee () { // Calculate the employee's number: counter = ++counter + NumberOfEmployees; }}class TestEmployee { static void Main () { Employee.NumberOfEmployees = 107; Employee e1 = new Employee (); e1.Name = "Claude Vige"; System.Console.WriteLine ("Employee number: {0}", Employee.Counter); System.Console.WriteLine ("Employee name: {0}", e1.Name); }}/* Output: Employee number: 108 Employee name: Claude Vige*/ 属性的声明1234567891011121314151617181920212223242526272829303132333435363738using System;using System.IO;public class Program { public static void Main () { Employee employee = new Employee (); // Call the FirstName property's setter. employee.FirstName = "Inigo"; // Call the FirstName property's getter. System.Console.WriteLine (employee.FirstName); }}class Employee { // FirstName property public string FirstName { get { return _FirstName; } set { _FirstName = value; } } private string _FirstName; // LastName property public string LastName { get { return _LastName; } set { _LastName = value; } } private string _LastName;} 自动实现的属性从C# 3.0开始,属性语法有了简化版本,允许在声明属性时,不添加取值或赋值方法,也不声明任何支持地段。一切都将自动实现。 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354using System.IO;using System;public class Program { public static void Main () { Employee employee1 = new Employee (); Employee employee2 = new Employee (); // Call the FirstName property's setter. employee1.FirstName = "Inigo"; // Call the FirstName property's getter. System.Console.WriteLine (employee1.FirstName); // Assign an auto-implemented property employee2.Title = "Computer Nerd"; employee1.Manager = employee2; // Print employee1's manager's title. System.Console.WriteLine (employee1.Manager.Title); }}class Employee { // FirstName property public string FirstName { get { return _FirstName; } set { _FirstName = value; } } private string _FirstName; // LastName property public string LastName { get { return _LastName; } set { _LastName = value; } } private string _LastName; // Title property public string Title { get; set; } // Manager property public Employee Manager { get; set; }} 具有支持字段的属性 Properties with backing fields有一个实现属性的基本模式,该模式使用私有支持字段来设置和检索属性值。One basic pattern for implementing a property involves using a private backing field for setting and retrieving the property value. get 访问器返回私有字段的值,set 访问器在向私有字段赋值之前可能会执行一些数据验证。The get accessor returns the value of the private field, and the set accessor may perform some data validation before assigning a value to the private field. 这两个访问器还可以在存储或返回数据之前对其执行某些转换或计算。Both accessors may also perform some conversion or computation on the data before it is stored or returned. 下面的示例阐释了此模式。The following example illustrates this pattern. 在此示例中,TimePeriod 类表示时间间隔。In this example, the TimePeriod class represents an interval of time. 在内部,该类将时间间隔以秒为单位存储在名为 seconds 的私有字段中。Internally, the class stores the time interval in seconds in a private field named seconds. 名为 Hours 的读-写属性允许客户以小时为单位指定时间间隔。A read-write property named Hours allows the customer to specify the time interval in hours. get 和 set 访问器都会执行小时与秒之间的必要转换。Both the get and the set accessors perform the necessary conversion between hours and seconds. 此外,set 访问器还会验证数据,如果小时数无效,则引发 ArgumentOutOfRangeException。 1234567891011121314151617181920212223242526272829using System;class TimePeriod { private double seconds; public double Hours { get { return seconds / 3600; } set { if (value < 0 || value > 24) throw new ArgumentOutOfRangeException ( $"{nameof(value)} must be between 0 and 24."); seconds = value * 3600; } }}class Program { static void Main () { TimePeriod t = new TimePeriod (); // The property assignment causes the 'set' accessor to be called. t.Hours = 24; // Retrieving the property causes the 'get' accessor to be called. Console.WriteLine ($"Time in hours: {t.Hours}"); }}// The example displays the following output:// Time in hours: 24 表达式主体定义 Expression body definitions属性访问器通常由单行语句组成,这些语句只分配或只返回表达式的结果。Property accessors often consist of single-line statements that just assign or return the result of an expression. 可以将这些属性作为 expression-bodied 成员来实现。You can implement these properties as expression-bodied members. => 符号后跟用于为属性赋值或从属性中检索值的表达式,即组成了表达式主体定义。Expression body definitions consist of the => symbol followed by the expression to assign to or retrieve from the property. 从 C# 6 开始,只读属性可以将 get 访问器作为 expression-bodied 成员实现。Starting with C# 6, read-only properties can implement the get accessor as an expression-bodied member. 在这种情况下,既不使用 get 访问器关键字,也不使用 return 关键字。In this case, neither the get accessor keyword nor the return keyword is used. 下面的示例将只读 Name 属性作为 expression-bodied 成员实现。The following example implements the read-only Name property as an expression-bodied member. 12345678910111213141516171819202122using System;public class Person { private string firstName; private string lastName; public Person (string first, string last) { firstName = first; lastName = last; } public string Name => $"{firstName} {lastName}";}public class Example { public static void Main () { var person = new Person ("Isabelle", "Butts"); Console.WriteLine (person.Name); }}// The example displays the following output:// Isabelle Butts 从 C# 7 开始,get 和 set 访问器都可以作为 expression-bodied 成员实现。Starting with C# 7, both the get and the set accessor can be implemented as expression-bodied members. 在这种情况下,必须使用 get 和 set 关键字。In this case, the get and set keywords must be present. 下面的示例阐释如何为这两个访问器使用表达式主体定义。The following example illustrates the use of expression body definitions for both accessors. 请注意,return 关键字不与 get 访问器搭配使用。Note that the return keyword is not used with the get accessor. 123456789101112131415161718192021222324252627282930using System;public class SaleItem { string name; decimal cost; public SaleItem (string name, decimal cost) { this.name = name; this.cost = cost; } public string Name { get => name; set => name = value; } public decimal Price { get => cost; set => cost = value; }}class Program { static void Main (string[] args) { var item = new SaleItem ("Shoes", 19.95m); Console.WriteLine ($"{item.Name}: sells for {item.Price:C2}"); }}// The example displays output like the following:// Shoes: sells for $19.95 只读和只写属性省略 set 访问器可使属性为只读,省略 get 访问器可使属性为只写。只读属性对于任何赋值气度都会造成编译错误。例如,下例中是Id为只读: 12345678910111213141516171819202122232425262728class Program { static void Main () { Employee employee1 = new Employee (); employee1.Initialize (42); // ERROR: Property or indexer 'Employee.Id' // cannot be assigned to -- it is read-only //employee1.Id = "490"; //will not compile if you uncomment this line }}class Employee { public void Initialize (int id) { // Use field because Id property has no setter, // it is read-only. _Id = id.ToString (); } // ... // Id property declaration public string Id { get { return _Id; } // No setter provided. } private string _Id;} 上例中采用Employee构造函数(而不是属性)对字段进行赋值(_Id = id)。如果通过属性来赋值,会造成编译错误。 限制访问器可访问性(C# 编程指南)Restricting Accessor Accessibility (C# Programming Guide)属性或索引器的 get 和 set 部分称为访问器。The get and set portions of a property or indexer are called accessors. 默认情况下,这些访问器具有相同的可见性或访问级别:其所属属性或索引器的可见性或访问级别。By default these accessors have the same visibility, or access level: that of the property or indexer to which they belong. 有关详细信息,请参阅可访问性级别。For more information, see accessibility levels. 不过,有时限制对其中某个访问器的访问是有益的。However, it is sometimes useful to restrict access to one of these accessors. 通常是在保持 get 访问器可公开访问的情况下,限制 set 访问器的可访问性。Typically, this involves restricting the accessibility of the set accessor, while keeping the get accessor publicly accessible. 可以为get或set部分指定访问修饰符(但不能为两者都指定),从而覆盖为属性声明指定的访问修饰符。例如: 12345678910111213141516171819202122232425262728293031class Program { static void Main () { Employee employee1 = new Employee (); employee1.Initialize (42); // ERROR: The property or indexer 'Employee.Id' // cannot be used in this context because the set // accessor is inaccessible //employee1.Id = "490"; //will not compile if you uncomment this line }}class Employee { public void Initialize (int id) { // Set Id property Id = id.ToString (); } // ... // Id property declaration public string Id { get { return _Id; } // Providing an access modifier is in C# 2.0 // and higher only private set { _Id = value; } } private string _Id;} 为赋值方法指定private修饰符后,属性对于处Employee之外的其他类来说是只读的。在Employee类内部,属性是可读/可写的,所以可在构造器中对属性进行赋值。为取值方法或赋值方法指定访问修饰符时,注意该访问修饰符的“限制性”必须比应用于整个属相的访问修饰符更“严格”。例如,属性声明为较为严格的private,但将它的赋值方法声明为较宽松的public,就会发生编译错误。 属性作为虚字段使用下例中,Name属性的实现: 12345678910111213141516171819202122232425262728293031323334353637383940414243444546474849505152535455565758596061626364656667using System;using System.IO;public class Program { public static void Main () { Employee employee1 = new Employee (); employee1.Name = "Inigo Montoya"; System.Console.WriteLine (employee1.Name); // ... }}class Employee { // FirstName property public string FirstName { get { return _FirstName; } set { _FirstName = value; } } private string _FirstName; // LastName property public string LastName { get { return _LastName; } set { _LastName = value; } } private string _LastName; // ... // Name property public string Name { get { return FirstName + " " + LastName; } set { // Split the assigned value into // first and last names. string[] names; names = value.Split (new char[] { ' ' }); if (names.Length == 2) { FirstName = names[0]; LastName = names[1]; } else { // Throw an exception if the full // name was not assigned. throw new System.ArgumentException ( string.Format ( "Assigned value '{0}' is invalid", value)); } } } // Title property public string Title { get; set; } // Manager property public Employee Manager { get; set; }} 构造器 Constructors (构造函数)每当创建类或结构时,将会调用其构造函数。Whenever a class or struct is created, its constructor is called. 类或结构可能具有采用不同参数的多个构造函数。A class or struct may have multiple constructors that take different arguments. 使用构造函数,程序员能够设置默认值、限制实例化,并编写灵活易读的代码。Constructors enable the programmer to set default values, limit instantiation, and write code that is flexible and easy to read. 有关详细信息和示例,请参阅使用构造函数和实例构造函数。 构造函数声明与调用1234567891011121314class Employee{ // Employee constructor public Employee(string firstName, string lastName) { FirstName = firstName; LastName = lastName; } public string FirstName { get; set; } public string LastName { get; set; } public string Salary { get; set; } //...} 构造函数是一种方法,其名称与其类名`完全相同。 其方法签名仅包含方法名称和其参数列表;它没有返回类型`。构造函数是“运行时”用来初始化对象实例的方法。在此例中,构造函数以员工的名字和姓氏作为参数,允许程序员在实例化Employee对象时制定这些参数的值。如下例: 123456789101112131415public class Program{ public static void Main() { Employee employee; employee = new Employee("Inigo", "Montoya"); employee.Salary = "Too Little"; System.Console.WriteLine( "{0} {1}: {2}", employee.FirstName, employee.LastName, employee.Salary); }} 如果某个构造函数可以作为单个语句实现,则可以使用表达式主体定义。If a constructor can be implemented as a single statement, you can use an expression body definition. 以下示例定义 Location 类,其构造函数具有一个名为“name”的字符串参数。The following example defines a Location class whose constructor has a single string parameter named name. 表达式主体定义给 locationName 字段分配参数。The expression body definition assigns the argument to the locationName field. 123456789101112public class Location{ private string locationName; public Location(string name) => locationName = name; public string Name { get => locationName; set => locationName = value; }} 默认构造器 Default constructors如果没有为类提供构造函数,默认情况下,C# 将创建一个会实例化对象并将成员变量设置为默认值的构造函数,如默认值表中所列。If you don’t provide a constructor for your class, C# creates one by default that instantiates the object and sets member variables to the default values as listed in the Default Values Table. 如果没有为结构提供构造函数,C# 将依赖于隐式默认构造函数,自动将值类型的每个字段初始化为其默认值,如默认值表中所列。If you don’t provide a constructor for your struct, C# relies on an implicit default constructor to automatically initialize each field of a value type to its default value as listed in the Default Values Table. 有关详细信息和示例,请参阅实例构造函数。 对象初始化器 Object Initializer可以使用对象初始值设定项(对象初始化器)以声明方式初始化类型对象,而无需显式调用类型的构造函数。You can use object initializers to initialize type objects in a declarative manner without explicitly invoking a constructor for the type. 以下示例演示如何将对象初始化器用于命名对象。The following examples show how to use object initializers with named objects. 编译器通过首先访问默认实例构造函数,然后处理成员初始化来处理对象初始值设定项。The compiler processes object initializers by first accessing the default instance constructor and then processing the member initializations. 因此,如果默认构造函数在类中声明为 private,则需要公共访问的对象初始值设定项将失败。Therefore, if the default constructor is declared as private in the class, object initializers that require public access will fail. 如果要定义匿名类型,则必须使用对象初始化器。You must use an object initializer if you’re defining an anonymous type. 有关详细信息,请参阅如何:在查询中返回元素属性的子集。For more information, see How to: Return Subsets of Element Properties in a Query. 下面的示例演示如何使用对象初始化器初始化新的 StudentName 类型。 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869public class Program { public static void Main () { // Declare a StudentName by using the constructor that has two parameters. StudentName student1 = new StudentName ("Craig", "Playstead"); // Make the same declaration by using an object initializer and sending // arguments for the first and last names. The default constructor is // invoked in processing this declaration, not the constructor that has // two parameters. StudentName student2 = new StudentName { FirstName = "Craig", LastName = "Playstead", }; // Declare a StudentName by using an object initializer and sending // an argument for only the ID property. No corresponding constructor is // necessary. Only the default constructor is used to process object // initializers. StudentName student3 = new StudentName { ID = 183 }; // Declare a StudentName by using an object initializer and sending // arguments for all three properties. No corresponding constructor is // defined in the class. StudentName student4 = new StudentName { FirstName = "Craig", LastName = "Playstead", ID = 116 }; System.Console.WriteLine (student1.ToString ()); System.Console.WriteLine (student2.ToString ()); System.Console.WriteLine (student3.ToString ()); System.Console.WriteLine (student4.ToString ()); } // Output: // Craig 0 // Craig 0 // 183 // Craig 116}public class StudentName { // The default constructor has no parameters. The default constructor // is invoked in the processing of object initializers. // You can test this by changing the access modifier from public to // private. The declarations in Main that use object initializers will // fail. public StudentName () { } // The following constructor has parameters for two of the three // properties. public StudentName (string first, string last) { FirstName = first; LastName = last; } // Properties. public string FirstName { get; set; } public string LastName { get; set; } public int ID { get; set; } public override string ToString () { return FirstName + " " + ID; }} 下面的示例演示如何使用集合初始化器来初始化 StudentName 类型的集合。The following example shows how to initialize a collection of StudentName types by using a collection initializer. 请注意,集合初始值设定项是一系列由逗号分隔的对象初始值设定项。Note that a collection initializer is a series of comma-separated object initializers. 1234567List<StudentName> students = new List<StudentName>(){ new StudentName {FirstName="Craig", LastName="Playstead", ID=116}, new StudentName {FirstName="Shu", LastName="Ito", ID=112}, new StudentName {FirstName="Gretchen", LastName="Rivas", ID=113}, new StudentName {FirstName="Rajesh", LastName="Rotti", ID=114}}; 终结器 Finalizers终结器用于析构类的实例。Finalizers are used to destruct instances of classes. 备注 Remarks 无法在结构中定义终结器。Finalizers cannot be defined in structs. 它们仅用于类。They are only used with classes. 一个类只能有一个终结器。A class can only have one finalizer. 不能继承或重载终结器。Finalizers cannot be inherited or overloaded. 不能手动调用终结器。Finalizers cannot be called. 可以自动调用它们。They are invoked automatically. 终结器不使用修饰符或参数。 例如,以下是类 Car 的终结器声明。For example, the following is a declaration of a finalizer for the Car class. 1234567class Car{ ~Car() // destructor { // cleanup statements... }} 终结器也可以作为表达式主体定义实现,如下面的示例所示。A finalizer can also be implemented as an expression body definition, as the following example shows. 12345678using System;public class Destroyer{ public override string ToString() => GetType().Name; ~Destroyer() => Console.WriteLine($"The {ToString()} destructor is executing.");} 程序员无法控制何时调用终结器,因为这由垃圾回收器决定。The programmer has no control over when the finalizer is called because this is determined by the garbage collector. 垃圾回收器检查应用程序不再使用的对象。The garbage collector checks for objects that are no longer being used by the application. 如果它认为某个对象符合终止条件,则调用终结器(如果有),并回收用来存储此对象的内存。If it considers an object eligible for finalization, it calls the finalizer (if any) and reclaims the memory used to store the object. 还可在程序退出后调用终结器。Finalizers are also called when the program exits. 可以通过调用 Collect 强制进行垃圾回收,但多数情况下应避免此操作,因为它可能会造成性能问题。 构造器的重载123456789101112131415161718192021222324252627class Employee { public Employee (string firstName, string lastName) { FirstName = firstName; LastName = lastName; } public Employee ( int id, string firstName, string lastName) { Id = id; FirstName = firstName; LastName = lastName; } public Employee (int id) { Id = id; // Look up employee name... // ... } public int Id { get; set; } public string FirstName { get; set; } public string LastName { get; set; } public string Salary { get; set; } // ...} 应优先使用可选参数而不是重载,以便在API中清楚地看出“默认”属性的默认值。例如,Person的一个构造器签名Person(string firstName, string lastName, int? age = null)就清楚地指明如果Person的Age未指定,就将它默认为null。 构造器链:使用this调用另一个构造器上例中,对Employee对象进行初始化的代码多处重复,可以从一个构造器中调用另一个构造器,避免重复输入代码。这称为构造器链,它是用构造器初始化器来实现的。C#采用的语法格式是在一个冒号后面添加this关键字,再添加被调用构造器的参数列表。构造器初始化器在自行当前的构造器实现之前,判断要调用另外哪一个构造器,实例如下: 1234567891011121314151617181920212223242526272829class Employee { public Employee (string firstName, string lastName) { FirstName = firstName; LastName = lastName; } public Employee ( int id, string firstName, string lastName) : this (firstName, lastName) { Id = id; } public Employee (int id) { Id = id; // Look up employee name... // ... // NOTE: Member constructors cannot be // called explicitly inline // this(id, firstName, lastName); } public int Id { get; set; } public string FirstName { get; set; } public string LastName { get; set; } public string Salary { get; set; } // ...} 上例中,3个参数的构造器调用2个参数的构造器。通常情况下,用参数最少的构造器调用参数最多的构造器,为未知的参数传递默认值。如下例: 12345678910111213public Employee(int id):this(id,"",""){ string firstName; string lastName; Id = id; // 检索、组合Employee数据 firstName = string.Empty; lastName = string.Empty; // ... Initialize(id, firstName, lastName);} 构造器链完整例子参考: 1234567891011121314151617181920212223242526272829303132333435363738394041using System;public class Person { public string personName; //定义年龄为可空类型,这样就可以赋予其null值 public int? personAge; //下面前三个构造函数都是去调用参数最多的第四个构造函数,只取它们所需要的部分参数即可 //这样的做法就是this串联构造函数 public Person () : this ("", 0) { } public Person (string name) : this (name, null) { } public Person (int age) : this ("", age) { } public Person (string name, int? age) { this.personName = name; //通过 ?? 判断传入的age是否null值 //如果属于null值,则赋值100 this.personAge = age ?? 100; } public void Display () { Console.WriteLine ("Name:{0},Age:{1}", personName, personAge); }}class Hello { static void Main (string[] args) { Person per1 = new Person (); per1.Display (); Person per2 = new Person (20); per2.Display (); Person per3 = new Person ("evan"); per3.Display (); Person per4 = new Person ("evan", 20); per4.Display (); Console.ReadKey (); }} 初学者主题:集中初始化创建单独的方法,将所有初始化代码集中在一起,如下例中,创建名为Initialize()方法,它同时获取员工的名字、姓氏和ID。示例如下: 1234567891011121314151617181920212223242526272829303132333435363738class Employee { public Employee (string firstName, string lastName) { int id; // Generate an employee ID... id = 0; // id needs to be initialized for this example // ... Initialize (id, firstName, lastName); } public Employee (int id, string firstName, string lastName) { Initialize (id, firstName, lastName); } public Employee (int id) { string firstName; string lastName; Id = id; // Look up employee data firstName = string.Empty; lastName = string.Empty; // ... Initialize (id, firstName, lastName); } private void Initialize ( int id, string firstName, string lastName) { Id = id; FirstName = firstName; LastName = lastName; } // ... private int Id { get; set; } public string FirstName { get; set; } public string LastName { get; set; }} 匿名类型C# 3.0引入了对匿名类型的支持,匿名类型提供了一种方便的方法,可用来将一组只读属性封装到单个对象中,而无需首先显式定义一个类型。 类型名由编译器生成,并且不能在源代码级使用。 每个属性的类型由编译器推断。 123456789101112131415161718192021222324252627282930313233343536373839404142using System;public class Program { public static void Main () { var patent1 = new { Title = "Bifocals", YearOfPublication = "1784" }; var patent2 = new { Title = "Phonograph", YearOfPublication = "1877" }; var patent3 = new { patent1.Title, Year = patent1.YearOfPublication }; System.Console.WriteLine ("{0} ({1})", patent1.Title, patent1.YearOfPublication); System.Console.WriteLine ("{0} ({1})", patent2.Title, patent1.YearOfPublication); Console.WriteLine (); Console.WriteLine (patent1); Console.WriteLine (patent2); Console.WriteLine (); Console.WriteLine (patent3); }}/* 输出ifocals (1784)Phonograph (1784){ Title = Bifocals, YearOfPublication = 1784 }{ Title = Phonograph, YearOfPublication = 1877 }{ Title = Bifocals, Year = 1784 }*/ 匿名类型通常用在查询表达式的 select 子句中,以便返回源序列中每个对象的属性子集。Anonymous types typically are used in the select clause of a query expression to return a subset of the properties from each object in the source sequence. 有关查询的详细信息,请参阅 LINQ 查询表达式。 静态成员使用 static 修饰符可声明属于类型本身而不是属于特定对象的静态成员。Use the static modifier to declare a static member, which belongs to the type itself rather than to a specific object. static 修饰符可用于类、字段、方法、属性、运算符、事件和构造函数,但不能用于索引器、终结器或类以外的类型。The static modifier can be used with classes, fields, methods, properties, operators, events, and constructors, but it cannot be used with indexers, finalizers, or types other than classes. 有关详细信息,请参阅静态类和静态类成员。For more information, see Static Classes and Static Class Members. C# 不支持静态局部变量(在方法范围中声明的变量)。C# does not support static local variables (variables that are declared in method scope). 可在成员的返回类型之前使用 static 关键字声明静态类成员,如下面的示例所示:You declare static class members by using the static keyword before the return type of the member, as shown in the following example: 123456789101112131415public class Automobile{ public static int NumberOfWheels = 4; public static int SizeOfGasTank { get { return 15; } } public static void Drive() { } public static event EventType RunOutOfGas; // Other non-static fields and properties...} 在首次访问静态成员之前以及在调用构造函数(如果有)之前,会初始化静态成员。Static members are initialized before the static member is accessed for the first time and before the static constructor, if there is one, is called. 若要访问静态类成员,请使用类的名称(而不是变量名称)指定成员的位置,如下面的示例所示:To access a static class member, use the name of the class instead of a variable name to specify the location of the member, as shown in the following example: 12Automobile.Drive();int i = Automobile.NumberOfWheels; 如果类包含静态字段,则提供在类加载时初始化它们的静态构造函数。 常量或类型声明是隐式的静态成员。A constant or type declaration is implicitly a static member. 不能通过实例引用静态成员。A static member cannot be referenced through an instance. 然而,可以通过类型名称引用它。Instead, it is referenced through the type name. 例如,请考虑以下类:For example, consider the following class: 1234567public class MyBaseC{ public struct MyStruct { public static int x = 100; }} 若要引用静态成员 x,除非可从相同范围访问该成员,否则请使用完全限定的名称 MyBaseC.MyStruct.x:To refer to the static member x, use the fully qualified name, MyBaseC.MyStruct.x, unless the member is accessible from the same scope: 1Console.WriteLine(MyBaseC.MyStruct.x); 尽管类的实例包含该类的所有实例字段的单独副本,但每个静态字段只有一个副本。 示例 此示例显示,尽管可以使用尚未声明的其他静态字段来初始化某个静态字段,但除非向该静态字段显式分配值,否则不会定义该结果。This example shows that although you can initialize a static field by using another static field not yet declared, the results will be undefined until you explicitly assign a value to the static field. 1234567891011121314151617181920using System;class Test { static int x = y; static int y = 5; static void Main () { Console.WriteLine (x); Console.WriteLine (y); x = 99; Console.WriteLine (x); }}/*Output: 0 5 99*/ 静态字段 使用static关键字声明一个静态字段,然后访问: 123456789101112131415161718192021222324252627282930313233343536373839404142434445using System;public class Program { public static void Main () { Employee.NextId = 1000000; Employee employee1 = new Employee ( "Inigo", "Montoya"); Employee employee2 = new Employee ( "Princess", "Buttercup"); Console.WriteLine ( "{0} {1} ({2})", employee1.FirstName, employee1.LastName, employee1.Id); Console.WriteLine ( "{0} {1} ({2})", employee2.FirstName, employee2.LastName, employee2.Id); Console.WriteLine ("NextId = {0}", Employee.NextId); }}class Employee { public Employee (string firstName, string lastName) { FirstName = firstName; LastName = lastName; Id = NextId; NextId++; } public static int NextId = 42; public int Id { get; set; } public string FirstName { get; set; } public string LastName { get; set; } public string Salary { get; set; }}/* 输出Inigo Montoya (1000000)Princess Buttercup (1000001)NextId = 1000002*/ NextId字段声明包含static修饰符,成为静态字段。静态字段和实例字段一样可以在声明时进行初始化。静态字段只属于类本身,从类外部访问静态字段时要使用类名,而不是类实例(变量)名。只有在类(或者派生类)内部代码中,才可以省略类名。 静态方法Console.WriteLine()这些,就是静态方法。无需实例话,直接引用。 静态构造器C#支持静态构造器,不允许有任何参数。使用静态构造器将类中的静态数据初始化成特定的值,尤其是无法通过声明时的一次简单赋值来获得初始值的时候。 声明静态构造器 123456789101112using System;class Employee { static Employee () { Random randomGenerator = new Random (); NextId = randomGenerator.Next (101, 999); } // ... public static int NextId = 42; // ...} 在静态构造器中进行的赋值,优先于声明时的赋值,这和实例字段情况一样。不要在静态构造器中抛出异常,这会造成类型在用用程序的剩余生存期内无法使用。 静态属性可以将属性声明为static,使用静态属性几乎肯定比使用公共静态字段好,因为公共静态字段在任何地方都能调用,而静态属性至少提供了一定程度的封装。 12345678910111213class Employee { // ... public static int NextId { get { return _NextId; } private set { _NextId = value; } } public static int _NextId = 42; // ...} 静态类静态类无法实例化。 换句话说,无法使用 new 关键字创建类类型的变量。 由于不存在任何实例变量,因此可以使用类名本身访问静态类的成员。静态类可以用作只对输入参数进行操作并且不必获取或设置任何内部实例字段的方法集的方便容器。 以下列表提供静态类的主要功能:The following list provides the main features of a static class: 只包含静态成员。Contains only static members. 无法进行实例化。Cannot be instantiated. 会进行密封。Is sealed. 不能包含实例构造函数。 因此,创建静态类基本上与创建只包含静态成员和私有构造函数的类相同。Creating a static class is therefore basically the same as creating a class that contains only static members and a private constructor. 私有构造函数可防止类进行实例化。A private constructor prevents the class from being instantiated. 使用静态类的优点是编译器可以进行检查,以确保不会意外地添加任何实例成员。The advantage of using a static class is that the compiler can check to make sure that no instance members are accidentally added. 编译器可保证无法创建此类的实例。The compiler will guarantee that instances of this class cannot be created. 静态类会进行密封,因此不能继承。Static classes are sealed and therefore cannot be inherited. 它们不能继承自任何类(除了 Object)。They cannot inherit from any class except Object. 静态类不能包含实例构造函数;但是,它们可以包含静态构造函数。Static classes cannot contain an instance constructor; however, they can contain a static constructor. 如果类包含需要进行重要初始化的静态成员,则非静态类还应定义静态构造函数。Non-static classes should also define a static constructor if the class contains static members that require non-trivial initialization. 有关详细信息,请参阅静态构造函数。 12345678910111213141516171819202122232425262728293031323334353637383940414243444546474849using System;public class Program { public static void Main () { int[] arrayNum = { 11, 12, 13, 14, 15, 16 }; int[] arrayNumber = new int[] { 1, 2, 3, 4, 5, 6 }; System.Console.WriteLine (SimpleMath.Max (arrayNumber)); System.Console.WriteLine (SimpleMath.Min (arrayNum)); }}public static class SimpleMath { // params allows the number of parameters to vary. public static int Max (params int[] numbers) { // Check that there is a least one item in numbers. if (numbers.Length == 0) { throw new ArgumentException ( "numbers cannot be empty"); } int result; result = numbers[0]; foreach (int number in numbers) { if (number > result) { result = number; } } return result; } // params allows the number of parameters to vary. public static int Min (params int[] numbers) { // Check that there is a least one item in numbers. if (numbers.Length == 0) { throw new ArgumentException ( "numbers cannot be empty"); } int result; result = numbers[0]; foreach (int number in numbers) { if (number < result) { result = number; } } return result; }} 扩展方法转自 田小计划 当我们想为一个现有的类型添加一个方法的时候,有两种方式:一是直接在现有类型中添加方法;但是很多情况下现有类型都是不允许修改的,那么可以使用第二种方式,基于现有类型创建一个子类,然后在子类中添加想要的方法。 当C# 2.0中出现了静态类之后,对于上面的问题,我们也可以创建静态工具类来实现想要添加的方法。这样做可以避免创建子类,但是在使用时代码就没有那么直观了。 其实,上面的方法都不是很好的解决办法。在C# 3.0中出现了扩展方法,通过扩展方法我们可以直接在一个现有的类型上”添加”方法。当使用扩展方法的时候,可以像调用实例方法一样的方式来调用扩展方法。 扩展方法的声明和调用相比普通方法,扩展方法有它自己的特征,下面就来看看怎么声明一个扩展方法: 它必须在一个非嵌套、非泛型的静态类中(所以扩展方法一定是静态方法) 它至少要有一个参数 第一个参数必须加上this关键字作为前缀 第一个参数类型也称为扩展类型(extended type),表示该方法对这个类型进行扩展 第一个参数不能用其他任何修饰符(比如out或ref) 第一个参数的类型不能是指针类型 根据上面的要求,我们给int类型添加了一个扩展方法,用来判断一个int值是不是偶数: 1234567891011121314151617181920212223242526272829using System;namespace ExtentionMethodTest { public static class ExtentionMethods { public static bool IsEven (this int num) { return num % 2 == 0; } } class Program { static void Main (string[] args) { int num = 10; //直接调用扩展方法 Console.WriteLine ("Is {0} a even number? {1}", num, num.IsEven ()); num = 11; //直接调用扩展方法 Console.WriteLine ("Is {0} a even number? {1}", num, num.IsEven ()); //通过静态类调用静态方法 Console.WriteLine ("Is {0} a even number? {1}", num, ExtentionMethods.IsEven (num)); Console.Read (); } }}/* 输出Is 10 a even number? TrueIs 11 a even number? FalseIs 11 a even number? False*/ 通过上面的例子可以看到,当调用扩展方法的时候,可以像调用实例方法一样。这就是我们使用扩展方法的原因之一,我们可以给一个已有类型”添加”一个方法。 既然扩展方法是一个静态类的方法,我们当然也可以通过静态类来调用这个方法。 通过IL可以看到,其实扩展方法也是编译器为我们做了一些转换,将扩展方法转化成静态类的静态方法调用。 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748.method private hidebysig staticvoid Main ( string[] args) cil managed { // Method begins at RVA 0x2068 // Code size 98 (0x62) .maxstack 3 .entrypoint .locals init ( [0] int32 ) IL_0000 : nop IL_0001 : ldc.i4.s 10 IL_0003 : stloc .0 IL_0004 : ldstr "Is {0} a even number? {1}" IL_0009 : ldloc .0 IL_000a : box[System.Runtime] System.Int32 IL_000f : ldloc .0 //直接调用扩展方法 IL_0010 : call bool ExtentionMethodTest.ExtentionMethods::IsEven (int32) IL_0015 : box[System.Runtime] System.Boolean IL_001a : call void[System.Console] System.Console::WriteLine (string, object, object) IL_001f : nop IL_0020 : ldc.i4.s 11 IL_0022 : stloc .0 IL_0023 : ldstr "Is {0} a even number? {1}" IL_0028 : ldloc .0 IL_0029 : box[System.Runtime] System.Int32 IL_002e : ldloc .0 //直接调用扩展方法 IL_002f : call bool ExtentionMethodTest.ExtentionMethods::IsEven (int32) IL_0034 : box[System.Runtime] System.Boolean IL_0039 : call void[System.Console] System.Console::WriteLine (string, object, object) IL_003e : nop IL_003f : ldstr "Is {0} a even number? {1}" IL_0044 : ldloc .0 IL_0045 : box[System.Runtime] System.Int32 IL_004a : ldloc .0 //通过静态类调用静态方法 IL_004b : call bool ExtentionMethodTest.ExtentionMethods::IsEven (int32) IL_0050 : box[System.Runtime] System.Boolean IL_0055 : call void[System.Console] System.Console::WriteLine (string, object, object) IL_005a : nop IL_005b : call int32[System.Console] System.Console::Read () IL_0060 : pop IL_0061 : ret} // end of method Program::Main const 和 readonly const修饰的常量在声明时必须初始化值;readonly修饰的常量可以不初始化值,且可以延迟到构造函数。 cons修饰的常量在编译期间会被解析,并将常量的值替换成初始化的值;而readonly延迟到运行的时候。 const修饰的常量注重的是效率;readonly修饰的常量注重灵活。 const修饰的常量没有内存消耗;readonly因为需要保存常量,所以有内存消耗。 const只能修饰基元类型、枚举类、或者字符串类型;readonly却没有这个限制。 const常量是不可变的值,在编译时是已知的,在程序的生命周期内不会改变。Constants are immutable values which are known at compile time and do not change for the life of the program. 常量使用 const 修饰符声明。Constants are declared with the const modifier. 仅 C# 内置类型(不包括 System.Object)可声明为 const。Only the C# built-in types (excluding System.Object) may be declared as const. 有关内置类型的列表,请参阅内置类型表。For a list of the built-in types, see Built-In Types Table. 用户定义的类型(包括类、结构和数组)不能为 const。User-defined types, including classes, structs, and arrays, cannot be const. 使用 readonly 修饰符创建在运行时一次性(例如在构造函数中)初始化的类、结构或数组,此后不能更改。Use the readonly modifier to create a class, struct, or array that is initialized one time at runtime (for example in a constructor) and thereafter cannot be changed. C# 不支持 const 方法、属性或事件。C# does not support const methods, properties, or events. 枚举类型使你能够为整数内置类型定义命名常量(例如 int、uint、long 等)。The enum type enables you to define named constants for integral built-in types (for example int, uint, long, and so on). 有关详细信息,请参阅枚举。For more information, see enum. 常量在声明时必须初始化。Constants must be initialized as they are declared. 例如: For example: 1234class Calendar1{ public const int months = 12;} 可以同时声明多个同一类型的常量,例如:Multiple constants of the same type can be declared at the same time, for example: 1234class Calendar2{ const int months = 12, weeks = 52, days = 365;} 常量字段自动成为静态字段,将常量字段显式声明为static会造成编译错误。 readonlyreadonly 关键字是一个可在字段上使用的修饰符。The readonly keyword is a modifier that you can use on fields. 当字段声明包括 readonly 修饰符时,该声明引入的字段赋值只能作为声明的一部分出现,或者出现在同一类的构造函数中。When a field declaration includes a readonly modifier, assignments to the fields introduced by the declaration can only occur as part of the declaration or in a constructor in the same class. 在此示例中,即使在类构造函数中给字段 year 赋了值,它的值仍无法在 ChangeYear 方法中更改:In this example, the value of the field year cannot be changed in the method ChangeYear, even though it is assigned a value in the class constructor: 123456789101112class Age{ readonly int _year; Age(int year) { _year = year; } void ChangeYear() { //_year = 1967; // Compile error if uncommented. }} readonly 关键字不同于 const 关键字。const 字段只能在该字段的声明中初始化。 readonly 字段可以在声明或构造函数中初始化。因此,根据所使用的构造函数,readonly 字段可能具有不同的值。另外,虽然 const 字段是编译时常量,但 readonly 字段可用于运行时常量,如下面的示例所示: 1public static readonly uint timeStamp = (uint)DateTime.Now.Ticks; 示例 12345678910111213141516171819202122232425262728293031323334using System;public class ReadOnlyTest { class SampleClass { public int x; // Initialize a readonly field public readonly int y = 25; public readonly int z; public SampleClass () { // Initialize a readonly instance field z = 24; } public SampleClass (int p1, int p2, int p3) { x = p1; y = p2; z = p3; } } static void Main () { SampleClass p1 = new SampleClass (11, 21, 32); // OK Console.WriteLine ("p1: x={0}, y={1}, z={2}", p1.x, p1.y, p1.z); SampleClass p2 = new SampleClass (); p2.x = 55; // OK Console.WriteLine ("p2: x={0}, y={1}, z={2}", p2.x, p2.y, p2.z); }}/* Output: p1: x=11, y=21, z=32 p2: x=55, y=25, z=24*/ 在前面的示例中,如果使用如下的语句: p2.y = 66; // Error 将收到编译器错误消息:you will get the compiler error message: The left-hand side of an assignment must be an l-value 这与尝试给常数赋值时收到的错误相同。 声明不包含字面值的类型的readonly字段 12345678910111213using System;class CommonGuid { public static readonly Guid ComIUnknownGuid = new Guid ("00000000-0000-0000-C000-000000000046"); public static readonly Guid ComIClassFactoryGuid = new Guid ("00000001-0000-0000-C000-000000000046"); public static readonly Guid ComIDispatchGuid = new Guid ("00020400-0000-0000-C000-000000000046"); public static readonly Guid ComITypeInfoGuid = new Guid ("00020401-0000-0000-C000-000000000046"); // ...} 嵌套类在类或构造中定义的类型称为嵌套类型。A type defined within a class or struct is called a nested type. 例如: 1234567class Container{ class Nested { Nested() { } }} 不论外部类型是类还是构造,嵌套类型均默认为 private;仅可从其包含类型中进行访问。Regardless of whether the outer type is a class or a struct, nested types default to private; they are accessible only from their containing type. 在上一个示例中,Nested 类无法访问外部类型。 还可指定访问修饰符来定义嵌套类型的可访问性,如下所示:You can also specify an access modifier to define the accessibility of a nested type, as follows: 嵌套类型的类可以是公共,保护,内部,受保护内部,私有或私有受保护。Nested types of a class can be public, protected, internal, protected internal, private or private protected. 但是,定义protected,protected internal或private protected嵌套类内的密封类将生成编译器警告CS0628,”新的保护的成员声明为密封类中。”However, defining a protected, protected internal or private protected nested class inside a sealed class generates compiler warning CS0628, “new protected member declared in sealed class.” 构造的嵌套类型可以是 public、internal 或 private。Nested types of a struct can be public, internal, or private. 以下示例使 Nested 类为 public:The following example makes the Nested class public: 1234567class Container{ public class Nested { Nested() { } }} 分部类和方法 Partial Classes and Methods可以将类或结构、接口或方法的定义拆分到两个或更多个源文件中。It is possible to split the definition of a class or a struct, an interface or a method over two or more source files. 每个源文件包含类型或方法定义的一部分,编译应用程序时将把所有部分组合起来。Each source file contains a section of the type or method definition, and all parts are combined when the application is compiled. 分部类 Partial Classes在以下几种情况下需要拆分类定义:There are several situations when splitting a class definition is desirable: 处理大型项目时,使一个类分布于多个独立文件中可以让多位程序员同时对该类进行处理。When working on large projects, spreading a class over separate files enables multiple programmers to work on it at the same time. 使用自动生成的源时,无需重新创建源文件便可将代码添加到类中。When working with automatically generated source, code can be added to the class without having to recreate the source file. Visual Studio 在创建 Windows 窗体、Web 服务包装器代码等时都使用此方法。Visual Studio uses this approach when it creates Windows Forms, Web service wrapper code, and so on. 无需修改 Visual Studio 创建的文件,就可创建使用这些类的代码。You can create code that uses these classes without having to modify the file created by Visual Studio. 若要拆分类定义,请使用 partial 关键字修饰符,如下所示:To split a class definition, use the partial keyword modifier, as shown here: 1234567// File:Program1.cspublic partial class Employee{ public void DoWork() { }} 1234567// File:Program2.cspublic partial class Employee{ public void GoToLunch() { }} partial 关键字指示可在命名空间中定义该类、结构或接口的其他部分。The partial keyword indicates that other parts of the class, struct, or interface can be defined in the namespace. 所有部分都必须使用 partial 关键字。All the parts must use the partial keyword. 在编译时,各个部分都必须可用来形成最终的类型。All the parts must be available at compile time to form the final type. 各个部分必须具有相同的可访问性,如 public、private 等。 嵌套类型是可以分部的: 12345678910111213// File: Program.cspartial class Program{ static void Main(string[] args) { CommandLine commandLine = new CommandLine(args); switch(commandLine.Action) { // ... } }} 12345678910111213141516171819// File: Program+CommandLine.cspartial class Program{ // Define a nested class for processing the command line. private class CommandLine { public CommandLine(string[] args) { //not implemented } // ... public int Action { get { throw new NotImplementedException(); } set { throw new NotImplementedException(); } } }} 分部方法 Partial MethodsC# 3.0 引入了分部方法概念,对 C# 2.0 的分部类进行了扩展。分部方法只存在于分部类中。 分部类或结构可以包含分部方法。A partial class or struct may contain a partial method. 类的一个部分包含方法的签名。One part of the class contains the signature of the method. 可以在同一部分或另一个部分中定义可选实现。An optional implementation may be defined in the same part or another part. 如果未提供该实现,则会在编译时删除方法以及对方法的所有调用。If the implementation is not supplied, then the method and all calls to the method are removed at compile time. 分部方法使类的某个部分的实施者能够定义方法(类似于事件)。Partial methods enable the implementer of one part of a class to define a method, similar to an event. 类的另一部分的实施者可以决定是否实现该方法。The implementer of the other part of the class can decide whether to implement the method or not. 如果未实现该方法,编译器会删除方法签名以及对该方法的所有调用。If the method is not implemented, then the compiler removes the method signature and all calls to the method. 调用该方法(包括调用中的任何参数计算结果)在运行时没有任何影响。The calls to the method, including any results that would occur from evaluation of arguments in the calls, have no effect at run time. 因此,分部类中的任何代码都可以随意地使用分部方法,即使未提供实现也是如此。Therefore, any code in the partial class can freely use a partial method, even if the implementation is not supplied. 调用但不实现该方法不会导致编译时错误或运行时错误。No compile-time or run-time errors will result if the method is called but not implemented. 在自定义生成的代码时,分部方法特别有用。Partial methods are especially useful as a way to customize generated code. 这些方法允许保留方法名称和签名,因此生成的代码可以调用方法,而开发人员可以决定是否实现方法。They allow for a method name and signature to be reserved, so that generated code can call the method but the developer can decide whether to implement the method. 与分部类非常类似,分部方法使代码生成器创建的代码和开发人员创建的代码能够协同工作,而不会产生运行时开销。Much like partial classes, partial methods enable code created by a code generator and code created by a human developer to work together without run-time costs. 分部方法声明由两个部分组成:定义和实现。A partial method declaration consists of two parts: the definition, and the implementation. 它们可以位于分部类的不同部分中,也可以位于同一部分中。These may be in separate parts of a partial class, or in the same part. 如果不存在实现声明,则编译器会优化定义声明和对方法的所有调用。If there is no implementation declaration, then the compiler optimizes away both the defining declaration and all calls to the method. 1234567// Definition in file1.cspublic partial class Person { partial void onNameChanged(); // ... } 12345678// Implementation in file2.cspublic partial class Person { partial void onNameChanged() { // method body } } 分部方法声明必须以上下文关键字 partial 开头,并且方法必须返回 void。Partial method declarations must begin with the contextual keyword partial and the method must return void. 分部方法可以有 ref 参数,但不能有 out 参数。Partial methods can have ref but not out parameters. 分部方法为隐式 private 方法,因此不能为 virtual 方法。Partial methods are implicitly private, and therefore they cannot be virtual. 分部方法不能为 extern 方法,因为主体的存在确定了方法是在定义还是在实现。Partial methods cannot be extern, because the presence of the body determines whether they are defining or implementing. 分部方法可以有 static 和 unsafe 修饰符。Partial methods can have static and unsafe modifiers. 分部方法可以是泛型的。Partial methods can be generic. 约束将放在定义分部方法声明上,但也可以选择重复放在实现声明上。Constraints are put on the defining partial method declaration, and may optionally be repeated on the implementing one. 参数和类型参数名称在实现声明和定义声明中不必相同。Parameter and type parameter names do not have to be the same in the implementing declaration as in the defining one. 你可以为已定义并实现的分部方法生成委托,但不能为已经定义但未实现的分部方法生成委托。 结尾]]></content>
<tags>
<tag>C#</tag>
<tag>C#本质论</tag>
<tag>Essential C#</tag>
</tags>
</entry>
<entry>
<title><![CDATA[Windows 系统常用工具软件]]></title>
<url>%2F2017%2F12%2F19%2FWindows-%E7%B3%BB%E7%BB%9F%E5%B8%B8%E7%94%A8%E5%B7%A5%E5%85%B7%E8%BD%AF%E4%BB%B6%2F</url>
<content type="text"><![CDATA[截图 Snipaste 大概是最犀利的截图工具了,稍微有点专业,适合重度截图使用者。 FastStone Capture 一款出色的屏幕捕捉(截图)软件,它集图像捕捉、浏览、编辑、视频录制等功能于一身,功能完善、使用方便,值得推荐!软件提供多种捕捉方式(如:活动窗口、窗口/对象、矩形区域、手绘区域、整个屏幕、滚动窗口等),还具备屏幕录像机、放大镜、颜色拾取、屏幕标尺等附加功能,支持快捷键操作。 网盘密码:7n6e PicPick 一款很不错的截图工具,小巧/实用/干净/免费/无广告,适合大多数用户使用。 OCR 文字识别 天若ocr文字识别工具 天若OCR识别工具,调用了各大网站的ocr接口,免费不限次数。具体介绍演示 移步这里。 录屏 ScreenToGif 录屏为Gif文件,小巧易用,功能强大。 网盘密码:wlww oCam 录屏为视频文件,同类软件中体积算是最小的了,功能足够。网盘密码:v7bk 图片 浏览查看 XnView 免费/干净。 iSee图片专家 图片浏览和编辑特效功能。 照片 排版打印 光影魔术手 照片排版打印,免费/无广告。 浏览器 QQ 浏览器 与Chrome浏览器一样,基于Chromium,所以chrome浏览器的插件都可以用,在应用中心也可以搜索到插件,具备chrome浏览器没有的侧边栏。 Chrome 浏览器 多数浏览器的标准参照。 健康 f.Lux 能随着一天的时间变化自动调整你电脑显示器屏幕的色温,过滤对人眼伤害最大的蓝光,从而尽可能减少屏幕对眼睛所带来的疲劳感并帮助提高夜猫子们的睡眠质量。 视频音频播放器 PotPlayer 干净/简洁/流畅/易操作。 音乐播放器 foolbar 2000 foobar2000,是一款免费软件,是Windows平台下的高级音频播放器。包含了一些重放增益支持、低内存占用等基本特色以及内置支持一些流行的音频格式。除了播放之外,它还支持生成媒体库、转换媒体文件编码、提取CD等功能,网盘内为汉化版。 网盘密码:hkzp 网易云音乐 Windows/Mac/iPhone/iPad/安卓/Linux 全平台支持,CD音质。 视频音频格式转换 格式工厂 免费多功能的多媒体文件转换工具。支持各种类型视频、音频、图片等多种格式,轻松转换到你想要的格式。 系统管理 Revo Uninstaller 可帮助您卸载软件,并删除您的计算机上安装的有害程序!即使是控制面板的“Windows 程序和功能“(添加或删除程序)里无法卸载的,它是一个更快、更强大的替代小程序!可以监控并记录程序安装过程中发生的所有系统修改项,以便日后完全卸载。如果有感觉不放心的软件,可以在Revo Uninstaller监控模式下操作该软件,查看软件对系统进行的修改项,验证是否安全。 网盘密码:nle1 系统安全 杀毒防护 Windows Defender 内置在 Windows Vista 以及以后的版本中,不用安装各种卫士了。 火绒安全软件 干净/靠谱/轻巧的安全软件,还具备清理和拦截弹窗等功能。 笔记 有道云笔记 提供了PC端、移动端、网页端等多端应用,用户可以随时随地对线上资料进行编辑、分享以及协同。 文本/代码 编辑器 Notepad2-mod 一个用来取代Notepad的开源、免费、绿色软件,只有一个主程序,不到2M大小。具有显示行号、内建各种程序语法的高亮度显示、改变背景颜色、支持Unicode与UTF-8的功能。 Sublime Text 一款流行的代码编辑器软件,支持多种编程语言和文件格式,可运行在Linux,Windows和Mac OS X。也是许多程序员喜欢使用的一款文本编辑器软件。 EmEditor 快速,轻巧,可扩展,使用方便的 Windows 文本编辑器,支持多种编程语言和文件格式。 Notepad++ 小巧高效、免费全能的文本编辑器,支持众多程序语言,比如C++、C#、Java等主流程序语言;支持HTML、XML、ASP,Perl、Python、JavaScript等网页/脚本语言。Notepad++作为程序员们最喜爱的编辑器之一,像语法高亮,语法折叠,宏等编辑器常用功能一个都不少,集成多种常用插件(适合开发者使用)。 网盘密码:6ebx Markdown文件编辑 Visual Studio Code Sublime 也是一款很好的 Markdown 编辑工具,并且相对轻量,但是 VSCode 添加了 Markdown All in One 和 markdownlint 等插件后,功能上相比 Sublime 更为强大,可以在编辑器内实时预览,同样可以保存为 HTML 格式。VSCode 相关插件参见Visual Studio Code VSCode 常用 插件 扩展。VSCode 是全功能的编辑/编码/调试工具,开源/免费/插件丰富/功能强大。 Markdown Preview Enhanced 是 VSCode Markdown 增强插件,功能比 VSCode 内置 Markdown 功能强大很多,具体功能参见Markdown Preview Enhanced 官网介绍。 Markdown 相关浏览器插件 (Chrome/QQ浏览器) Markdown Cheatsheet Markdown 语法备忘表 Convert Medium Posts to Markdown 将网页文章转换为 Markdown 拷贝为 Markdown 将你在页面中选中的HTML格式转化为Markdown格式。 Copy as Markdown Quotation 拷贝Web页面为Markdown引用格式,与上面插件区别为多了引用标记。 文件管理 Total Commander 著名的文件管理软件,其功能类似于资源管理器。它具有两个并排的文件窗口,方便用户对文件及文件夹进行复制、移动、删除等操作和管理。 Q-Dir 一款非常独特的资源管理器,Q-Dir 特别适用于频繁在各个目录间跳跃复制、粘贴和移动的情况。功能类似于 Total Commander,相比较更小巧/简单易用。 TreeSize 一个磁盘空间管理器,用树形、饼图等图形描述出来,能够显示文件大小和实际占用空间数及浪费的空间等信息,比较磁盘空间变化情况,让你做出相应的删除决定。 网盘密码:bbx2 FileTypesMan 一个用来查看系统中已注册文件类型的小工具。可以给没有图标的文件指定图标或者修改图标,比如 Markdown 文件。 网盘密码:fqzc 下载 proxyee-down 百度网盘文件下载利器,不限速,开源免费。同时支持http方式下载文件,使用本地http代理服务器方式嗅探下载请求,支持所有操作系统和大部分主流浏览器,支持分段下载和断点下载。 迅雷5 迅雷7/9版本似乎主要是限速和广告,之前的迅雷才是真正的下载工具。 网盘密码:s8lq qBittorrent 一个新的轻量级BitTorrent客户端,它简单易用,功能强大,被视为一个良好的替代其他BitTorrent软件的客户端。软件界面自动支持中文。 PDF 阅读/编辑 Foxit Reader 福昕阅读器,这里提供的是早期版本,体积小巧(5M),启动迅速,单文件免安装,干净/无广告,对PDF文件需要的功能,都具备了。 网盘密码:v9c8 PDF-XChange Editor Plus PDF-XChange Editor Plus是著名的PDF阅读工具PDF-XChange的编辑器增强版本。专注于PDF电子文档的编辑功能,体积小巧,速度飞快,提供中文版,界面友好,具备丰富的配置和功能选项,而且非常简单易用! 可帮助你快速的制作和编写PDF电子文档!PDF-XChange Editor编辑器就能修改PDF文档中的任意内容了,比如删除文字/图片/文档页面,添加文字或者图片等,当然也可以添加图表了。 网盘密码:guxr SumatraPDF 一款开源的pdf阅读器。它的功能极度精简,速度很快,支持 PDF, ePub, MOBI, CHM, XPS, DjVu, CBZ, CBR 多种格式,支持中文界面。 PdgCntEditor 一个图形化文件目录编辑器,支持PDF 、DjVu 和PDG三种格式,可以快速编辑 PDF 文件标签、目录并保存文件。 网盘密码:wji8 电子邮件 eMail 客户端 YoMail 优秀的邮件客户端,还支持 Gmail,具备协作沟通功能。 网易邮箱大师 同样是不错的邮件客户端,同样支持 Gmail。 压缩软件 BandZip Bandizip 是一款来自韩国的免费优秀的文件压缩/解压缩软件,支持 Win 与 Mac,操作方式与 WinRAR 相似,支持压缩和解压 rar、zip、7z 等众多主流格式。提供中文版,免费、纯净、无广告!Bandizip 同样支持文件右键菜单快速压缩/解压缩文件和文件夹,甚至还可以在右键菜单上快速预览压缩包里的文件列表。 7-Zip 一款完全免费而且开源的压缩软件,相比其他软件有更高的压缩比但同时耗费的资源也相对更多,如果你需要一款能够提供强大压缩性能的软件,那么它是你最好的选择。 输入法 QQ输入法 简洁、干净、字库云同步,多平台支持。 微软拼音 Windows 10 自带的拼音输入法挺好,干净,也可以云存储个人输入习惯和词库。 网络优化 DNS Benchmark 一个测试DNS速度的工具,可以测试出最适合你的DNS,免费。中文使用说明参见这里。 DNS Jumper 用于一键切换DNS的免费应用程序,里面已经包含18组DNS ,当然也包含了Google DNS,或者可以自定义添加。 网盘密码:cfiq Shell Cmder 是一个增强型命令行工具,不仅可以使用windows下的所有命令,也可以使用linux的命令,shell命令。还附带了漂亮的monokai等多个配色主题。 MobaXterm 开源/免费/干净/功能强大的全能终端软件。 Windows 系统重装 微PE工具箱 PE工具全功能集合,最干净的PE工具,用于制作系统启动 U 盘和系统启动维护。 欢迎推荐 持续更新]]></content>
<tags>
<tag>Windows</tag>
<tag>工具软件</tag>
</tags>
</entry>
<entry>
<title><![CDATA[C#本质论笔记 第4章 方法和参数]]></title>
<url>%2F2017%2F12%2F19%2FCSharp%E6%9C%AC%E8%B4%A8%E8%AE%BA%E7%AC%94%E8%AE%B0-%E7%AC%AC4%E7%AB%A0-%E6%96%B9%E6%B3%95%E5%92%8C%E5%8F%82%E6%95%B0%2F</url>
<content type="text"><![CDATA[方法方法是包含一系列语句的代码块。 程序通过调用该方法并指定任何所需的方法参数使语句得以执行。 在 C# 中,每个执行的指令均在方法的上下文中执行。 Main 方法是每个 C# 应用程序的入口点,并在启动程序时由公共语言运行时 (CLR) 调用。 规范 要为方法名使用动词或动词短语。 1System.Console.Write("Enter Your first name: ") 解析 System 命名空间 Console 类型名称 Write 方法名称 "Enter Your first name: " 实参 命名空间命名空间是一种分类机制,用于组合功能相关的所有类型。System 命名空间包含了用于执行大量基本变成活动的类型。 常见命名空间 命名空间 描述 System 主命名空间 System.Collections.Generic 泛型集合接口和类 System.Collections 使用Arraylist System.Linq 语言集成查询 System.Text 编码方式 System.Text.RegularExpressions 正则表达式 System.Threading 多线程 System.Windows.Forms winform窗体 System.Windows.Forms.Control winform控件 System.IO 操作文件、文件夹 System.Data 操作ado.net System.Data.SqlClient 操作ado.net System.Net 操作网络 System.Net.Sockets 操作网络套接字 System.Management 获取硬件信息(需要添加引用) System.Drawing 绘制系统 System.Xml 操作xml System.Media 播放wav和系统wav文件 Microsoft.Win32 操作注册表 System.Runtime.InteropServices 引用dll System.Security.Cryptography 加密解密 System.Text.RegularExpressions 正则表达式 Microsoft.VisualBasic 简繁体转换(需要添加引用) System.Diagnostics 调试输出 System.Web.UI web页面 System.Web.UI.Control web控件 System.Configuration 配置信息 System.DateTime 操作时间 System.Math 操作数字类 Microsoft.Win32.Registry 操作注册表 方法的声明和调用在类或者结构中声明方法。 例子 123456789101112131415161718192021222324252627282930class IntroducingMethods { static void Main (string[] args) { string firstName; string lastName; string fullName; System.Console.WriteLine ("Hey you!"); firstName = GetUserInput ("Enter your first name: "); lastName = GetUserInput ("Enter you last name: "); fullName = GetFullName (firstName, lastName); DisplayGreeting (fullName); } static string GetUserInput (string prompt) { System.Console.Write (prompt); return System.Console.ReadLine (); } static string GetFullName (string firstName, string lastName) { return firstName + " " + lastName; } static void DisplayGreeting (string name) { System.Console.WriteLine ("Your full name is {0}.", name); return; }} 初学者主题:用方法进行重构将一组相关语句转移到一个方法中,而不是把它们留在一个较大的方法中,这是重构 refactoring 的一种形式。 重构有助于减少重复代码,因为可以从多个位置调用方法,而不必在每个位置都重复这个方法的代码。 重构还有助于增强代码的可读性。 方法的返回类型和返回值方法可以将值返回给调用方。 如果列在方法名之前的返回类型 不是 void,则该方法可通过使用 return 关键字返回值。 带 return 关键字,后跟与返回类型匹配的值的语句将该值返回到方法调用方。 return 语句并非只能在方法末尾出现,例如,if 或 switch 语句中可以包含 return 语句,举例如下: 123456789101112131415class Program{ static bool MyMethod() { string command = ObtainCommand(); switch(command) { case "quit": return false; // ... omitted, other cases default: return true; } }} 如果 return 之后有“不可达”的语句,编译器会发出警告,指出有永远执行不到的语句。 指定 void 作为返回类型,表示没有返回值,所以,return 可有可无。 using 指令C# 允许简写类的全名,为此,要在文件的顶部列出类的命名空间(名称空间),前面加上 using 关键字。在文件的其他地方,就可以使用其类型名称来引用空间中的类型了。 例子 12345678910111213// The using directive imports all types from the// specified namespace into the entire file.using System;class HelloWorld{ static void Main() { // No need to qualify Console with System // because of the using directive above. Console.WriteLine("Hello, my name is Inigo Montoya"); }} 如果要引用某个命名空间的子命名空间中的类型,同样需要在文件顶部用 using 语句明确指定子命名空间引用。例如,要访问 System.Text 中的 StringBuilder 类型,必须增加一个 using System.Text; 指令,或者使用 Syste.Text.StringBuilder 对类型进行完全限定,而不能仅仅写成 Text.StringBuilder。 使用别名可以利用 using 指令为命名空间或类型取一个别名。别名alias是在using指令起作用范围内可以使用的替代名称。别名的两个最常见的用途是消除两个同名类型的歧义和缩写长名称。 例如,System.Timers 中存在 Timer 类型,System.Threading 中也存在一个 Timer 类型,当这两个命名空间同时存在并同时需要被引用时,需要用别名来加以区分: 123456789101112using System;using System.Threading;using CountDownTimer = System.Timers.Timer;class HelloWorld{ static void Main() { CountDownTimer timer; // ... }} 如果别名跟要区别的类型名相同,或者说别名占用了类型名的名字,那么如果要引用另外一个类型,就必须完全限定或者定义新的不同的别名,如下例: 123456789101112131415using System;using System.Threading;// Declare alias Timer to refer to System.Timers.Timer to// avoid code ambiguity with System.Threading.Timerusing Timer = System.Timers.Timer;class HelloWorld{ static void Main() { Timer timer; // ... }} 上例中,Timer 时别名,如果要引用 System.Threading.Timer类型,必须完全限定或者定义不同的别名。这又是要搞么?😥 高级主题:嵌套的 using 指令也可以在命名空间内部使用 using 指令,有效范围限于命名空间内部。不过,很少这样使用。 1234567891011121314namespace EssentialCSharp{ using System; class HelloWorld { static void Main() { // No need to qualify Console with System // because of the using directive above. Console.WriteLine("Hello, my name is Inigo Montoya"); } }} Main() 和命令行参数Main 方法是 C# 应用程序的入口点。 (库和服务不要求使用 Main 方法作为入口点)。Main 方法是应用程序启动后调用的第一个方法。 C# 程序中只能有一个入口点。 如果多个类包含 Main 方法,必须使用 /main 编译器选项来编译程序,以指定将哪个 Main 方法用作入口点。 有关详细信息,请参阅 /main(C# 编译器选项)。 概述 Main 方法是可执行程序的入口点,也是程序控制开始和结束的位置。 Main 在类或结构中声明。 Main 必须是静态方法,不得为公共方法。 (在前面的示例中,它获得的是私有成员的默认访问权限)。封闭类或结构不一定要是静态的。 Main 可以具有 void、int,或者以 C# 7.1、Task 或 Task 返回类型开头。 当且仅当 Main 返回 Task 或 Task 时,Main 的声明可包括 async 修饰符。 请注意,该操作可明确排除 async void Main 方法。 使用或不使用包含命令行自变量的 string[] 参数声明 Main 方法都行。 使用 Visual Studio 创建 Windows 应用程序时,可以手动添加此形参,也可以使用 Environment 类来获取命令行实参。 参数被读取为从零开始编制索引的命令行自变量。 与 C 和 C++ 不同,程序的名称不被视为第一个命令行自变量。 命令行参数 可以通过以下方式之一定义方法来将自变量发送到 Main 方法: 1static int Main(string[] args) 1static void Main(string[] args) 程序运行时,通过 string 数组参数将命令行参数传递给 Main()。要获取参数,操作数组就可以了。 示例 命令行输入 传递给 Main 的字符串数组 executable.exe a b c “a”“b”“c” executable.exe one two “one”“two” executable.exe “one two” three “one two”“three” 实例说明 123456789101112131415161718192021class CommandLine { static void Main (string[] args) { // The Length property provides the number of array elements System.Console.WriteLine ("parameter count = {0}", args.Length); for (int i = 0; i < args.Length; i++) { System.Console.WriteLine ("Arg[{0}] = [{1}]", i, args[i]); } }}/* 执行 CommandLine.exe a b c 输出: parameter count = 3 Arg[0] = [a] Arg[1] = [b] Arg[2] = [c]*//* 执行 commandline.exe "a b" c 输出: parameter count = 2 Arg[0] = [a b] Arg[1] = [c]*/ 高级主题:多个 Main() 方法如果多个类包含 Main 方法,对 csc.exe 使用 /main:class 选项将指定包含程序入口点的类。 示例 编译 t2.cs 和 t3.cs,指出 Main 方法可在 Test2 中找到: csc t2.cs t3.cs /main:Test2 方法的参数按引用传递和按值传递参数默认情况下,值类型传递给方法时,传递的是副本而不是对象本身。 因此,对参数的更改不会影响调用方法中的原始副本。 可以使用 ref 关键字按引用传递值类型。 有关详细信息,请参阅传递值类型参数。 有关内置值类型的列表,请参阅值类型表。 引用类型的对象传递到方法中时,将传递对对象的引用。 也就是说,该方法接收的不是对象本身,而是指示该对象位置的参数。 如果通过使用此引用更改对象的成员,即使是按值传递该对象,此更改也会反映在调用方法的参数中。 例子 123456789101112131415161718using System;public class SampleRefType { public int value;}public class Test { public static void Main () { SampleRefType rt = new SampleRefType (); rt.value = 11; ModifyObject (rt); Console.WriteLine (rt.value); } static void ModifyObject (SampleRefType obj) { obj.value = 66; }}/* 输出66*/ 在 C# 中,实参可以按值或按引用传递给形参。 按引用传递使函数成员、方法、属性、索引器、运算符和构造函数可以更改参数的值,并让该更改在调用环境中保持。 若要按引用传递参数,请使用 ref 或 out 关键字。 例子 1234567891011121314151617181920212223242526272829using System;class Program { static void Main (string[] args) { int arg; // Passing by value. // The value of arg in Main is not changed. arg = 4; squareVal (arg); Console.WriteLine (arg); // Output: 4 // Passing by reference. // The value of arg in Main is changed. arg = 4; squareRef (ref arg); Console.WriteLine (arg); // Output: 16 } static void squareVal (int valParameter) { valParameter *= valParameter; } // Passing by reference static void squareRef (ref int refParameter) { refParameter *= refParameter; }} 引用参数 ref在方法的参数列表中使用 ref 关键字时,它指示参数按引用传递,而非按值传递。 按引用传递的效果是,对所调用方法中参数进行的任何更改都反映在调用方法中。 例如,如果调用方传递本地变量表达式或数组元素访问表达式,所调用方法会替换 ref 参数引用的对象,然后,当该方法返回时,调用方的本地变量或数组元素将开始引用新对象。 若要使用 ref 参数,方法定义和调用方法均必须显式使用 ref 关键字,如下面的示例所示。 12345678910111213using System;class RefExample { static void Method (ref int i) { i = i + 44; } static void Main () { int val = 1; Method (ref val); Console.WriteLine (val); // Output: 45 }} 输出参数 outout 关键字通过引用传递参数。 它与 ref 关键字相似,只不过 ref 要求在传递之前初始化变量。 若要使用 out 参数,方法定义和调用方法均必须显式使用 out 关键字。 例如: 12345678910111213using System;class OutExample { static void Method (out int i) { i = 44; } static void Main () { int value; Method (out value); Console.WriteLine (value); // value is now 44 }} 调用具有 out 参数的方法在 C# 6 及更早版本中,必须先在单独的语句中声明变量,然后才能将其作为 out 参数传递。 下面的示例先声明了变量 number,然后再将它传递给将字符串转换为数字的 Int32.TryParse 方法。 123456789101112131415using System;public class Example { public static void Main () { string value = "1640"; int number; if (Int32.TryParse (value, out number)) Console.WriteLine ($"Converted '{value}' to {number}"); else Console.WriteLine ($"Unable to convert '{value}'"); }}// The example displays the following output:// Converted '1640' to 1640 从 C# 7 开始,可以在方法调用的参数列表而不是单独的变量声明中声明 out 变量。 这使得代码更简洁可读,还能防止在方法调用之前无意中向该变量赋值。 下面的示例与上一个示例基本相同,不同之处在于它在对 Int32.TryParse 方法的调用中定义了 number 变量。 1234567891011121314using System;public class Example { public static void Main () { string value = "1640"; if (Int32.TryParse (value, out int number)) Console.WriteLine ($"Converted '{value}' to {number}"); else Console.WriteLine ($"Unable to convert '{value}'"); }}// The example displays the following output:// Converted '1640' to 1640 数组参数参数以数组形式体现,即数组参数。数组可以作为实参传递给方法形参。由于数组是引用类型,因此方法可以更改元素的值。 将一维数组作为参数传递 可将初始化的一维数组传递给方法。 例如,下列语句将一个数组发送给了 Print 方法。 12int[] theArray = { 1, 3, 5, 7, 9 };PrintArray(theArray); 可在同一步骤中初始化并传递新数组,如下例所示。 1PrintArray(new int[] { 1, 3, 5, 7, 9 }); 将多维数组作为参数传递 通过与传递一维数组相同的方式,向方法传递初始化的多维数组。 12int[,] theArray = { { 1, 2 }, { 2, 3 }, { 3, 4 } };Print2DArray(theArray); 可在同一步骤中初始化并传递新数组,如下例所示。 1Print2DArray(new int[,] { { 1, 2 }, { 3, 4 }, { 5, 6 }, { 7, 8 } }); 示例 在下列示例中,初始化一个整数的二维数组,并将其传递至 Print2DArray 方法。 该方法将显示数组的元素。 12345678910111213141516171819202122232425262728class ArrayClass2D { static void Print2DArray (int[, ] arr) { // Display the array elements. for (int i = 0; i < arr.GetLength (0); i++) { for (int j = 0; j < arr.GetLength (1); j++) { System.Console.WriteLine ("Element({0},{1})={2}", i, j, arr[i, j]); } } } static void Main () { // Pass the array as an argument. Print2DArray (new int[, ] { { 1, 2 }, { 3, 4 }, { 5, 6 }, { 7, 8 } }); // Keep the console window open in debug mode. System.Console.WriteLine ("Press any key to exit."); System.Console.ReadKey (); }}/* Output: Element(0,0)=1 Element(0,1)=2 Element(1,0)=3 Element(1,1)=4 Element(2,0)=5 Element(2,1)=6 Element(3,0)=7 Element(3,1)=8*/ 示例 传递一个长度可变的参数列表 1234567891011121314151617181920212223242526272829303132333435363738394041424344454647using System;using System.IO;class PathEx { static void Main () { string fullName; // Call Combine() with four arguments fullName = Combine ( Directory.GetCurrentDirectory (), "bin", "config", "index.html"); Console.WriteLine (fullName); // Call Combine() with only three arguments fullName = Combine ( Environment.SystemDirectory, "Temp", "index.html"); Console.WriteLine (fullName); // Call Combine() with an array fullName = Combine ( new string[] { "C:\\", "Data", "HomeDir", "index.html" }); Console.WriteLine (fullName); // ... } static string Combine (params string[] paths) { string result = string.Empty; foreach (string path in paths) { result = System.IO.Path.Combine (result, path); } return result; }}/* 输出d:\WaProj\CSharpGuide\bin\config\index.htmlC:\Windows\system32\Temp\index.htmlC:\Data\HomeDir\index.html*/ 数组参数需要注意的地方 参数不一定是方法的唯一参数,但必须时方法声明中的最后一个参数。所以,只能有一个数组参数; 调用者可以显式地使用数组,而不必是以都好分隔的参数列表。最终生成的CIL代码是一样的。 递归递归是一项非常重要的编程技巧,它使函数调用其本身。 示例 返回目录中所有 .cs文件代码行总数: 1234567891011121314151617181920212223242526272829303132333435363738394041424344454647484950515253using System;using System.IO;public static class LineCounter { // Use the first argument as the directory // to search, or default to the current directory. public static void Main (string[] args) { int totalLineCount = 0; string directory; if (args.Length > 0) { directory = args[0]; } else { directory = Directory.GetCurrentDirectory (); } totalLineCount = DirectoryCountLines (directory); System.Console.WriteLine (totalLineCount); } static int DirectoryCountLines (string directory) { int lineCount = 0; foreach (string file in Directory.GetFiles (directory, "*.cs")) { lineCount += CountLines (file); } foreach (string subdirectory in Directory.GetDirectories (directory)) { lineCount += DirectoryCountLines (subdirectory); } return lineCount; } private static int CountLines (string file) { string line; int lineCount = 0; FileStream stream = new FileStream (file, FileMode.Open); StreamReader reader = new StreamReader (stream); line = reader.ReadLine (); while (line != null) { if (line.Trim () != "") { lineCount++; } line = reader.ReadLine (); } reader.Close (); // Automatically closes the stream return lineCount; }} 示例 遍历目录中所有目录和文件,在终端中格式化输出: 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172using System;using System.IO;namespace DirectoryFileList { class Program { static void Main (string[] args) { string directory; if (args.Length > 0) { directory = args[0]; } else { directory = Directory.GetCurrentDirectory (); } Console.WriteLine (directory); DirectoryFileList (directory, directory); Console.ReadKey (); } static void DirectoryFileList (string beginDirectory, string currentDirectory) { int level = currentDirectory.Split ('\\').Length - beginDirectory.Split ('\\').Length; string formatPrefix = "|--"; for (int currentLevel = 0; currentLevel < level; currentLevel++) { formatPrefix += "|--"; } foreach (string subdirectory in Directory.GetDirectories (currentDirectory)) { Console.WriteLine (formatPrefix + subdirectory.Substring (subdirectory.LastIndexOf ('\\'))); DirectoryFileList (beginDirectory, subdirectory); } foreach (string file in Directory.GetFiles (currentDirectory)) { Console.ForegroundColor = ConsoleColor.DarkGreen; Console.WriteLine (formatPrefix + Path.GetFileName (file)); Console.ForegroundColor = ConsoleColor.White; } } }}/* 输出d:\WaProj\CSharpGuide|--\.vscode|--|--launch.json|--|--tasks.json|--\bin|--|--\Debug|--|--|--\netcoreapp2.0|--|--|--|--CSharpGuide.deps.json|--|--|--|--CSharpGuide.dll|--|--|--|--CSharpGuide.pdb|--|--|--|--CSharpGuide.runtimeconfig.dev.json|--|--|--|--CSharpGuide.runtimeconfig.json|--\obj|--|--\Debug|--|--|--\netcoreapp2.0|--|--|--|--CSharpGuide.AssemblyInfo.cs|--|--|--|--CSharpGuide.AssemblyInfoInputs.cache|--|--|--|--CSharpGuide.csproj.CoreCompileInputs.cache|--|--|--|--CSharpGuide.csproj.FileListAbsolute.txt|--|--|--|--CSharpGuide.dll|--|--|--|--CSharpGuide.pdb|--|--CSharpGuide.csproj.nuget.cache|--|--CSharpGuide.csproj.nuget.g.props|--|--CSharpGuide.csproj.nuget.g.targets|--|--project.assets.json|--CSharpGuide.csproj|--Program.cs*/ 递归很容易陷入无限递归调用,编码/调试时多注意。 方法重载 Mathod Overloading一个类包含两个或者更多同名的方法,就会发生方法重载。以WriteLine()方法为例,可向它传递一个格式字符串和其他一些参数,也可以只传递一个整数。两者的实现肯定不同,但在逻辑上,对于调用者,这个方法就是负责输出数据,至于方法内部如恶化实现,调用者并不关心。 使用重载统计.cs文件中的行数 12345678910111213141516171819202122232425262728293031323334353637383940414243444546474849505152535455565758596061626364656667686970using System.IO;public static class LineCounter { public static void Main (string[] args) { int totalLineCount; if (args.Length > 1) { totalLineCount = DirectoryCountLines (args[0], args[1]); } if (args.Length > 0) { totalLineCount = DirectoryCountLines (args[0]); } else { totalLineCount = DirectoryCountLines (); } System.Console.WriteLine (totalLineCount); } static int DirectoryCountLines () { return DirectoryCountLines ( Directory.GetCurrentDirectory ()); } static int DirectoryCountLines (string directory) { return DirectoryCountLines (directory, "*.cs"); } static int DirectoryCountLines ( string directory, string extension) { int lineCount = 0; foreach (string file in Directory.GetFiles (directory, extension)) { lineCount += CountLines (file); } foreach (string subdirectory in Directory.GetDirectories (directory)) { lineCount += DirectoryCountLines (subdirectory); } return lineCount; } private static int CountLines (string file) { int lineCount = 0; string line; FileStream stream = new FileStream (file, FileMode.Open); StreamReader reader = new StreamReader (stream); line = reader.ReadLine (); while (line != null) { if (line.Trim () != "") { lineCount++; } line = reader.ReadLine (); } reader.Close (); // Automatically closes the stream return lineCount; }} 可选参数(可选实参)方法、构造函数、索引器或委托的定义可以指定其形参为必需还是可选。 任何调用都必须为所有必需的形参提供实参,但可以为可选的形参省略实参。 每个可选形参都有一个默认值作为其定义的一部分。 如果没有为该形参发送实参,则使用默认值。 默认值必须是以下类型的表达式之一: 常量表达式; new ValType() 形式的表达式,其中 ValType 是值类型,例如 enum 或 struct; default(ValType) 形式的表达式,其中 ValType 是值类型。 可选参数定义于参数列表的末尾和必需参数之后。 如果调用方为一系列可选形参中的任意一个形参提供了实参,则它必须为前面的所有可选形参提供实参。例如,在以下代码中,使用一个必选形参和两个可选形参定义实例方法 ExampleMethod。 1public void ExampleMethod(int required, string optionalstr = "default string", int optionalint = 10) 下面对 ExampleMethod 的调用会导致编译器错误,原因是为第三个形参而不是为第二个形参提供了实参。 //anExample.ExampleMethod(3, ,4); 但是,如果知道第三个形参的名称,则可以使用命名实参来完成此任务。 anExample.ExampleMethod(3, optionalint: 4); 命名参数(命名实参)命名参数 是 C# 4.0 新增的方法调用功能。有了命名实参,你将不再需要记住或查找形参在所调用方法的形参列表中的顺序。 每个实参的形参都可按形参名称进行指定。 如果你不记得参数的顺序,但知道其名称,你可以按任何顺序发送自变量。 PrintOrderDetails(orderNum: 31, productName: "Red Mug", sellerName: "Gift Shop"); PrintOrderDetails(productName: "Red Mug", sellerName: "Gift Shop", orderNum: 31); 高级主题:方法解析当编译器必须从一系列“适用”的方法中训责一个最适合某个特定调用的方法时,会选择拥有最据提的参数类型的那个方法。 如果有多个适用的方法,但无法从中挑选出最具唯一性的,编译器就会宝座,指明调用存在的歧义。 例如,给定以下的方法: 1234static void Method(object thing){}static void Method(double thing){}static void Method(long thing){}static void Method(int thing){} 调用 Method(42) 会被解析成 Method(int thing),因为存在着一个从实参类型到形参类型的完全匹配的方法。如果删除该方法,那么重载解析会选择 long 版本,因为 long 比 double 和 object 更具体。 异常处理语句C# 提供用于处理反常情况(称为异常,可能会在执行程序的过程中发生)的内置支持。 这些异常由正常控制流之外的代码进行处理。 try-catchTry 块之后必须紧跟着一个或多个 catch 块(或/和一个 Finally 块),这些子句指定不同异常的处理程序。只要数据类型与异常类型匹配,对应的 catch 块就会执行。 在前面例子“遍历目录中所有目录和文件,在终端中格式化输出”中,如果权限不够,就会引发异常,下面例子加入异常处理: 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748using System;using System.IO;namespace DirectoryFileList { class Program { static void Main (string[] args) { string directory; if (args.Length > 0) { directory = args[0]; } else { directory = Directory.GetCurrentDirectory (); } Console.WriteLine (directory); DirectoryFileList (directory, directory); Console.ReadKey (); } static void DirectoryFileList (string beginDirectory, string currentDirectory) { int level = currentDirectory.Split ('\\').Length - beginDirectory.Split ('\\').Length; level = ((beginDirectory.Split ('\\') [1] == "") && (currentDirectory.Split ('\\') [1] != "")) ? level + 1 : level; string formatPrefix = "|--"; for (int currentLevel = 0; currentLevel < level; currentLevel++) { formatPrefix += "|--"; } try { foreach (string subdirectory in Directory.GetDirectories (currentDirectory)) { Console.WriteLine (formatPrefix + subdirectory.Substring (subdirectory.LastIndexOf ('\\'))); DirectoryFileList (beginDirectory, subdirectory); } foreach (string file in Directory.GetFiles (currentDirectory)) { Console.ForegroundColor = ConsoleColor.DarkGreen; Console.WriteLine (formatPrefix + Path.GetFileName (file)); Console.ForegroundColor = ConsoleColor.White; } } catch (UnauthorizedAccessException) { System.Console.WriteLine ("发现受保护文件目录,禁止访问。"); } catch (Exception exception) { System.Console.WriteLine ("程序运行异常:" + exception); } } }} throwThrow会抛出/传递程序执行期间出现异常的信号,通过在catch块里使用throw语句.可以改变产生的异常,比如我们可以抛出一个新的异常。 C#允许开发人员从代码中引发异常,如下例所示: 123456789101112131415161718192021222324252627282930using System;public class ThrowingExceptions { public static void Main () { try { Console.WriteLine ("Begin executing"); Console.WriteLine ("Throw exception"); throw new Exception ("自定义异常"); Console.WriteLine ("End executing"); //永远不会执行 } catch (FormatException exception) { Console.WriteLine ( "A FormateException was thrown"); } catch (Exception exception) { Console.WriteLine ( "Unexpected error: {0}", exception.Message); } catch //永远不会执行 { Console.WriteLine ("Unexpected error!"); } Console.WriteLine ( "Shutting down..."); }}/* 输出Begin executingThrow exceptionUnexpected error: 自定义异常Shutting down...*/ 有时catch块能捕捉到异常,但不能正确或者完整处理它。这种情况下,可以让这个catch块重新抛出异常,据提办法时使用一个单独的throw语句,不要在它后面指定任何异常。 throw; 和 throw exception;的区别:throw; 保持了异常中的“调用栈”信息,而throw exception;将那些信息替换成当前调用栈信息。如下例所示: 12345678910111213141516171819202122232425262728293031323334353637using System;class Program { static void Main () { try { X (); } catch (Exception ex) { Console.WriteLine ("X throw:" + ex.TargetSite); } try { Y (); } catch (Exception ex) { Console.WriteLine ("Y throw:" + ex.TargetSite); } } static void X () { try { int.Parse ("?"); } catch (Exception) { throw; // Rethrow 构造 } } static void Y () { try { int.Parse ("?"); } catch (Exception ex) { throw ex; // Throw 捕获的ex变量 } }}/*X throw:Void StringToNumber(System.String, System.Globalization.NumberStyles, NumberBuffer ByRef, System.Globalization.NumberFormatInfo, Boolean)Y throw:Void Y()*/ throw; | throw exception; | throw new exception; 区别示例: 12345678910111213141516171819202122232425262728293031323334353637383940414243444546474849505152535455565758596061626364656667686970717273747576777879808182838485868788899091using System;class hello { public static void Main () { ExceptionClass ec = new ExceptionClass (); try { ec.ExceptionThrow1 (); } catch (Exception ex) { Console.WriteLine (ex.ToString ()); } try { ec.ExceptionThrow2 (); } catch (Exception ex) { Console.WriteLine (ex.ToString ()); } try { ec.ExceptionThrow3 (); } catch (Exception ex) { Console.WriteLine (ex.ToString ()); } try { ec.ExceptionThrow4 (); } catch (Exception ex) { Console.WriteLine (ex.ToString ()); } Console.ReadKey (); }}public class ExceptionClass { public void ExceptionThrow1 () { try { // 调用原始异常抛出方法来抛出异常 this.ExceptionMethod (); } catch (Exception ex) { throw ex; } } public void ExceptionThrow2 () { try { this.ExceptionMethod (); } catch (Exception ex) { throw; } } public void ExceptionThrow3 () { try { this.ExceptionMethod (); } catch { throw; } } public void ExceptionThrow4 () { try { this.ExceptionMethod (); } catch (Exception ex) { throw new Exception ("经过进一步包装的异常", ex); } } private void ExceptionMethod () { throw new DivideByZeroException (); }}/* 输出System.DivideByZeroException: Attempted to divide by zero. at ExceptionClass.ExceptionThrow1() in d:\WaProj\CSharpGuide\Program.cs:line 47 at hello.Main() in d:\WaProj\CSharpGuide\Program.cs:line 8System.DivideByZeroException: Attempted to divide by zero. at ExceptionClass.ExceptionMethod() in d:\WaProj\CSharpGuide\Program.cs:line 88 at ExceptionClass.ExceptionThrow2() in d:\WaProj\CSharpGuide\Program.cs:line 58 at hello.Main() in d:\WaProj\CSharpGuide\Program.cs:line 14System.DivideByZeroException: Attempted to divide by zero. at ExceptionClass.ExceptionMethod() in d:\WaProj\CSharpGuide\Program.cs:line 88 at ExceptionClass.ExceptionThrow3() in d:\WaProj\CSharpGuide\Program.cs:line 69 at hello.Main() in d:\WaProj\CSharpGuide\Program.cs:line 20System.Exception: 经过进一步包装的异常 ---> System.DivideByZeroException: Attempted to divide by zero. at ExceptionClass.ExceptionMethod() in d:\WaProj\CSharpGuide\Program.cs:line 88 at ExceptionClass.ExceptionThrow4() in d:\WaProj\CSharpGuide\Program.cs:line 78 --- End of inner exception stack trace --- at ExceptionClass.ExceptionThrow4() in d:\WaProj\CSharpGuide\Program.cs:line 80 at hello.Main() in d:\WaProj\CSharpGuide\Program.cs:line 26*/ 结尾]]></content>
<tags>
<tag>C#</tag>
<tag>C#本质论</tag>
<tag>Essential C#</tag>
</tags>
</entry>
<entry>
<title><![CDATA[Hexo Next 使用配置]]></title>
<url>%2F2017%2F12%2F18%2FHexo-Next-%E4%BD%BF%E7%94%A8%E9%85%8D%E7%BD%AE%2F</url>
<content type="text"><![CDATA[Github Pages 启用 HTTPS GitHub Pages 现在已经免费提供数字证书,在 Github Pages 上创建的个人站点可以免费支持 HTTPS,操作流程如下: 到 DNS 服务商网站,修改域名解析,删除原来的 A 记录,按照下面格式增加 4 条 A 记录,例如你的域名是 example.com ,效果如下表所示: 类型 名称 值 A example.com 185.199.108.153 A example.com 185.199.109.153 A example.com 185.199.110.153 A example.com 185.199.111.153 IP地址固定统一都是上面所列的4个,具体内容,参见 Setting up an apex domain 然后打开 Github ,依次打开个人站点资料库->’Setting’->’GitHub Pages’->’Enforce HTTPS ‘ 如果 Enforce HTTPS 不能点选,等待,正常情况大约24小时不到,Enforce HTTPS 就可以启用了 启用 HTTPS 后,如果之前页面中有引用非 https 协议的资源(包括图片、脚本等各种链接资源),比如 src=”http://code.jquery.com/jquery-3.2.1.min.js" 这样 http 协议的资源,需要修改为 https 协议 src=”https://code.jquery.com/jquery-3.2.1.min.js" 就可以正常运行,不会提示不安全。 npm 安装 hexo 卡住问题解决用 npm 安装话经常出现卡住而导致无法正常安装,解决办法就是修改 npm 的安装源,这里选择淘宝 NPM 镜像,这是一个完整 npmjs.org 镜像,你可以用此代替官方版本(只读),同步频率目前为 10分钟 一次以保证尽量与官方服务同步。。 npm config set registry https://registry.npm.taobao.org 一次解决所有卡顿问题! Next 5.x 升级 6.x 下载 Next 6 主题,解压缩并重命名为next6,放置于 \themes\ 目录下,这样保留旧版本的 next 主题,可以随时切换回去。 修改站点配置文件 _config.yml,指定使用新主题 1234# Extensions## Plugins: https://hexo.io/plugins/## Themes: https://hexo.io/themes/theme: next6 修改主题语言设置 12#language: zh-Hans # next 5.xlanguage: zh-CN # next 6 修改主题配置文件\themes\next6\_config.yml,比较旧版本主题配置文件_config.yml中改动的地方,一一修改到新版本中。 旧版本主题个性化定制的文件复制到新版本主题对应目录下: next/source/css/_custom/* next/source/css/_variables/* next/layout/_custom/* 执行命令: hexo clean # 清理旧文件 hexo d -g # 重新编译/发布 博客文章评论之 Valine 注册Leancloud,Valine评论系统其实是放在Leancloud上的,因此首先需要去注册一个账号,Leancloud官网,点我注册。 注册完以后需要创建一个应用,名字可以随便起,然后 进入应用->设置->应用key 拿到你的appid和appkey之后,打开主题配置文件 搜索 valine,填入appid 和 appkey 1234567891011121314# Valine.# You can get your appid and appkey from https://leancloud.cn# more info please open https://valine.js.orgvaline: enable: true # When enable is set to be true, leancloud_visitors is recommended to be closed for the re-initialization problem within different leancloud adk version. appid: xxxxx-xxxx # your leancloud application appid appkey: xxxxxxx # your leancloud application appkey notify: false # mail notifier , https://github.com/xCss/Valine/wiki verify: false # Verification code placeholder: Just go go # comment box placeholder avatar: mm # gravatar style guest_info: nick,mail,link # custom comment header pageSize: 10 # pagination size visitor: false # leancloud-counter-security is not supported for now. 最后!记得在Leancloud -> 设置 -> 安全中心 -> Web 安全域名 把你的域名加进去,带 http 或者 https 前缀。 5.x版本 Pisces 主题页面留白太多问题修改 \themes\next\source\css\_schemes\Pisces\_layout.styl 文件中下面的字段内容: 123.header{ width: 80%; } /* 80% */.container .main-inner { width: 80%; } /* 80% */.content-wrap { width: calc(100% - 260px); } 完整文件参见 这个地址。 首页文章增加阴影分割效果1.修改文件 \themes\next\source\css\_custom\custom.styl,增加以下内容: 12345678// 主页文章添加阴影效果.post {margin-top: 20px;margin-bottom: 20px;padding: 20px;-webkit-box-shadow: 0 0 5px rgba(202, 203, 203, .5);-moz-box-shadow: 0 0 5px rgba(202, 203, 204, .5);} 2.修改文件 \themes\next\source\css\_common\components\post\post-eof.styl,注释掉以下内容 123456789101112.posts-expand { .post-eof {/* display: block; margin: $post-eof-margin-top auto $post-eof-margin-bottom; width: 8%; height: 1px; background: $grey-light; text-align: center;*/ }} 首页文章阅读全文按钮样式修改修改文件 \themes\next\source\css\_custom\custom.styl,增加以下内容: 1234567891011121314151617181920212223//首页文章阅读全文按钮样式.post-button { margin-top: 30px; text-align: center;}.post-button .btn { color: #fff; font-size: 15px; background: #686868; border-radius: 16px; line-height: 2; margin: 0 4px 8px 4px; padding: 0 20px; border:none; -webkit-box-shadow: 0px 5px 30px -3px rgba(0,0,0,0.75); -moz-box-shadow: 0px 5px 30px -3px rgba(0,0,0,0.75);}.post-button .btn:hover { color: #000000; background: #ffffff;} 文章中嵌入html页面在文章中插入 iframe。 直接使用 Hexo 标签插件 在 Markdown 文件中,可以直接使用 Hexo 标签插件来实现,在文章中插入 iframe。 {% iframe url [width] [height] %} 直接使用 HTML 语句 1<iframe src="http://example.com" width="700px" height="500px" frameborder="0" scrolling="no"></iframe> 指定在新窗口打开文章中的链接 使用 Hexo 标签插件 在 Markdown 文件中,可以直接使用 Hexo 标签插件来实现,在文章中插入链接,并自动给外部链接添加 target="_blank" 属性。方法如下: {% link text url [external] [title] %} 使用 HTML 语句 可以在 Markdown 文件或者 HTML 文件中采用下面的写法: 1<a href="http://example.com/" target="_blank">Hello, world!</a> 让 Hexo 忽略编译指定文件或目录 写在前面 编辑保存后,一定要执行 Hexo clean 命令,然后再执行 hexo g -d 命令,否则不生效,这个有点坑。😥 Hexo 编译 站点目录 下的 source 目录下的文件。skip_render 参数设置的路径是相对于 source 目录的路径。 编辑 站点配置文件 \blog\_config.yml 的 skip_render: 位置,比如 GitHub 的 reaadme.md、404文件、搜索引擎验证文件等,都不需要 Hexo 进行编译,示例如下: 12345skip_render:- readme.md //排除 readme.md- 404.html //排除 404.html- demo/** //排除 /source/demo目录中所有文件- demo/*.html //排除 /source/demo目录中所有后缀为 html 的文件 修改文章内链接文本样式将链接文本设置为 蓝色,鼠标划过时文字 颜色加亮,并显示下划线。 修改文件 themes\next\source\css\_common\components\post\post.styl,添加如下 css 样式: 12345678910111213141516171819202122// 文章内链接文本样式.post-body p a{ color: #0593d3; border-bottom: none; border-bottom: 1px solid #0593d3; &:hover { color: #fc6423; border-bottom: none; border-bottom: 1px solid #fc6423; }}.post-body li a{ color: #0593d3; border-bottom: none; border-bottom: 1px solid #0593d3; &:hover { color: #fc6423; border-bottom: none; border-bottom: 1px solid #fc6423; }} 修改字体编辑主题文件 themes\next\source\css\_variables\custom.styl,增加以下内容: // 标题,修改成你期望的字体族 $font-family-headings = Georgia, sans // 修改成你期望的字体族 $font-family-base = "Microsoft YaHei", Verdana, sans-serif // 代码字体 $code-font-family = "Input Mono", "PT Mono", Consolas, Georgia, Monaco, Menlo, monospace // 正文字体的大小 $font-size-base = 16px // 代码字体的大小 $code-font-size = 14px 修改代码块自定义样式 修改 CSS 样式文件后,基本都要执行 hexo clean 再编译/发布,才能生效。 打开自定义CSS: \themes\next\source\css\_custom\custom.styl 加入以下内容,可根据自己的情况进行调整: 123456789101112// 代码块自定义样式``内的code { color: #fc6423; background: #fbf7f8; margin: 2px;}// 大代码块的自定义样式.highlight, pre { margin: 2px 0; padding: 2px; border-radius: 3px;} 侧边栏嵌入网易云音乐修改 \themes\next\layout_custom\sidebar.swig,加入下面代码: 1<iframe frameborder="no" border="0" marginwidth="0" marginheight="0" width=330 height=110 src="//music.163.com/outchain/player?type=0&id=网易云音乐歌单ID&auto=0&height=90"></iframe> 上面的网易云音乐歌单ID改成网易云音乐某个歌单的ID,不是用户ID,参考链接。另外,专辑中一定不要有存在版权问题的歌曲,否则不能播放,也即是说,允许用户无条件下载的歌曲才可以。 部分内容参考以下博客: Moorez 二次元の技术宅]]></content>
<tags>
<tag>Hexo</tag>
<tag>Next 主题</tag>
<tag>Next 升级</tag>
<tag>Github Pages</tag>
<tag>HTTPS</tag>
<tag>npm</tag>
</tags>
</entry>
<entry>
<title><![CDATA[C#本质论笔记 第3章 操作符和控制流]]></title>
<url>%2F2017%2F12%2F17%2FCSharp%E6%9C%AC%E8%B4%A8%E8%AE%BA%E7%AC%94%E8%AE%B0-%E7%AC%AC3%E7%AB%A0-%E6%93%8D%E4%BD%9C%E7%AC%A6%E5%92%8C%E6%8E%A7%E5%88%B6%E6%B5%81%2F</url>
<content type="text"><![CDATA[操作符 Operators操作符,也叫运算符,英文:Operrators,见 Microsoft Docs C# 运算符。 名称 含义 例子 一元运算符 接受一个操作数的运算符 ++ 或 new 二元运算符 接受两个操作数的运算符 + - * / 三元运算符 接受三个操作数,C#中唯一的条件运算符 ?: 一元操作符 表达式 描述 说明 +x 标识 +5 ,正号,无意义,出于对 - 操作符的对称才加进来的 -x 求反 -5 ,负号,等价于从零减去操作数 ! 逻辑求反 它针对 bool 定义,当且仅当其操作数为 false 时返回 true。 ~x 按位求反 影响操作数的每一个位(bit) ++x 前递增 操作的结果是操作数递增后的值。 x++ 后递增 操作的结果是操作数递增前的值。 --x 前递减 操作的结果是操作数递减后的值。 x-- 后递减 操作的结果是操作数递减前的值。 (T)x 将 x 显式转换为类型 T 指定强制转换或类型转换。 递增和递减操作符 ++ --递增运算符(Post-Increment)++ 按 1 递增其操作数。 递增运算符可以在其操作数之前或之后出现: ++variable 和 variable++。第一种形式是前缀递增操作。 操作的结果是操作数递增后的值。 第二种形式是后缀递增操作。 操作的结果是操作数递增前的值。 递减运算符 (Post-Decrement) -- 按 1 递减其操作数。 减量运算符可以在其操作数之前或之后出现:–variable 和 variable–。 第一种形式是前缀递减操作。 操作的结果是操作数递减后的值。 第二种形式是后缀递减操作。 操作的结果是操作数递减前的值。 123456int x = 123;int y = 123;// 输出 123,124,125System.Console.WriteLine ("{0},{1},{2}", x++, x++, x);// 输出 124,125,125System.Console.WriteLine ("{0},{1},{2}", ++y, ++y, y); 选择语句if-else 下列中,如果 m > 10 不成立,后面代码全部不执行 12345678910111213// Try with m = 12 and then with m = 8.int m = 12;int n = 18;if (m > 10) if (n > 20) { Console.WriteLine("Result1"); } else { Console.WriteLine("Result2"); } 下列中,如果 m > 10 不成立,则执行 else 部分代码 1234567891011121314// Try with m = 12 and then with m = 8.int m = 12;int n = 18;// Try with m = 12 and then with m = 8.if (m > 10){ if (n > 20) Console.WriteLine("Result1");}else{ Console.WriteLine("Result2");} 可以将 if 语句嵌套到 else 块中,如以下部分代码所示。 1234567891011121314151617181920212223242526272829// Change the values of these variables to test the results.bool Condition1 = true;bool Condition2 = true;bool Condition3 = true;bool Condition4 = true;if (Condition1){ // Condition1 is true.}else if (Condition2){ // Condition1 is false and Condition2 is true.}else if (Condition3){ if (Condition4) { // Condition1 and Condition2 are false. Condition3 and Condition4 are true. } else { // Condition1, Condition2, and Condition4 are false. Condition3 is true. }}else{ // Condition1, Condition2, and Condition3 are false.} switch针对 3 个或更多条件测试单个表达式,switch 语句通常用作 if-else 结构的替代项。 示例 123456789101112131415161718192021222324using System;public class Example{ public static void Main() { int caseSwitch = 1; switch (caseSwitch) { case 1: Console.WriteLine("Case 1"); break; case 2: Console.WriteLine("Case 2"); break; default: Console.WriteLine("Default case"); break; } }}// The example displays the following output:// Case 1 开关部分C# 不允许从一个开关部分继续执行到下一个开关部分。 因此,以下代码将生成编译器错误。 error CS0163: 控制不能从一个 case 标签(“case 1:”)贯穿到另一个 case 标签 12345678910switch (caseSwitch){ // The following switch section causes an error. case 1: Console.WriteLine("Case 1..."); // Add a break or other jump statement here. case 2: Console.WriteLine("... and/or Case 2"); break;} default casedefault case 可以在 switch 语句中以任何顺序显示。 无论其在源代码中的顺序如何,都将在对所有 case 标签进行计算之后,最后对其进行计算。 匹配表达式匹配表达式提供与 case 标签中的模式相匹配的值。 语法为: switch (expr) 在 C# 6 中,匹配表达式必须是返回以下类型值的表达式: 字符型。 字符串。 bool。 整数值,例如 int 或 long。 枚举值。 从 C# 7 开始,匹配表达式可以是任何非 null 表达式。 switch 语句的 模式匹配每个 case 语句定义一个模式,如果它与匹配表达式相匹配,则会导致执行其包含的开关部分。 所有版本的 C# 都支持常量模式。 其余模式从 C# 7 开始支持。 常量模式常量模式测试匹配表达式是否等于指定常量。 语法为: case constant: 其中 constant 是要测试的值。 constant 可以是以下任何常数表达式: bool 文本,为 true 或 false。 任何整数常量,例如 int、long 或字节。 已声明 const 变量的名称。 一个枚举常量。 字符型文本。 字符串文本。 示例 使用常量模式来确定特定日期是否为周末、工作周的第一天、工作周的最后一天或工作周的中间日期。 它根据 DayOfWeek 枚举的成员计算当前日期的 DateTime.DayOfWeek 属性。 1234567891011121314151617181920212223242526using System;class Program{ static void Main() { switch (DateTime.Now.DayOfWeek) { case DayOfWeek.Sunday: case DayOfWeek.Saturday: Console.WriteLine("The weekend"); break; case DayOfWeek.Monday: Console.WriteLine("The first day of the work week."); break; case DayOfWeek.Friday: Console.WriteLine("The last day of the work week."); break; default: Console.WriteLine("The middle of the work week."); break; } }}// The example displays output like the following:// The middle of the work week. 类型模式类型模式可启用简洁类型计算和转换。 使用 switch 语句执行模式匹配时,会测试表达式是否可转换为指定类型,如果可以,则将其转换为该类型的一个变量。 语法为: case type varname 示例 使用类型模式来提供有关各种集合类型的信息。 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051using System;using System.Collections;using System.Collections.Generic;using System.Linq;class Example{ static void Main(string[] args) { int[] values = { 2, 4, 6, 8, 10 }; ShowCollectionInformation(values); var names = new List<string>(); names.AddRange( new string[] { "Adam", "Abigail", "Bertrand", "Bridgette" } ); ShowCollectionInformation(names); List<int> numbers = null; ShowCollectionInformation(numbers); } private static void ShowCollectionInformation(object coll) { switch (coll) { case Array arr: Console.WriteLine($"An array with {arr.Length} elements."); break; case IEnumerable<int> ieInt: Console.WriteLine($"Average: {ieInt.Average(s => s)}"); break; case IList list: Console.WriteLine($"{list.Count} items"); break; case IEnumerable ie: string result = ""; foreach (var item in ie) result += "${e} "; Console.WriteLine(result); break; case null: // Do nothing for a null. break; default: Console.WriteLine($"A instance of type {coll.GetType().Name}"); break; } }}// The example displays the following output:// An array with 5 elements.// 4 items case 语句和 when 子句从 C# 7 开始,因为 case 语句不需要互相排斥,因此可以添加 when 子句来指定必须满足的附加条件使 case 语句计算为 true。 when 子句可以是返回布尔值的任何表达式。 when 子句的更常见用法之一是防止在匹配表达式的值为 null 时执行开关部分。 据提参见 Microsoft Docs : swittch 关键字 -> case 语句和 when 子句 高级主题:switch case 和 枚举值 直接利用枚举值例子 123456789101112131415161718192021222324252627282930using System;public class EnumSwitchCase{ enum Days { Sunday = 6, Monday = 3, Tuesday, Wednesday, Thursday, Friday, Saturday }; static void Main() { Days Day = Days.Tuesday; switch (Day) { case Days.Sunday: Console.WriteLine(Days.Sunday); break; case Days.Monday: Console.WriteLine(Days.Monday); break; case Days.Tuesday: Console.WriteLine(Days.Tuesday); break; default: break; } Console.ReadKey(); }}/* 输出Tuesday*/ 枚举值显式显式转换例子 12345678910111213141516171819202122232425262728293031using System;public class EnumTest{ enum Day { Sunday = 6, Monday = 3, Tuesday, Wednesday, Thursday, Friday, Saturday }; static void Main() { int ienum = 4; switch (ienum) { case (int)Day.Sunday: Console.WriteLine(Day.Sunday); break; case (int)Day.Monday: Console.WriteLine(Day.Monday); break; case (int)Day.Tuesday: Console.WriteLine(Day.Tuesday); break; default: break; } Console.ReadKey(); }}/* 输出Tuesday*/ 迭代语句dodo 语句重复执行一个语句或语句块,直到指定的表达式计算为 false 值。 循环体必须括在大括号 {} 内,除非它由单个语句组成。 在这种情况下,大括号是可选的。 在下面的示例中,只要变量 x 小于 5,do-while 循环语句就开始执行。 1234567891011121314151617181920public class TestDoWhile{ public static void Main () { int x = 0; do { Console.WriteLine(x); x++; } while (x < 5); }}/* Output: 0 1 2 3 4*/ 与 while 语句不同的是,do-while 循环会在计算条件表达式之前执行一次。 在 do-while 块中的任何点,都可使用 ==break== 语句跳出循环。 可通过使用 ==continue== 语句直接步入 while 表达式计算语句。 如果 while 表达式计算结果为 true,则继续执行循环中的第一个语句。 如果表达式的计算结果为 false,则继续执行 do-while 循环后的第一个语句。 do-while 循环还可以通过 goto、return 或 throw 语句退出。 whilewhile 语句执行一条语句或一个语句块,直到指定的表达式的计算结果为 false 为止。 示例 1234567891011121314151617181920class WhileTest{ static void Main() { int n = 1; while (n < 6) { Console.WriteLine("Current value of n is {0}", n); n++; } }}/* Output: Current value of n is 1 Current value of n is 2 Current value of n is 3 Current value of n is 4 Current value of n is 5 */ 因为 while 表达式的测试在每次执行循环之前开始,所以 while 循环执行零次或多次。 这不同于 do 循环,该循环执行一次或多次。 break、goto、return 或 throw 语句将控制转移到循环外时,while 循环可能终止。 若要将控制传递到下一个迭代,而不退出循环,则使用 continue 语句。 for使用 for 循环,可以重复运行一个语句或语句块,直到指定的表达式的计算结果为 false 为止。 这种类型的循环可用于循环==访问数组==,以及==事先知道==循环要在其中进行==循环访问的次数==的其他应用程序。 在下面的示例中,i 的值被写入控制台,并在循环的每次迭代过程中递增 1。 123456789101112131415161718class ForLoopTest{ static void Main() { for (int i = 1; i <= 5; i++) { Console.WriteLine(i); } }}/*Output:12345*/ 因为 while 表达式的测试在每次执行循环之前开始,所以 while 循环执行零次或多次。 这不同于 do 循环,该循环执行一次或多次。 break、goto、return 或 throw 语句将控制转移到循环外时,while 循环可能终止。 若要将控制传递到下一个迭代,而不退出循环,则使用 continue 语句。 下面的示例阐释了几种不太常见的选择:为初始化表达式部分中的外部循环变量赋值、同时在初始化表达式部分和迭代器部分中调用 Console.WriteLine 方法,以及更改迭代器部分中的两个变量的值。 12345678910111213141516static void Main() { int i; int j = 10; for (i = 0, Console.WriteLine("Start: {0}",i); i < j; i++, j--, Console.WriteLine("i={0}, j={1}", i, j)) { // Body of the loop. } } // Output: // Start: 0 // i=1, j=9 // i=2, j=8 // i=3, j=7 // i=4, j=6 // i=5, j=5 定义 for 语句的所有表达式都是可选的。 例如,以下语句创建一个无限循环。 1234for (; ; ){ // ...} foreach inforeach 语句针对实现 System.Collections.IEnumerable 或 System.Collections.Generic.IEnumerable 接口的数组或集合中的每个元素重复一组嵌入语句。 在 foreach 块中的任何点上,可以使用 break 中断关键字中断该循环,或者可以使用 continue 继续关键字单步执行到循环中的下一次迭代。 显示整数数组内容的典型 foreach 循环 123456789101112131415int[] fibarray = new int[] { 0, 1, 1, 2, 3, 5, 8, 13 };foreach (int element in fibarray){ System.Console.WriteLine(element);}System.Console.WriteLine();// Output:// 0// 1// 1// 2// 3// 5// 8// 13 执行相同操作的 for 循环 12345678910111213141516int[] fibarray = new int[] { 0, 1, 1, 2, 3, 5, 8, 13 };// Compare the previous loop to a similar for loop.for (int i = 0; i < fibarray.Length; i++){ System.Console.WriteLine(fibarray[i]);}System.Console.WriteLine();// Output:// 0// 1// 1// 2// 3// 5// 8// 13 维护数组中元素数计数的 foreach 循环 12345678910111213141516171819int[] fibarray = new int[] { 0, 1, 1, 2, 3, 5, 8, 13 };// You can maintain a count of the elements in the collection.int count = 0;foreach (int element in fibarray){ count += 1; System.Console.WriteLine("Element #{0}: {1}", count, element);}System.Console.WriteLine("Number of elements in the array: {0}", count);// Output:// Element #1: 0// Element #2: 1// Element #3: 1// Element #4: 2// Element #5: 3// Element #6: 5// Element #7: 8// Element #8: 13// Number of elements in the array: 8 因为 while 表达式的测试在每次执行循环之前开始,所以 while 循环执行零次或多次。 这不同于 do 循环,该循环执行一次或多次。 跳转语句使用跳转语句执行分支,这将立即转移程序控制。 事实上,可以用跳转语句退出循环,或者跳过以此循环的剩余部分并开始下一次循环–即使当前循环条件仍然为true。跳转语句中使用下列关键字: break continue goto return breakC#使用brek语句退出循环或者switch语句。任何时候遇到break语句,控制都会立即离开循环或者switch。 break 语句将终止其所在位置的最接近封闭循环或 switch 语句。 控制权将传递给已终止语句后面的语句(若有)。 示例 在此示例中,条件语句包含一个应从 1 计数到 100 的计数器;但 break 语句在计数器计数到 4 后终止了循环。 12345678910111213141516171819202122232425class BreakTest{ static void Main() { for (int i = 1; i <= 100; i++) { if (i == 5) { break; } Console.WriteLine(i); } // Keep the console open in debug mode. Console.WriteLine("Press any key to exit."); Console.ReadKey(); }}/* Output: 1 2 3 4*/ 示例 在此示例中,break 语句用于中断内层嵌套循环,并将控制权返回给外层循环。 12345678910111213141516171819202122232425262728293031323334353637383940414243444546474849505152535455class BreakInNestedLoops{ static void Main(string[] args) { int[] numbers = { 0, 1, 2, 3, 4, 5, 6, 7, 8, 9 }; char[] letters = { 'a', 'b', 'c', 'd', 'e', 'f', 'g', 'h', 'i', 'j' }; // Outer loop for (int x = 0; x < numbers.Length; x++) { Console.WriteLine("num = {0}", numbers[x]); // Inner loop for (int y = 0; y < letters.Length; y++) { if (y == x) { // Return control to outer loop break; } Console.Write(" {0} ", letters[y]); } Console.WriteLine(); } // Keep the console open in debug mode. Console.WriteLine("Press any key to exit."); Console.ReadKey(); }}/* * Output: num = 0 num = 1 a num = 2 a b num = 3 a b c num = 4 a b c d num = 5 a b c d e num = 6 a b c d e f num = 7 a b c d e f g num = 8 a b c d e f g h num = 9 a b c d e f g h i */ continuecontinue 语句将控制传递到其中出现的封闭 while、do、for 或 foreach 语句的下一次迭代。如果循环条件仍然为 true,循环将继续执行。 示例 在本示例中,计数器最初是从 1 到 10 进行计数。 通过结合使用 continue 语句和表达式 (i < 9),跳过 continue 和 for 主体末尾之间的语句。 1234567891011121314151617181920212223class ContinueTest{ static void Main() { for (int i = 1; i <= 10; i++) { if (i < 9) { continue; } Console.WriteLine(i); } // Keep the console open in debug mode. Console.WriteLine("Press any key to exit."); Console.ReadKey(); }}/*Output:910*/ gotoC# 确实支持 goto ,并且,不仅仅可以用在 switch 语句中。 goto 语句将程序控制直接传递给标记语句。 goto 的一个通常用法是将控制传递给特定的 switch-case 标签或 switch 语句中的默认标签。 goto 语句还用于跳出深嵌套循环。 示例 下面的示例演示了 goto 在 switch 语句中的使用。 1234567891011121314151617181920212223242526272829303132333435363738394041424344class SwitchTest{ static void Main() { Console.WriteLine("Coffee sizes: 1=Small 2=Medium 3=Large"); Console.Write("Please enter your selection: "); string s = Console.ReadLine(); int n = int.Parse(s); int cost = 0; switch (n) { case 1: cost += 25; break; case 2: cost += 25; goto case 1; case 3: cost += 50; goto case 1; default: Console.WriteLine("Invalid selection."); break; } if (cost != 0) { Console.WriteLine("Please insert {0} cents.", cost); } Console.WriteLine("Thank you for your business."); // Keep the console open in debug mode. Console.WriteLine("Press any key to exit."); Console.ReadKey(); }}/*Sample Input: 2Sample Output:Coffee sizes: 1=Small 2=Medium 3=LargePlease enter your selection: 2Please insert 50 cents.Thank you for your business.*/ 示例 下面的示例演示了使用 goto 跳出嵌套循环。 12345678910111213141516171819202122232425262728293031323334353637383940414243444546474849505152535455public class GotoTest1{ static void Main() { int x = 200, y = 4; int count = 0; string[,] array = new string[x, y]; // Initialize the array: for (int i = 0; i < x; i++) for (int j = 0; j < y; j++) array[i, j] = (++count).ToString(); // Read input: Console.Write("Enter the number to search for: "); // Input a string: string myNumber = Console.ReadLine(); // Search: for (int i = 0; i < x; i++) { for (int j = 0; j < y; j++) { if (array[i, j].Equals(myNumber)) { goto Found; } } } Console.WriteLine("The number {0} was not found.", myNumber); goto Finish; Found: Console.WriteLine("The number {0} is found.", myNumber); Finish: Console.WriteLine("End of search."); // Keep the console open in debug mode. Console.WriteLine("Press any key to exit."); Console.ReadKey(); }}/*Sample Input: 44Sample OutputEnter the number to search for: 44The number 44 is found.End of search.*/ returnreturn 语句可终止它所在的方法的执行,并将控制权返回给调用方法。 它还可以返回可选值。 如果方法是 void 类型,则 return 语句可以省略。 示例 在下面的示例中,该方法CalculateArea()返回局部变量area作为double值 1234567891011121314151617181920class ReturnTest{ static double CalculateArea(int r) { double area = r * r * Math.PI; return area; } static void Main() { int radius = 5; double result = CalculateArea(radius); Console.WriteLine("The area is {0:0.00}", result); // Keep the console open in debug mode. Console.WriteLine("Press any key to exit."); Console.ReadKey(); }}// Output: The area is 78.54 C# 预处理指令控制流语句中的表达式是在运行时求值。相反,C#预处理器在编译时调用。 #define 和 #undef使用 #define 来定义符号。 将符号用作传递给 #if 指令的表达式时,该表达式的计算结果为 true,如以下示例所示: #define DEBUG 还可以通过 /define 编译器选项来定义符号。 可以通过 #undef 取消定义符号。 #undef 允许你定义一个符号,这样一来,通过将该符号用作 #if 指令中的表达式,表达式将计算为 false。 #if, #elif, #else, 和 #endif以 #if 指令开头的条件指令必须以 #endif 指令显式终止 12345678910111213141516171819// preprocessor_if.cs#define DEBUG#define MYTESTusing System;public class MyClass{ static void Main() {#if (DEBUG && !MYTEST) Console.WriteLine("DEBUG is defined");#elif (!DEBUG && MYTEST) Console.WriteLine("MYTEST is defined");#elif (DEBUG && MYTEST) Console.WriteLine("DEBUG and MYTEST are defined");#else Console.WriteLine("DEBUG and MYTEST are not defined");#endif }} #warning 和 #error #warning 可从代码中的特定位置生成一个级别的警告。 #error 可从代码中的特定位置生成错误。 编译器遇到 #warning 指令,会显示 #warning 指令后面的文本,然后继续编译。 编译器遇到 #error 指令,会显示后面的文本,然后立即退出编译,不会生成IL代码。 #region 和 #endregion利用 #region 和 #endregion,可以指定在使用 Visual Studio 编辑器的大纲功能时可展开或折叠的代码块。 #region 块必须通过 #endregion 指令终止。 #region 块不能与 #if 块重叠。 但是,可以将 #region 块嵌套在 #if 块内,或将 #if 块嵌套在 #region 块内。 #line #line 可修改编译器的行号及(可选)用于错误和警告的文件名输出。 此示例演示如何报告与行号相关联的两个警告。 #line 200 指令将行号强制设为 200(尽管默认值为 #7),直到下一个 #line 指令前,文件名都将报告为“特殊”。 #line 默认指令将行号返回至其默认行号,默认行号对由上个指令重新编号的行进行计数。 123456789101112131415class MainClass{ static void Main() {#line 200 "Special" int i; // CS0168 on line 200 int j; // CS0168 on line 201#line default char c; // CS0168 on line 9 float f; // CS0168 on line 10#line hidden // numbering not affected string s; double d; // CS0168 on line 13 }} #line hidden 指令能对调试程序隐藏连续行,当开发者逐行执行代码时,介于 #line hidden 和下一 #line 指令(假设它不是其他 #line hidden 指令)间的任何行都将被跳过。 意思是,调试器会执行 #hidden 后面代码,只是不会在 #hidden 后面代码调试停留。即便设置了断点,也会被调试器忽略。 示例 下列示例演示调试程序如何忽略代码中的隐藏行。 运行示例时,它将显示三行文本。 但是,如果按照示例所示设置断点、并按 F10 逐行执行代码,可观察到调试程序忽略隐藏行。 另请注意,即使在隐藏行设置断点,调试程序仍将忽略它。C# 12345678910111213// preprocessor_linehidden.csusing System;class MainClass{ static void Main() { Console.WriteLine("Normal line #1."); // Set break point here.#line hidden Console.WriteLine("Hidden line.");#line default Console.WriteLine("Normal line #2."); }} 运行时输出结果: Normal line #1. Hidden line. Normal line #2. #pragam #pragma 指令可以启用或者禁用特定的编译警告。 下面例子禁止“字段未使用”警告,然后在编译MyClass类后还原该警告。 123456#pragma warning disable 169public class MyClass{ int neverUsedField;}#pragma warning restore 169 若要查找警告编号,请在 Visual Studio 中生成项目,然后在“输出”窗口中查找警告编号。这是要干嘛?😥 结尾]]></content>
<tags>
<tag>C#</tag>
<tag>C#本质论</tag>
<tag>Essential C#</tag>
</tags>
</entry>
<entry>
<title><![CDATA[Visual Studio Code VSCode 常用 插件 扩展]]></title>
<url>%2F2017%2F12%2F14%2FVisual-Studio-Code-%E5%B8%B8%E7%94%A8%E6%8F%92%E4%BB%B6%2F</url>
<content type="text"><![CDATA[界面主题 Monokai : VSCcode 内置主题 Agila : 包含多个主题 Dark-Dracula Theme : Dracula 风格主题里面比较好的 Nosferatu : Dracula 风格主题 monokai light : 白色,背景色类色默认的vs亮色主题背景 FlatUI : 包含多个主题 Flatland Monokai Theme : monokai 风格主题 暗 theme-dark-monokai : monokai 风格主题 暗 Monokai Dark Soda : monokai 风格主题 暗 Monokai Light : monokai 风格主题 亮 图标主题 vscode-icons:Icons : 主要推荐 material icon theme : 备选 功能插件通用常用 Setting Sync: 同步备份VSCode的设置和插件到GitHUB,换电脑时可一键恢复; Bracket Pair Colorizer :对括号对进行着色,再也不会眼晕了; Output Colorizer : 可以终端日志输出着色,实用; XML Tools :作者 Josh Johnson - XML 格式化/Tree View XML Tools (备选) : 作者 Qub - 格式化XML文档 Alt+Shift+F vscode-fileheader: 添加头header comment Align : 代码对齐插件 Markdown Markdown All in One : All in One,提供多种快捷操作,非常好用; Markdown Preview Enhanced : 强大的 Markdown 预览/导出插件,具体说明见这里 markdownlint : 编写 Markdown 文件时智能提示,有助于养成好习惯。 Markdown Table Prettifier : Markdown 文件中,对表格进行强制格式化,看起来更整齐,舒服。(备注:Makdwon All In One 已经具备此功能。) Python Python : Python extension for Visual Studio Code ,微软出品 Python Extension Pack : 包含多个插件,snippets 提示比微软的要更全面 C# C# : VSCode 开发 C# 必备 C# FixFormat : 格式化代码,好用;This extension helps to format C# code. When OmniSharp will support all features - will be deprecated. C# Extensions : 提高开发效率的扩展工具 WEB Auto Rename Tag : sublime和webstorm也有这个内置功能,改变标签的时候同时改动开闭合标签;【HTML/XML】 Beautify :格式化代码,支持 javascript, JSON, CSS, Sass, and HTML; Document this : js 的注释模板 (注意:新版的vscode已经原生支持,在function上输入/** 弹出注释选择) ESlint : 添加对 ESLint 的支持 HTMLHint : HTML 代码检查 Icon Fonts : 个能够在项目中添加图标字体的插件。该插件支持超过 20 个热门的图标集,包括了 Font Awesome、Ionicons、Glyphicons 和 Material Design Icons。 IntelliSense for CSS class names : A Visual Studio Code extension that provides CSS class name completion for the HTML class attribute based on the CSS class definitions that can be found in your workspace or external files referenced through the link element. Open-In-Browser :此插件在快捷菜单中添加了在默认浏览器查看文件,快捷键Ctrl + K D可能会有冲突,可以改为Ctrl + K B;不在工作区的文件没有快捷菜单。 CSS Peek :当你在 HTML 文件中右键单击选择器时,选择“ Go to Definition 和 Peek definition ”选项,它便会给你发送样式设置的 CSS 代码。 Path Autocomplete : 路径智能补全 Path Intellisense : 路径智能提示 stylelint : 比内置的要全,更智能]]></content>
<tags>
<tag>VSCode</tag>
<tag>插件</tag>
<tag>Visual Studio</tag>
<tag>Visual Studio Code</tag>
</tags>
</entry>
<entry>
<title><![CDATA[推荐2个好看的linux发行版本]]></title>
<url>%2F2017%2F12%2F14%2F%E6%8E%A8%E8%8D%902%E4%B8%AA%E5%A5%BD%E7%9C%8B%E7%9A%84linux%E5%8F%91%E8%A1%8C%E7%89%88%E6%9C%AC%2F</url>
<content type="text"><![CDATA[feren OS官网地址:https://ferenos.weebly.com/ 界面截图 elementary OS官网地址:https://www.elementary.io/ 界面截图]]></content>
<tags>
<tag>Linux</tag>
<tag>elementary OS</tag>
<tag>feren OS</tag>
</tags>
</entry>
<entry>
<title><![CDATA[NumLock灯在多个linux版本中的不同设置方法]]></title>
<url>%2F2017%2F12%2F14%2FNumLock%E7%81%AF%E5%9C%A8%E5%A4%9A%E4%B8%AAlinux%E7%89%88%E6%9C%AC%E4%B8%AD%E7%9A%84%E4%B8%8D%E5%90%8C%E8%AE%BE%E7%BD%AE%E6%96%B9%E6%B3%95%2F</url>
<content type="text"><![CDATA[vim 编辑工具使用提示 vi 或者 vim,视Linux发行版本而定 键入 i 切换到插入模式 键入 d 删除模式,然后,d 删除字符,dd删除整行 方向键调整光标到插入位置,复制粘贴代码 Esc键退出插入模式,回到默认的命令模式 按Esc后,键入 :wq 表示保存并退出 按Esc后,键入 :q! 强制不保存退出 elementary OS第一步:安装numlockx,输入命令 sudo apt-get install numlockx 第二步:用 vim 打开 /etc/lightdm/pantheon-greeter.conf 文件 sudo vi /etc/lightdm/pantheon-greeter.conf 第三步:取消numlock行前面的 # # activate-numlock=true 结果如下图: 最后:重启或者注销后。小键盘就可以自动启动了 ferenOS,Linux Mint第一步:安装numlockx,输入命令: sudo apt-get install numlockx 第二步:用 vim 打开 /etc/lightdm/lightdm.conf文件,如果文件不存在,打开 /etc/lightdm/lightdm.conf.d/70-linuxmint.conf文件,。 sudo vi /etc/lightdm/lightdm.conf 或者 sudo vi /etc/lightdm/lightdm.conf.d/70-linuxmint.conf 第三步:在打开的文件中添加一行下面命令: greeter-setup-script=/usr/bin/numlockx on 结果如下图: 同样的,重启或者注销后,生效。 CentOS ,Scientific Linux,Fedora and other rpm based 先安装numlockx,可以去opensuse的软件中心里找rpm包进行安装 下载地址:https://pkgs.org/download/numlockx/ 安装命令: sudo rpm -i numlockx-1.2-6.el7.nux.x86_64.rpm 或者:sudo yum install numlockx 先备份 cp /etc/gdm/Init/Default /etc/gdm/Init/Default_backup 再修改 vim /etc/gdm/Init/Default 在exit 0之前加上/usr/bin/numlockx on,重启就会发现小键盘已经打开了! Ubuntu and other debian based基于 gdm安装 numlockx ,在终端中输入命令: sudo apt-get install numlockx 配置 numlockx sudo gedit /etc/gdm/Init/Default 在 exit 0 之前,添加如下内容: #set numlock on if [ -x /usr/bin/numlockx ]; then /usr/bin/numlockx on fi 结果如下图: 基于 lightdmsudo gedit /etc/lightdm/lightdm.conf 最后一行添加下面语句: greeter-setup-script=/usr/bin/numlockx on 结果如下图: 重启或者注销后,生效。]]></content>
<tags>
<tag>Linux</tag>
<tag>NumLock</tag>
</tags>
</entry>
<entry>
<title><![CDATA[C#本质论笔记 第2章 数据类型]]></title>
<url>%2F2017%2F12%2F13%2FCSharp%E6%9C%AC%E8%B4%A8%E8%AE%BA%E7%AC%94%E8%AE%B0-%E7%AC%AC2%E7%AB%A0-%E6%95%B0%E6%8D%AE%E7%B1%BB%E5%9E%8B%2F</url>
<content type="text"><![CDATA[数据类型预定义类型(predefined type)/基元类型(primitive type),也翻译为基本类型、基础类型或者原始类型。 类型 Size Range BCL name Signed 精度 后缀 例子 sbyte 8 bits -128 to 127 System.SByte 是 byte 8 bits 0 to 255 System.Byte 否 short 16 bits -32,768 to 32,767 System.Int16 是 ushort 16 bits 0 to 65,535 System.UInt16 否 int 32 bits -2,147,483,648 to 2,147,483,647 System.Int32 是 uint 32 bits 0 到 4,294,967,295 System.UInt32 否 U或u long 64 bits -9,223,372,036,854,775,808 to 9,223,372,036,854,775,807 System.Int64 是 L或l ulong 64 bits -9,223,372,036,854,775,808 to 9,223,372,036,854,775,807 System.UInt64 否 UL或ul float 32 bits ±1.5e−45 到 ±3.4e38 System.Single 7 F或f double 64 bits ±5.0e−324 到 ±1.7e308 System.Double 15-16 D或d decimal 128 bits ±1.0e−28 到 ±7.9e28 System.Decimal 28-29 M或m bool bits System.Boolean char 16 bits Unicode 字符 U+0000 到 U+ffff System.Char char test; test = ‘A’ string System.String string test; test = “A” object System.Object dynamic 无相应.NET类型 dynamic dyn = 1; 类型关键字和BCL name效果相同,规范建议指定数据类型时使用C#关键字,而不是BCL名称(例如,使用==string==而不是System.String或者==String==)。 后缀不区分大小写,一般建议采用大写。但对于long ,强烈建议使用大写字母L,因为小写字母l和数字1不好区分。 关于指数记数法,需要使用 e 或者 E 中缀,在中缀字母后添加正整数或者负整数,并在字面量的最后添加恰当的数据类型后缀。例如将阿伏加德罗常熟作为float输出,代码如下: 1System.Console.WriteLine(6.023E23F); 输出结果 6.023E+23 经常用到的数字格式化输出例子1234567891011121314151617class hello { static void Main () { double dValue = 12345.6789; //几种格式转换输出方式 System.Console.WriteLine (dValue); //原值输出 输出 12345.6789 System.Console.WriteLine ("{0}", dValue); System.Console.WriteLine (string.Format ("{0}", dValue)); System.Console.WriteLine (dValue.ToString ()); System.Console.WriteLine ("以下几种方法在数据有效范围内效果相同:"); System.Console.WriteLine ("WriteLine (dValue);"); System.Console.WriteLine ("WriteLine (\"{0}\", dValue);"); System.Console.WriteLine ("WriteLine (string.Format (\"{0}\", dValue));"); System.Console.WriteLine ("WriteLine (dValue.ToString ());"); }} 输出结果 12345.6789 12345.6789 12345.6789 12345.6789 以下几种方法在数据有效范围内效果相同: WriteLine (dValue); WriteLine ("{0}", dValue); WriteLine (string.Format ("{0}", dValue)); WriteLine (dValue.ToString ()); 基本数值类型C#基本数值类型包括整数类型、浮点类型以及decimal类型。 浮点数的精度是可变的。例如浮点数 0.1,可以表示成 0.0999999999 或者 0.100000001(或者任何非常接近0.1的数)。 decimal是一种特殊的浮点类型,是128位精度的10进制浮点类型,能够存书大数字而无表示错误,适合大而精确的计算。 与==二进制==浮点数不同,decimal类型保证范围内所有的==十进制==数都是精确的,0.1就是0.1。 ==关于浮点精度理解==,目前计算机是2进制(只有0,1状态)的运算机器,所有小于1的数,只能用除法计算得到结果,计算机中的除法就是移位。 例如0.1,表示成分数是1/10,分母10不是2的整数次幂,因此1/10不能用有限的二进制小数表示。 字面值或字面量(literal value) literal value 表示源代码中的固定值,就是数据值本身。Microsoft Docs 将 literal 翻译作 文本。 个人理解,字面量可以叫做常数,常数值,固定值等等,可以是用户自己定义的或者系统定义的常数、符号等(例如:==123==、==’A’==、==\n== 等)。 例如,下面代码中的数字值: 12System.Console.WriteLine(42);System.Console.WriteLine(1.618034); 输出结果 42 1.618034 用变量定义来实现上面的效果,定义int 类型变量 x 并赋值 42 ,定义 double 类型变量 y 并赋值 1.618034 。 1234int x = 42;double y = 1.618034;System.Console.WriteLine(x);System.Console.WriteLine(y); 输出结果 42 1.618034 初学者主题:使用硬编码要慎重直接将值放到源代码中成为硬编码(==hardcoding==),如果以后需要更改值,就必须重新编译代码。给予方便维护考虑,可以考虑从外部获取值,比如从一个配置文件中,这样以后修改值的时候,不需要重新编译代码。 带小数点的字面值,编译器自动解释成 double 类型。 整数值(没有小数点)通常默认为 int,前提是值在 int 类型存储范围内。如果值太大,编译器会把它解释成 long 。 C# 允许想非 int 类型赋值,前提是字面值对于目标数据类型来说是合法的。例如,short s = 42和byte b = 77都是允许的。但值一点进队字面值成立。如果不使用额外语法,b = s就是非法的。 数据精度相关代码示例12System.Console.WriteLine(1.234567890123456);System.Console.WriteLine(1.012345678901234); 输出结果 1.23456789012346 1.01234567890123 受限于 double 类型的精度,超过精度的遵循==四舍五入==原则截取。 要显示具有完整精度的数字,必须将字面值显示生命为 decimal 类型,知识通过追加一个M(或者m)后缀来实现的。 下面代码指定一个 decimal 字面值 12System.Console.WriteLine(1.234567890123456M);System.Console.WriteLine(1.012345678901234m); 输出结果 1.234567890123456 1.012345678901234 初学者主题:十六进制表示法 Hexadecimal NotationC#允许指定十六进制值,需要附加 0x 或者 0X 前缀(效果一样),如下示例: 12345//用16进制字面量显示数值10System.Console.WriteLine(0x000A);System.Console.WriteLine(0x000a);System.Console.WriteLine(0X000A);System.Console.WriteLine(0X000a); 输出结果 10 10 10 10 注意,代码输出结果是10,而不是0x000A 或者 0x000a 。 高级主题:将数格式化成十六进制要以十六进制形式格式化输出一个数值,必须使用==x==或者==X==数值格式说明符。==大小写==决定了十六进制字母的大小写,数值字面量可采用十进制或者十六进制形式,结果一样。如下示例: 1234567891011121314//显示数值 "0xA" 或者 “0xa”//字面量为十进制数值,格式化为十六进制,大写System.Console.WriteLine("0x{0:X}", 10);//字面量为十进制数值,格式化为十六进制,小写System.Console.WriteLine("0x{0:x}", 10);//字面量为十六进制大写数值,格式化为十六进制,小写System.Console.WriteLine("0x{0:x}", 0X000A);//字面量为十六进制小写数值,格式化为十六进制,大写System.Console.WriteLine("0x{0:X}", 0X000a);int value;value = 0X000a;System.Console.WriteLine(value.ToString("X"));System.Console.WriteLine("0x{0}", value.ToString("X")); 输出结果 0xA 0xa 0xa 0xA A 0xA 高级主题:round-trip (往返过程)格式化在格式化字符串时,使用round-trip格式说明符(R 或者 r),用于确保转换为字符串的数值将再次分析为相同的数值。只有 Single、 Double 和 BigInteger 类型支持此格式。 例如,string.Format(“{0}”,0.1234567890123125) 结果是 0.123456789012313 而, string.Format(“{0:R}”,0.1234567890123125) 结果是 0.1234567890123125 。 123456789101112131415161718192021222324252627282930public class Program{ public static void Main() { const double number = 0.1234567890123125; double result; string text; text = string.Format("{0}",number); //double.Parase 将数字的字符串表示形式转换为它的等效双精度浮点数。 result = double.Parse(text); System.Console.WriteLine(number); System.Console.WriteLine("text = {0}" , text); System.Console.WriteLine(result); //double.ToSring() 将此实例的数值转换为其等效的字符串表示形式。 System.Console.WriteLine(number.ToString()); System.Console.WriteLine("{0}: text = number", text == number.ToString()); System.Console.WriteLine("{0}: result = number", result != number); text = string.Format("{0:R}",number); System.Console.WriteLine("text = {0}" , text); result = double.Parse(text); System.Console.WriteLine("{0}: result = number", result == number); System.Console.ReadKey(); }} 输出结果 0.123456789012313 text = 0.123456789012313 0.123456789012313 0.123456789012313 True: text = number True: result = number text = 0.1234567890123125 True: result = number 标准数字格式字符串(Numeric Format Strings) 标准数字格式字符串用于格式化通用数值类型。 标准数字格式字符串采用 Axx 的形式,其中: A 是称为格式说明符的单个字母字符。任何包含一个以上字母字符(包括空白)的数字格式字符串都被解释为自定义数字格式字符串。 xx 是称为精度说明符的可选整数。 精度说明符的范围从 0 到 99,并且影响结果中的位数。请注意,精度说明符控制数字的字符串表示形式中的数字个数。 它不舍入该数字。若要执行舍入运算,请使用 Math.Ceiling、 Math.Floor 或 Math.Round 方法。 当精度说明符控制结果字符串中的小数位数时,结果字符串反映远离零的一侧舍入的数字(即,使用 MidpointRounding.AwayFromZero)。 所有数字类型的 ToString 方法的某些重载支持标准数字格式字符串。 例如,可将数字格式字符串提供给 ToString(String) 类型的 ToString(String, IFormatProvider) 方法和 Int32 方法。 .NET Framework 复合格式化功能也支持标准数字格式字符串,该功能由 Write 和 WriteLine 类的某些 Console 和 StreamWriter 方法、 String.Format 方法以及 StringBuilder.AppendFormat 方法使用。复合格式功能允许你将多个数据项的字符串表示形式包含在单个字符串中,以指定字段宽度,并在字段中对齐数字。 下表描述标准的数字格式说明符并显示由每个格式说明符产生的示例输出。 格式说明符 名称 描述 示例 “C”或“c” 货币 结果:货币值。受以下类型支持:所有数值类型。精度说明符:小数位数。默认值精度说明符:由 NumberFormatInfo.CurrencyDecimalDigits 定义。更多信息: 货币(“C”)格式说明符。 123.456 (“C”, en-US) -> $123.46 123.456 (“C”, fr-FR) -> 123,46 € 123.456 (“C”, ja-JP) -> ¥123 -123.456 (“C3”, en-US) -> ($123.456)-123.456 (“C3”, fr-FR) -> -123,456 €-123.456 (“C3”, ja-JP) -> -¥123.456 “D”或“d” Decimal 结果:整型数字,负号可选。受以下类型支持:==仅整型==。精度说明符:最小位数。默认值精度说明符:所需的最小位数。更多信息: 十进制(“D”)格式说明符。 1234 (“D”) -> 1234 -1234 (“D6”) -> -001234 “E”或“e” 指数(科学型) 结果:指数记数法。受以下类型支持:所有数值类型。精度说明符:小数位数。默认值精度说明符:6。更多信息: 指数(“E”)格式说明符。 1052.0329112756 (“E”, en-US) -> 1.052033E+0031052.0329112756 (“e”, fr-FR) -> 1,052033e+003-1052.0329112756 (“e2”, en-US) -> -1.05e+003-1052.0329112756 (“E2”, fr_FR) -> -1,05E+003 “F”或“f” 定点 结果:整数和小数,负号可选。受以下类型支持:所有数值类型。精度说明符:小数位数。默认值精度说明符:由 NumberFormatInfo.NumberDecimalDigits 定义。更多信息: 定点(“F”)格式说明符。 1234.567 (“F”, en-US) -> 1234.571234.567 (“F”, de-DE) -> 1234,571234 (“F1”, en-US) -> 1234.01234 (“F1”, de-DE) -> 1234,0-1234.56 (“F4”, en-US) -> -1234.5600-1234.56 (“F4”, de-DE) -> -1234,5600 “G”或“g” 常规 结果:最紧凑的定点表示法或科学记数法。受以下类型支持:所有数值类型。精度说明符:有效位数。默认值精度说明符:取决于数值类型。更多信息:常规(“G”)格式说明符。 -123.456 (“G”, en-US) -> -123.456-123.456 (“G”, sv-SE) -> -123,456123.4546 (“G4”, en-US) -> 123.5123.4546 (“G4”, sv-SE) -> 123,5-1.234567890e-25 (“G”, en-US) -> -1.23456789E-25-1.234567890e-25 (“G”, sv-SE) -> -1,23456789E-25 “N”或“n” 数字 结果:整数和小数、组分隔符和小数分隔符,负号可选。受以下类型支持:所有数值类型。精度说明符:所需的小数位数。默认值精度说明符:由 NumberFormatInfo.NumberDecimalDigits 定义。更多信息: 数字(“N”)格式说明符。 1234.567 (“N”, en-US) -> 1,234.571234.567 (“N”, ru-RU) -> 1 234,571234 (“N1”, en-US) -> 1,234.01234 (“N1”, ru-RU) -> 1 234,0-1234.56 (“N3”, en-US) -> -1,234.560-1234.56 (“N3”, ru-RU) -> -1 234,560 “P”或“p” 百分比 结果:乘以 100 并显示百分比符号的数字。受以下类型支持:所有数值类型。 精度说明符:所需的小数位数。默认值精度说明符:由 NumberFormatInfo.PercentDecimalDigits 定义。更多信息: 百分比(“P”)格式说明符。 1 (“P”, en-US) -> 100.00 %1 (“P”, fr-FR) -> 100,00 %-0.39678 (“P1”, en-US) -> -39.7 %-0.39678 (“P1”, fr-FR) -> -39,7 % “R”或“r” 往返过程 结果:可以往返至相同数字的字符串。受以下类型支持: Single、 Double 和 BigInteger。精度说明符:忽略。更多信息: 往返过程(“R”)格式说明符。 123456789.12345678 (“R”) -> 123456789.12345678-1234567890.12345678 (“R”) -> -1234567890.1234567 “X”或“x” 十六进制 结果:十六进制字符串。受以下类型支持:仅整型。精度说明符:结果字符串中的位数。更多信息: 十六进制(“X”)格式说明符。 255 (“X”) -> FF-1 (“x”) -> ff255 (“x4”) -> 00ff-1 (“X4”) -> 00FF 任何其他单个字符 未知说明符 结果:在运行时引发 FormatException。 其他 自定义数字格式字符串 你可以创建自定义数字格式字符串,这种字符串由一个或多个自定义数字说明符组成,用于定义设置数值数据格式的方式。 自定义数字格式字符串是任何不属于 ==标准数字格式字符串== 的格式字符串。 所有数字类型的 ToString 方法的某些重载支持自定义数字格式字符串。 例如,可将数字格式字符串提供给 Int32 类型的 ToString(String) 方法和 ToString(String, IFormatProvider) 方法。 .NET Framework 复合格式化功能也支持自定义数字格式字符串,该功能由 Console 和 StreamWriter 类的某些 Write 和 WriteLine 方法、 String.Format 方法以及 StringBuilder.AppendFormat 方法所使用。 下表描述自定义数字格式说明符并显示由每个格式说明符产生的示例输出。 格式说明符 名称 描述 示例 “0” 零占位符 用对应的数字(如果存在)替换零;否则,将在结果字符串中显示零。 1234.5678 (“00000”) ->012350.45678 (“0.00”, en-US) -> 0.460.45678 (“0.00”, fr-FR) -> 0,46 “#” 数字占位符 用对应的数字(如果存在)替换“#”符号;否则,不会在结果字符串中显示任何数字。 1234.5678 (“#####”) -> 12350.45678 (“#.##”, en-US) -> .460.45678 (“#.##”, fr-FR) -> ,46 “.” 小数点 确定小数点分隔符在结果字符串中的位置。 0.45678 (“0.00”, en-US) -> 0.460.45678 (“0.00”, fr-FR) -> 0,46 “,” 组分隔符和数字比例换算 用作组分隔符和数字比例换算说明符。作为组分隔符时,它在各个组之间插入本地化的组分隔符字符。作为数字比例换算说明符,对于每个指定的逗号,它将数字除以1000。 组分隔符说明符:2147483647 (“##,#”, en-US) -> 2,147,483,6472147483647 (“##,#”, es-ES) -> 2.147.483.647比例换算说明符:2147483647 (“#,#,,”, en-US) -> 2,1472147483647 (“#,#,,”, es-ES) -> 2.147 “%” 百分比占位符 将数字乘以 100,并在结果字符串中插入本地化的百分比符号。 0.3697 (“%#0.00”, en-US) -> %36.970.3697 (“%#0.00”, el-GR) -> %36,970.3697 (“##.0 %”, en-US) -> 37.0 %0.3697 (“##.0 %”, el-GR) -> 37,0 % “‰” 千分比占位符 将数字乘以 1000,并在结果字符串中插入本地化的千分比符号。 0.03697 (“#0.00‰”, en-US) -> 36.97‰0.03697 (“#0.00‰”, ru-RU) -> 36,97‰ “E0”“E+0”“E-0”“e0”“e+0”“e-0” 指数表示法 如果后跟至少一个0(零),则使用指数表示法设置结果格式。“E”或“e”指示指数符号在结果字符串中是大写还是小写。跟在“E”或“e”字符后面的零的数目确定指数中的最小位数。 加号 (+)指示符号字符总是置于指数前面。减号(-)指示符号字符仅置于负指数前面。 987654 (“#0.0e0”) -> 98.8e41503.92311 (“0.0##e+00”) -> 1.504e+031.8901385E-16 (“0.0e+00”) -> 1.9e-16 \ 转义符 使下一个字符被解释为文本而不是自定义格式说明符。 987654 (“###00#“) -> #987654# ‘string’“string” 文本字符串分隔符 指示应复制到未更改的结果字符串的封闭字符。 68 (“# ‘ degrees’”) -> 68 degrees68 (“# ‘ degrees’”) -> 68 degrees ; 部分分隔符 通过分隔格式字符串定义正数、负数和零各部分。 12.345 (“#0.0#;(#0.0#);-\0-“) -> 12.350 (“#0.0#;(#0.0#);-\0-“) -> -0--12.345 (“#0.0#;(#0.0#);-\0-“) -> (12.35)12.345 (“#0.0#;(#0.0#)”) -> 12.350 (“#0.0#;(#0.0#)”) -> 0.0-12.345 (“#0.0#;(#0.0#)”) -> (12.35) 其他 所有其他字符 字符将复制到未更改的结果字符串。 68 (“# °”) -> 68 ° 标准数字格式化字符串 简单例子1234567891011121314151617181920212223242526272829303132333435363738394041424344454647484950using System;using System.Globalization;using System.Threading;public class NumericFormats { public static void Main () { // Display string representations of numbers for en-us culture CultureInfo ci = new CultureInfo ("en-us"); // Output floating point values double floating = 10761.937554; Console.WriteLine ("原始数字:10761.937554"); Console.WriteLine ("C: {0}", floating.ToString ("C", ci)); // Displays "C: $10,761.94" Console.WriteLine ("E: {0}", floating.ToString ("E03", ci)); // Displays "E: 1.076E+004" Console.WriteLine ("F: {0}", floating.ToString ("F04", ci)); // Displays "F: 10761.9376" Console.WriteLine ("G: {0}", floating.ToString ("G", ci)); // Displays "G: 10761.937554" Console.WriteLine ("N: {0}", floating.ToString ("N03", ci)); // Displays "N: 10,761.938" Console.WriteLine ("P: {0}", (floating / 10000).ToString ("P02", ci)); // Displays "P: 107.62 %" Console.WriteLine ("R: {0}", floating.ToString ("R", ci)); // Displays "R: 10761.937554" Console.WriteLine (); // Output integral values int integral = 8395; Console.WriteLine ("原始数字:8395"); Console.WriteLine ("C: {0}", integral.ToString ("C", ci)); // Displays "C: $8,395.00" Console.WriteLine ("D: {0}", integral.ToString ("D6", ci)); // Displays "D: 008395" Console.WriteLine ("E: {0}", integral.ToString ("E03", ci)); // Displays "E: 8.395E+003" Console.WriteLine ("F: {0}", integral.ToString ("F01", ci)); // Displays "F: 8395.0" Console.WriteLine ("G: {0}", integral.ToString ("G", ci)); // Displays "G: 8395" Console.WriteLine ("N: {0}", integral.ToString ("N01", ci)); // Displays "N: 8,395.0" Console.WriteLine ("P: {0}", (integral / 10000.0).ToString ("P02", ci)); // Displays "P: 83.95 %" Console.WriteLine ("X: 0x{0}", integral.ToString ("X", ci)); // Displays "X: 0x20CB" Console.WriteLine (); }} 输出结果 原始数字:10761.937554 C: $10,761.94 E: 1.076E+004 F: 10761.9376 G: 10761.937554 N: 10,761.938 P: 107.62% R: 10761.937554 原始数字:8395 C: $8,395.00 D: 008395 E: 8.395E+003 F: 8395.0 G: 8395 N: 8,395.0 P: 83.95% X: 0x20CB 标准数字格式化字符串 详细例子123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128using System;using System.Globalization;class hello { static void Main () { double dValue = 12345.6789; int iValue = 12345; //几种格式转换输出方式 System.Console.WriteLine (dValue); //原值输出 输出 12345.6789 System.Console.WriteLine ("{0}", dValue); System.Console.WriteLine (string.Format ("{0}", dValue)); System.Console.WriteLine (dValue.ToString ()); System.Console.WriteLine ("以下几种方法在数据有效范围内效果相同:"); System.Console.WriteLine ("WriteLine (dValue);"); System.Console.WriteLine ("WriteLine (\"{0}\", dValue);"); System.Console.WriteLine ("WriteLine (string.Format (\"{0}\", dValue));"); System.Console.WriteLine ("WriteLine (dValue.ToString ());"); System.Console.WriteLine ("======================================"); //■■ 货币 "C" 格式说明符 ■■// Console.WriteLine ("{0:c}", dValue); //货币格式 默认两位小数 输出 ¥12,345.68 Console.WriteLine ("{0:c2}", dValue); //货币格式 输出 指定两位小数 ¥12,345.68 Console.WriteLine (dValue.ToString ("C")); //货币格式 输出 ¥12,345.68KT Console.WriteLine (dValue.ToString ("C2")); //货币格式 输出 ¥12,345.68 Console.WriteLine (dValue.ToString ("C3")); //货币格式 指定三位小数 输出 ¥12,345.679 //货币格式 当前国家 默认位小数 输出 ¥12,345.68 //System.Globalization. CultureInfo.CurrentCulture Console.WriteLine (dValue.ToString ("C", CultureInfo.CurrentCulture)); //货币格式 当前国家 指定两位小数 输出 ¥12,345.68 Console.WriteLine (dValue.ToString ("C2", CultureInfo.CurrentCulture)); //货币格式 中国货币 指定三位小数 输出 ¥12,345.679 Console.WriteLine (dValue.ToString ("C3", CultureInfo.CreateSpecificCulture ("zh-CN"))); //货币格式 丹麦货币 指定三位小数 输出 12.345,679 kr. Console.WriteLine (dValue.ToString ("C3", CultureInfo.CreateSpecificCulture ("da-DK"))); //■■ 十进制 "D" 格式例子 ■■// Console.WriteLine (iValue.ToString ("D")); //输出 12345 Console.WriteLine (iValue.ToString ("D8")); //输出 00012345 iValue = -12345; Console.WriteLine (iValue.ToString ("D")); //输出 -12345 Console.WriteLine (iValue.ToString ("D8")); //输出 -00012345 //错误示例 “D”只接受整型 其他编译不通过 //Console.WriteLine(dValue.ToString("D8")); //■■ 指数 "E" 格式说明符 ■■// Console.WriteLine (dValue.ToString ("E")); //输出 1.234568E+004 Console.WriteLine (dValue.ToString ("E10")); //输出 1.2345678900E+004 //■■ 定点 “F” 格式说明符 ■■// iValue = 12345; Console.WriteLine (iValue.ToString ("F")); //输出 12345.00 iValue = -12345; Console.WriteLine (iValue.ToString ("F3")); //输出 12345.000 dValue = 12345.6789; Console.WriteLine (dValue.ToString ("F")); //输出 12345.68 Console.WriteLine (dValue.ToString ("F0")); //输出 12345 //■■ 常规 “G” 格式说明符 ■■// dValue = 12345.6789; Console.WriteLine (dValue.ToString ("G")); // Displays 12345.6789 Console.WriteLine (dValue.ToString ("G", CultureInfo.CreateSpecificCulture ("fr-FR"))); // Displays 12345,6789 Console.WriteLine (dValue.ToString ("G7")); // Displays 12345.68 dValue = .0000023; Console.WriteLine (dValue.ToString ("G")); // Displays 2.3E-06 Console.WriteLine (dValue.ToString ("G", CultureInfo.CreateSpecificCulture ("fr-FR"))); // Displays 2,3E-06 dValue = .0023; Console.WriteLine (dValue.ToString ("G")); // Displays 0.0023 dValue = 1234; Console.WriteLine (dValue.ToString ("G2")); // Displays 1.2E+03 dValue = Math.PI; Console.WriteLine (dValue.ToString ("G5")); // Displays 3.1416 //■■ 数字 “N” 格式说明符 ■■// dValue = -12345.6789; Console.WriteLine (dValue.ToString ("G")); // Displays 12345.6789N Console.WriteLine (dValue.ToString ("N1", CultureInfo.CreateSpecificCulture ("sv-SE"))); // Displays -12 445,7 iValue = 123456789; Console.WriteLine (iValue.ToString ("N1", CultureInfo.InvariantCulture)); // Displays 123,456,789.0 //■■ 数字 “N” 格式说明符 ■■// dValue = -.2468013; Console.WriteLine (dValue.ToString ("P")); // Displays -24.68 % Console.WriteLine (dValue.ToString ("P1", CultureInfo.CreateSpecificCulture ("sv-SE"))); // Displays -24,68% Console.WriteLine (dValue.ToString ("P1", CultureInfo.InvariantCulture)); // Displays -24.7 % //■■ 数字 “R” 格式说明符 ■■// dValue = Math.PI; Console.WriteLine (dValue.ToString ("r")); // Displays 3.1415926535897931 Console.WriteLine (dValue.ToString ("r", CultureInfo.CreateSpecificCulture ("fr-FR"))); // Displays 3,1415926535897931 dValue = 1.623e-21; Console.WriteLine (dValue.ToString ("r")); // Displays 1.623E-21 //■■ 十六进制 “X” 格式说明符 ■■// iValue = 0x2045e; Console.WriteLine (iValue.ToString ("x")); // Displays 2045e Console.WriteLine (iValue.ToString ("X")); // Displays 2045E Console.WriteLine (iValue.ToString ("X8")); // Displays 0002045E iValue = 123456789; Console.WriteLine (iValue.ToString ("X")); // Displays 75BCD15 Console.WriteLine (iValue.ToString ("X2")); // Displays 75BCD15 Console.ReadKey (); }} 自定义数字格式化字符串例子 下面的示例演示两个自定义数字格式字符串。 在这两个示例中,数字占位符 (#) 显示数值数据,且所有其他字符被复制到结果字符串。 1234567891011121314151617using System;using System.Globalization;public class Example { public static void Main () { double number1 = 1234567890; string value1 = number1.ToString ("(###) ###-####"); Console.WriteLine (value1); int number2 = 42; string value2 = number2.ToString ("My Number = #"); Console.WriteLine (value2); // The example displays the following output: // (123) 456-7890 // My Number = 42 }} 更多基本类型char (字符)类型 char 关键字用于声明 System.Char 结构的实例,.NET Framework 使用该结构来表示 Unicode 字符。 Char 对象的值为 16 位的==数字(序号)值==(见Microsoft Docs C# 参考 char)。 Unicode 字符用于表示世界各地大多数的书面语言。 类型 范围 大小 .NET Framwork char U+0000 到 U+FFFF Unicode 16 位字符 System.char 后面经常用到的 \uxxxx 转义序列,XXXX是代表某个Unicode字符的16进制编码值。通过System.Text.Encoding.UTF8.GetBytes(char[])和System.Text.UnicodeEncoding.GetBytes(String)这些方法得到的值是10进制的,需要(通过 ==标准数字格式字符串== 中的 ==X2== )转换成16进制,就是相应的Unicode编码了。 char 可以隐式转换为 ushort、 int、 uint、 long、 ulong、 float、 double 或 decimal。 但是,不存在从其他类型到 char 类型的隐式转换。 char 类型的常数可以写成==字符==(例如 ‘x’)、==十六进制换码序列==(例如 ‘\t’)或 ==Unicode== (例如 ‘\u02C0’)表示形式。 您也可以显式转换整数字符代码。 在下面的示例中,几个 char 变量使用同一字符 X 初始化: 1234567891011121314151617181920212223242526272829303132333435363738394041424344454647484950515253545556using System;using System.Text;class charType{ static void Main() { char[] chars = new char[7]; chars[0] = 'X'; // Character literal chars[1] = '\x0058'; // Hexadecimal chars[2] = '\u0058'; // Unicode chars[3] = (char)88; // Cast from integral type chars[4] = (char)0x0058; // 转换自16进制整型 chars[5] = '\x5927'; //汉字 大 的Unicode编码 chars[6] = '大'; //汉字 大 foreach (char c in chars) { Console.Write(c + " "); } Console.WriteLine(); //获取汉字“大”的编码。保存为一个字节序列 byte[] bytes = Encoding.Unicode.GetBytes("大"); //得到编码的10进制字节数据 [39][89] Console.WriteLine("[{0}][{1}]", bytes[0], bytes[1]); //得到编码的16进制字节数据 [27][59] ,显示是litte-Endian方式 Console.WriteLine("[{0:X2}][{1:X2}]", bytes[0], bytes[1]); //判断 大端 Big-Endian 小端 Little-Endian if (BitConverter.IsLittleEndian) Array.Reverse(bytes); // 转换为 大端模式 结果为 [59][27] //输出 [59][27] Console.WriteLine("[{0:X2}][{1:X2}]", bytes[0], bytes[1]); Console.ReadKey(); } //得到字符串中个字符(char)的Unicode编码 protected static string GetUnicode(string text) { string result = ""; for (int i = 0; i < text.Length; i++) { if ((int)text[i] > 32 && (int)text[i] < 127) { result += text[i].ToString(); } else result += string.Format("\\u{0:x4}", (int)text[i]); } return result; }} 输出结果 X X X X X 大 大 [39][89] [27][59] [59][27] 高级主题:解析字符串中字符的16进制编码(Unicode)123456789101112131415using System;public class Program { static void Main (string[] args) { string input = "你好 大世界!Hello World!"; for (int i = 0; i < input.Length; i++) { // 占位符 方式 Console.Write ("{0} {1} ", input[i], ((int) input[i]).ToString ("X")); // 字符串内插 $ 方式 Console.Write ($"{input[i]} {((int)input[i]).ToString("X")} "); } System.Console.WriteLine (); Console.ReadKey (); }} 输出结果 你 4F60 好 597D 20 大 5927 世 4E16 界 754C ! FF01 H 48 e 65 l 6C l 6C o 6F 20 W 57 o 6F r 72 l 6C d 64 ! 21 高级主题:大端 Big-Endian 小端 Little-Endian不同的计算机结构采用不同的字节顺序存储数据。” Big-endian”表示最大的有效字节位于单词的左端。” Little-endian”表示最大的有效字节位于单词的右端。 另以一种解释 Little-Endian 就是低位字节排放在内存的低地址端,高位字节排放在内存的高地址端。 Big-Endian 就是高位字节排放在内存的低地址端,低位字节排放在内存的高地址端。 例子,比如数字0x12 34 56 78在内存中的表示形式为: 大端模式: 低地址 -----------------> 高地址 0x12 | 0x34 | 0x56 | 0x78 小端模式: 低地址 ------------------> 高地址 0x78 | 0x56 | 0x34 | 0x12 可见,大端模式和字符串的存储模式类似。 具体例子:汉字“大”(\u5927)的Unicode编码在 Little-endian 模式(以及 Big-endian 模式)CPU内存中的存放方式(假设从地址0x4000开始存放)为: 内存地址 小端模式存放内容 大端模式存放内容 0x4000 0x27 0x59 0x4001 0x59 0x27 转义序列 escape sequence 由反斜杠 ( \ ) 后接字母或数字组合构成的字符组合称为“转义序列”。要在字符常量中表示换行符,单引号或某些其他字符,你必须使用转义序列。 转义序列被视为单个字符,因此,它是有效的字符常量。 转义序列通常用于指定操作,例如终端和打印机上的回车和制表符移动。它们还用于提供非打印字符的文本表现形式和通常具有特殊意义的字符,例如双引号 ( “ )。 下表列出 ANSI 转义序列以及它们所表示的内容。 请注意,在字符序列将被错误解释为三元组的情况下,前接反斜杠的问号 ( \?) 指定文本问号。 转义序列 含义(字符名称) Unicode Encoding(Unicode 编码) \’ 单引号 0x0027 \” 双引号 0x0022 \\ 反斜杠 0x005C \? 文本问号 \0 Null 0x0000 \a Alert 或 Bell 铃声(提醒) 0x0007 \b Backspace 退格 0x0008 \f 换页 0x000C \n 换行 0x000A \r 回车 0x000D \t 水平制表符 0x0009 \v 垂直制表符 0x000B \ ooo 在八进制表示法的 ASCII 字符 \uxxxx 16进制Unicode字符,Unicode 转义序列 \u0041 = “A” \x [h][h][h]h 16进制Unicode字符,(前三个占位符可选),\uxxxx的长度可变版本。除长度可变外,Unicode 转义序列与“\u”类似。 \u3A \x hh 以十六进制表示法的 ASCII 字符 \x hhhh 十六进制表示法的 Unicode 字符(如果此转义序列用于宽字符常量或 Unicode 字符串文本)。以十六进制表示法,则此转义序列用于常量宽字符或 Unicode 字符串的 Unicode 字符。例如,WCHAR f = L’\x4e00’ 或 WCHAR b[] = L”The Chinese character for one is \x4e00”。 \x0041 = “A” \Unnnnnnnn U代理项对的 Unicode 转义序列。 \Unnnnnnnn 字符串常规字符串 regular 常规字符串 123456789101112string columns = "Column 1\tColumn 2\tColumn 3";//Output: Column 1 Column 2 Column 3string rows = "Row 1\r\nRow 2\r\nRow 3";/* Output: Row 1 Row 2 Row 3*/string title = "\"The \u00C6olean Harp\", by Samuel Taylor Coleridge";//Output: "The Æolean Harp", by Samuel Taylor Coleridge @ 逐字字符串12345678910111213141516171819202122232425262728293031string filePath = @"C:\Users\scoleridge\Documents\";//Output: C:\Users\scoleridge\Documents\string text = @"My pensive SARA ! thy soft cheek reclined Thus on mine arm, most soothing sweet it is To sit beside our Cot,...";/* Output:My pensive SARA ! thy soft cheek reclined Thus on mine arm, most soothing sweet it is To sit beside our Cot,... */System.Console.Write (@"begin /\ / \ / \ / \ /________\end");/*输出begin /\ / \ / \ / \ /________\end */string quote = @"Her name was ""Sara.""";//Output: Her name was "Sara." $ 字符串内插 借助==字符串内插==,可以将字符串中的占位符替换成字符串变量的值。 在 C# 6 中,我们最终实现了这种样式的字符串内插。 可以在字符串前面使用 $,以指明应使用变量/表达式替换相应的值。 在低于 C# 6 的版本中,使用 System.String.Format 实现字符串内插。 虽然这样做是可行的,但由于要用到编号占位符,因此加大了读取难度且过程更为冗长。 先决条件 使用==字符串内插==,必须将计算机设置为运行 ==.Net Core==。 字符串内插简介 使用 System.String.Format在字符串中指定要被字符串后面的参数替换的“==占位符==”。 例如: 1234var firstName = "Matt";var lastName = "Groves";var str = String.Format("My name is {0} {1}", firstName, lastName);Console.WriteLine(str); 输出结果 “My name is Matt Groves” 在 C# 6 中,定义内插字符串的方式为,在内插字符串前面添加 ==$== 符号,然后直接在字符串中使用变量,而不使用 String.Format。 例如: 1234var firstName = "Matt";var lastName = "Groves";var str = $"My name is {firstName} {lastName}";Console.WriteLine(str); 输出结果 “My name is Matt Groves” 不必局限于变量。 ==可以在括号内使用任意表达式==。 例如: 123for(var i = 0; i < 5; i++) { Console.WriteLine($"This is line number {i + 1}");} 输出结果 This is line number 1 This is line number 2 This is line number 3 This is line number 4 This is line number 5 字符串内插的工作方式 在后台,编译器将此类字符串内插语法转换成 String.Format。 因此,可以执行之前使用 String.Format 执行的相同操作。 例如,可以添加填充和数值格式: 123456var rand = new Random();for(var i = 998; i < 1005; i++){ var randomDecimal = rand.NextDouble() * 10000; Console.WriteLine($"{i, -10} {randomDecimal, 6:N2}");} 输出结果 998 5,177.67 999 6,719.30 1000 9,910.61 1001 529.34 1002 1,349.86 1003 2,660.82 1004 6,227.77 字符串转字符数组123456789101112131415161718192021using System;class TestRef { static void Main () { string a = "Hello World! 你好 大世界"; char[] b = a.ToCharArray (); for (int i = 0; i < b.Length; i++) { Console.Write (b[i]); } // 输出 Hello World! 你好 大世界 Console.WriteLine (); Array.Reverse (b); for (int i = 0; i < b.Length; i++) { Console.Write (b[i]); } // 输出 界世大 好你 !dlroW olleH System.Console.ReadKey (); }} 字符串数组123456//只有在使用字符串数组初始化字符串时,才能使用 new 运算符串讲字符串对象string[] xx = new string[8];xx[0] = "aaaa";xx[1] = "bbbb";System.Console.WriteLine(xx[0]); //输出 aaaaSystem.Console.WriteLine(xx[1]); //输出 bbbb 字符串对象的不可变性123456789string text;System.Console.Write ("Enter text: ");text = System.Console.ReadLine ();// UNEXPECTED: Does not convert text to uppercasetext.ToUpper ();System.Console.WriteLine (text); 输出结果 Enter text: This is a test of the emergency broadcast system. This is a test of the emergency broadcast system. 字符串对象是“==不可变的==”:它们在创建后无法更改。 看起来是在修改字符串的所有 String 方法和 C# 运算符实际上都是在新的字符串对象中返回结果。 因此,text.ToUpper()不会改变 text 的内容为大写,而是返回了一个新的字符串,它需要保存到变量中,或者直接传给System.Console.WriteLine()。下面代码是改进后的: 12345string text, uppercase;System.Console.Write ("Enter text: ");text = System.Console.ReadLine (); // Return a new string in uppercaseuppercase = text.ToUpper ();System.Console.WriteLine (uppercase); 输出结果 Enter text: This is a test of the emergency broadcast system. THIS IS A TEST OF THE EMERGENCY BROADCAST SYSTEM. 访问单个字符 可以使用包含索引值的数组表示法来获取对单个字符的只读访问权限,如下面的示例中所示: 1234567string s5 = "Printing backwards";for (int i = 0; i < s5.Length; i++){ System.Console.Write(s5[s5.Length - i - 1]);}// Output: "sdrawkcab gnitnirP" 如果 String 方法不提供修改字符串中的各个字符所需的功能,可以使用 ==StringBuilder== 对象“就地”修改各个字符,再新建字符串来使用 StringBuilder 方法存储结果。 在下面的示例中,假定必须以特定方式修改原始字符串,然后存储结果以供未来使用: 1234567891011121314string question = "hOW DOES mICROSOFT wORD DEAL WITH THE cAPS lOCK KEY?";System.Text.StringBuilder sb = new System.Text.StringBuilder(question);for (int j = 0; j < sb.Length; j++){ if (System.Char.IsLower(sb[j]) == true) sb[j] = System.Char.ToUpper(sb[j]); else if (System.Char.IsUpper(sb[j]) == true) sb[j] = System.Char.ToLower(sb[j]);}// Store the new string.string corrected = sb.ToString();System.Console.WriteLine(corrected);// Output: How does Microsoft Word deal with the Caps Lock key? 使用 StringBuilder 快速创建字符串 .NET 中的字符串操作进行了高度的优化,在大多数情况下不会显著影响性能。 但是,在某些情况下(例如,执行数百次或数千次的紧密循环),字符串操作可能影响性能。 StringBuilder 类创建字符串缓冲区,用于在程序执行多个字符串操控时提升性能。 使用 StringBuilder 字符串,还可以重新分配各个字符,而内置字符串数据类型则不支持这样做。 例如,此代码更改字符串的内容,而无需创建新的字符串: 123456System.Text.StringBuilder sb = new System.Text.StringBuilder("Rat: the ideal pet");sb[0] = 'C';System.Console.WriteLine(sb.ToString());System.Console.ReadLine();//Outputs Cat: the ideal pet 在以下示例中,StringBuilder 对象用于通过一组数字类型创建字符串: 12345678910111213141516171819class TestStringBuilder{ static void Main() { System.Text.StringBuilder sb = new System.Text.StringBuilder(); // Create a string composed of numbers 0 - 9 for (int i = 0; i < 10; i++) { sb.Append(i.ToString()); } System.Console.WriteLine(sb); // displays 0123456789 // Copy one character of the string (not possible with a System.String) sb[0] = sb[9]; System.Console.WriteLine(sb); // displays 9123456789 }} Null 字符串和空字符串 空字符串是包含零个字符的 System.String 对象实例。 空字符串常用在各种编程方案中,表示空文本字段。 可以对空字符串调用方法,因为它们是有效的 System.String 对象。 对空字符串进行了初始化,如下所示: 1string s = String.Empty; 相比较而言,null 字符串并不指 System.String 对象实例,只要尝试对 null 字符串调用方法,都会引发 NullReferenceException。 但是,可以在串联和与其他字符串的比较操作中使用 null 字符串。 以下示例说明了对 null 字符串的引用会引发和不会引发意外的某些情况: 12345678910111213141516171819202122232425262728293031323334static void Main(){ string str = "hello"; string nullStr = null; string emptyStr = String.Empty; string tempStr = str + nullStr; // Output of the following line: hello Console.WriteLine(tempStr); bool b = (emptyStr == nullStr); // Output of the following line: False Console.WriteLine(b); // The following line creates a new empty string. string newStr = emptyStr + nullStr; // Null strings and empty strings behave differently. The following // two lines display 0. Console.WriteLine(emptyStr.Length); Console.WriteLine(newStr.Length); // The following line raises a NullReferenceException. //Console.WriteLine(nullStr.Length); // The null character can be displayed and counted, like other chars. string s1 = "\x0" + "abc"; string s2 = "abc" + "\x0"; // Output of the following line: * abc* Console.WriteLine("*" + s1 + "*"); // Output of the following line: *abc * Console.WriteLine("*" + s2 + "*"); // Output of the following line: 4 Console.WriteLine(s2.Length);} 比较字符串1234567string sCompare = "h";int result = string.Compare (sCompare, "a");System.Console.WriteLine (result); //输出 1 "h" > "a" result = string.Compare (sCompare, "h", true);System.Console.WriteLine (result); //输出 0 "h" = "h" result = string.Compare (sCompare, "x", true);System.Console.WriteLine (result); //输出 -1 "h" < "x" 子字符串12345678string s3 = "Visual C# Express";System.Console.WriteLine (s3.Substring (7, 2)); // Output: "C#"System.Console.WriteLine (s3.Replace ("C#", "Basic")); // Output: "Visual Basic Express"// Index values are zero-basedint index = s3.IndexOf ("C"); // index = 7 null 和 void、 null 值表明变量不引用任何有效的对象。void 表示没有类型、或者没有任何值。 null null 可以作为字符串类型的字面量。null 值只能赋给引用类型、指针类型和可空类型。变量设为null,会显式地设置引用,使它不指向任何位置。 null 不等于 “” 。”” 意味变量有一个叫做“空字符串”的值。null 以为这变量无任何值。 void 指定为 void 类型就无需传递任何数据了。void 本质上并不是一个数据类型,知识用于指出没有数据类型这一事实。 高级主题:隐式类型的局部变量 C# 3.0增加了上下文关键字 var 来声明隐式类型的局部变量。该变量在编译时仍然会接收一个类型,但该类型是由编译器提供的。 虽然允许使用 var 取代显示的数据类型,但是在数据类型已知的情况下,最好不要使用 var。 C# 3.0添加 var 的目的是支持匿名类型。匿名类型是在方法内部动态声明数据类型,而不是通过显式的类定义来生命的,如下例: 12345678910111213141516171819202122232425262728using System;using System.Text;public class SamplesString { public static void Main () { var patent1 = new { Title = "Bifocals", YearOfPublication = "1784" }; var patent2 = new { Title = "Phonograph", YearOfPublication = "1877" }; System.Console.WriteLine ("{0} ({1})", patent1.Title, patent1.YearOfPublication); System.Console.WriteLine ("{0} ({1})", patent2.Title, patent2.YearOfPublication); Console.ReadKey (); }} 输出结果 Bifocals (1784) Phonograph (1877) var 声明局部变量的各种方式 123456789101112131415161718192021// i is compiled as an intvar i = 5;// s is compiled as a stringvar s = "Hello";// a is compiled as int[]var a = new[] { 0, 1, 2 };// expr is compiled as IEnumerable<Customer>// or perhaps IQueryable<Customer>var expr = from c in customers where c.City == "London" select c;// anon is compiled as an anonymous typevar anon = new { Name = "Terry", Age = 34 };// list is compiled as List<int>var list = new List<int>(); 在以下上下文中,使用 var 关键字 在 for 初始化语句中。 1for(var x = 1; x < 10; x++) 在 foreach 初始化语句中。 1foreach(var item in list){...} 在 using 域间中。 1using (var file = new StreamReader("C:\\myfile.txt")) {...} 可空修饰符 可以为null的类型 ?C# 2.0 开始引入的这个特性,没有值表示“未定义”的意思。 可通过以下两种方式之一声明可为 null 的类型: System.Nullable<T> variable T? variable T 是可以为 null 的类型的基础类型。 T 可以是包括 struct 在内的任意值类型;它不能是引用类型。 可为 null 的类型示例 int? 任何值类型都可用作 作为null的类型 的基础。例如: 12345int? i = 10;double? d1 = 3.14;bool? flag = null;char? letter = 'a';int?[] arr = new int?[10]; 可为 null 的类型的成员 可以为 null 的类型的每个实例都有两个公共只读属性: ==HasValue== 类型为 bool。 如果该变量包含非 null 值,则将其设置为 true。 ==Value== 与基础类型相同。 如果 HasValue 为 true,则 Value 包含有意义的值。 如果 HasValue 是 false,则访问 Value 将引发 InvalidOperationException。 将 null 赋给值类型,在数据库编程中尤其有用。在数据表中,经常出现值类型的列允许为空的情况。除非允许包含 null 值,否则在C#代码中检索这些列并将他们的值赋给对应的字段会出现问题。可控修饰符妥善地解决了这个问题。 高级主题:null 合并运算符 ?? ?? 运算符定义一个默认值,若将一个可为 null 的类型赋给不可为 null 的类型,则会返回该值。 ?? 运算符称作 null 合并运算符 (空接合操作符)。 如果此运算符的左操作数不为 null,则此运算符将返回左操作数;否则返回右操作数。 12345int? x = null;// Set y to the value of x if x is NOT null; otherwise,// if x == null, set y to -1.int y = x ?? -1; 空接合操作符可以“链接”。例如,对于表达式 x ?? y ?? z ,如果 x 不为 null 则返回 x ;否则,如果 y 不为 null 则返回 y;否则返回 z。 高级主题:NULL条件运算符 ?.用于在执行成员访问 ==(?.)== 或索引 ==(?[)== ==操作之前==,测试是否存在 NULL。 这些运算符可帮助编写更少的代码来处理 null 检查,尤其是对于下降到数据结构。 12345678int? length = customers?.Length; // null if customers is null Customer first = customers?[0]; // null if customers is null // null if customers, the first customer, or Orders is nullint? count = customers?[0]?.Orders?.Count(); // ?. 结合 ??int? len = customers?.Length ?? 0; 高级主题:条件运算符 ?:条件运算符 ?: 根据 Boolean 表达式的值返回两个值之一。下面是条件运算符的语法。 condition ? first_expression : second_expression; condition 的计算结果必须为 true 或 false。 如果 condition 为 true,则将计算 first_expression 并使其成为结果。 如果 condition 为 false,则将计算 second_expression 并使其成为结果。 只计算两个表达式之一。first_expression 和 second_expression 的类型必须相同,或者必须存在从一种类型到另一种类型的隐式转换。 你可通过使用条件运算符表达可能更确切地要求 if-else 构造的计算。 例如,以下代码首先使用 if 语句,然后使用条件运算符将整数分类为正整数或负整数。 1234567891011int input = Convert.ToInt32(Console.ReadLine());string classify;// if-else construction.if (input > 0) classify = "positive";else classify = "negative";// ?: conditional operator.classify = (input > 0) ? "positive" : "negative"; 条件运算符为右联运算符。 表达式 a ? b : c ? d : e 作为 a ? b : (c ? d : e) 而非 (a ? b : c) ? d : e 进行计算。 无法重载条件运算符。 类型的分类值类型C# 中有两种类型:==引用类型==和==值类型==。 引用类型的变量存储对其数据(对象)的引用,而值类型的变量直接包含其数据。 对于引用类型,两种变量可引用同一对象;因此,对一个变量执行的操作会影响另一个变量所引用的对象。 对于值类型,每个变量都具有其自己的数据副本,对一个变量执行的操作不会影响另一个变量(ref 和 out 参数变量除外,请参阅 ref 和 out 参数修饰符)。 值类型直接包含值,变量引用的位置就是值在内存中的实际存储位置。 将一个变量的值赋给另一个变量会导致在新变量的位置创建原始变量值的一个内存副本。 类似的,将值类型的示例传给方法,如 Console.WriteLine(),也会产生一个内存副本。 由于值类型需要创建内存副本,因此定义时不要让它们占用太多内存(通常应该给小于16字节)。 引用类型 引用类型的变量存储的时对数据存储位置的引用,而不是直接存储数据。 数据类型 转换显式转换(explicit conversions | casts | 强制转换) 显式转换需要强制转换运算符。在==转换中可能丢失信息时或在出于其他原因转换可能不成功==时,必须进行强制转换。 典型的示例包括从数值到精度较低或范围较小的类型的转换和从基类实例到派生类的转换。 如果进行转换可能会导致信息丢失,则编译器会要求执行显式转换,显式转换也称为强制转换。 强制转换是显式告知编译器你打算进行转换且你知道可能会发生数据丢失的一种方式。 若要执行强制转换,请在要转换的值或变量前面的括号中指定要强制转换到的类型。下面的程序将 double 强制转换为 int。==如不强制转换则该程序不会进行编译==。 123456789101112class Test{ static void Main() { double x = 1234.7; int a; // Cast double to int. a = (int)x; System.Console.WriteLine(a); }}// Output: 1234 有关支持的显式数值转换的列表,请参阅显式数值转换表。 高级主题:checked 和 unchecked 转换 在 cheecked 块内,如果在运行时发生一次溢出的赋值,就会引发异常。 123456789101112131415using System;using System.Text;public class Program { public static void Main () { checked { // int.MaxValue equals 2147483647 int n = int.MaxValue; n = n + 1; System.Console.WriteLine (n); } Console.ReadKey (); }} 输出结果 未经处理的异常: System.OverflowException: 算术运算导致溢出。 在 Program.Main() unchecked 块,强制不进行一处检查,不会为块中溢出的赋值引发异常。 123456789101112131415using System;using System.Text;public class Program { public static void Main () { unchecked { // int.MaxValue equals 2147483647 int n = int.MaxValue; n = n + 1; System.Console.WriteLine (n); } Console.ReadKey (); }} 输出结果 -2147483648 隐式转换(implicit conversions) 由于该转换是一种类型==安全的转换,不会导致数据丢失==,因此不需要任何特殊的语法。 对于内置数值类型,如果要存储的值==无需截断或四舍五入==即可适应变量,则可以进行隐式转换。 例如,long 类型的变量(8 字节整数)能够存储 int(在 32 位计算机上为 4 字节)可存储的任何值。 隐式转换无需使用转换操作符 12int intNumber = 31416;long longNumber = intNumber; 隐式转换 也可以强制添加转换操作符 12int intNumber = 31416;long longNumber = (long) intNumber; 不使用转型操作符的类型转换 ==字符串到数值类型== 转换,需要使用 Parse()/TryParse() 这样的方法。每个数值类型都包含 Parse()/TryParse() 方法,它允许将字符串还换成对应的数值类型。 例子 12string text = "9.11E-31";float kgElectronMass = float.Parse(text); 还可以利用 ==特殊类型System.Convert== 将一种类型转换成另一种类型。不过,System.Convert只支持预定义类型,而且是不可扩展的。它允许从任何==基元(基本)类型==(bool, char, sbyte, short, int, long, ushort, uint, ulong, float, double, decimal, DateTime, and string)转换到任何其它基元(基本)类型。参考 MSDN Convert.aspx) 类。 例子 123string middleCText = "261.626";double middleC = System.Convert.ToDouble(middleCText);bool boolean = System.Convert.ToBoolean(middleC); 除了以上方法,所有类型都支持==ToString()方法== 转换,可以用它得到一个类型的值的字符串表示。 例子 1234567bool boolean = true;string text = boolean.ToString();System.Console.WriteLine(text); // Display "True"double dVal = -1.123456;text=dVal.ToString();System.Console.WriteLine(dVal);// Display "-1.123456" 高级主题:TryParse()从 C# 2.0 开始,所有基元数据数据类型都包含静态 TryParse() 方法。该方法与 Parse() 非常相似,知识在缓缓失败的清康熙,它不引发异常,而是返回 false ,如下示例: 12345678910111213141516double number;string input;System.Console.Write("Enter a number: ");input = System.Console.ReadLine();if (double.TryParse(input, out number)){ // Converted correctly, now use number // ...}else{ System.Console.WriteLine( "The text entered was not a valid number.");} 高级主题:字符串转换为数字 字符串转数字的几种方法: Parse 转换对象必须是组成合适数值类型(int、long、ulong、float、decimal 等)的字符。(例如:Int32.Parse(“-15”)) TryParse 转换对象必须是组成合适数值类型(int、long、ulong、float、decimal 等)的字符。(例如:Int32.TryParse(“-15”, out j)) Convert.ToInt32 针对各种数值类型(int、long、float等,例如:Convert.ToInt32(“-15”)) 如果你具有字符串,则调用 TryParse 方法(例如 int.TryParse(“11”))会稍微更加高效且简单。 使用 Convert 方法对于实现 IConvertible 的常规对象更有用。 可以对预期字符串会包含的数值类型(如 ==System.Int32== 类型)使用 Parse 或 TryParse 方法。 Convert.ToUInt32 方法在内部使用 Parse。 如果字符串的格式无效,则 Parse 会引发异常,而 TryParse 会返回 false。 示例 Parse 和 TryParse 例:System.Int32.Parse ==Parse 和 TryParse== 方法会忽略字符串开头和末尾的空格,但所有其他字符必须是组成合适数值类型(int、long、ulong、float、decimal 等)的字符。 组成数字的字符中的任何空格都会导致错误。 例如,可以使用 decimal.TryParse 分析“10”、“10.3”、“ 10 ”,但不能使用此方法分析从“10X”、“1 0”(注意空格)、“10 .3”(注意空格)、“10e1”(float.TryParse 在此处适用)等中分析出 10。 下面的示例演示了对 Parse 和 TryParse 的成功调用和不成功的调用。 1234567891011121314151617181920212223242526272829303132333435363738394041424344using System;using System.Collections;using System.Collections.Generic;using System.Linq;public class Program { public static void Main () { int numVal = Int32.Parse ("-105"); Console.WriteLine (numVal); // Output: -105 double dVal = Double.Parse("105.12345"); Console.WriteLine(dVal); // 输出 105.12345 // TryParse returns true if the conversion succeeded // and stores the result in j. int j; if (Int32.TryParse ("-105", out j)) Console.WriteLine (j); else Console.WriteLine ("String could not be parsed."); // Output: -105 try { int m = Int32.Parse ("abc"); } catch (FormatException e) { Console.WriteLine (e.Message); } // Output: Input string was not in a correct format. string inputString = "abc"; int numValue; bool parsed = Int32.TryParse (inputString, out numValue); if (!parsed) Console.WriteLine ("Int32.TryParse could not parse '{0}' to an int.\n", inputString); // Output: Int32.TryParse could not parse 'abc' to an int. Console.ReadKey (); }} 示例 Convert 例:System.Convert.ToInt32 下表列出了 Convert 类中可使用的一些方法。 数值类型 方法 decimal ToDecimal(String) float ToSingle(String) double ToDouble(String) short ToInt16(String) int ToInt32(String) long ToInt64(String) ushort ToUInt16(String) uint ToUInt32(String) ulong ToUInt64(String) 此示例调用 Convert.ToInt32(String) 方法将输入的 string 转换为 int。 代码将捕获此方法可能引发的最常见的两个异常:FormatException 和 OverflowException。 如果该数字可以递增而不溢出整数存储位置,则程序使结果加上 1 并打印输出。 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960using System;using System.Collections;using System.Collections.Generic;using System.Linq;public class Program { static void Main (string[] args) { int numVal = -1; bool repeat = true; while (repeat) { Console.WriteLine ("Enter a number between −2,147,483,648 and +2,147,483,647 (inclusive)."); string input = Console.ReadLine (); // ToInt32 can throw FormatException or OverflowException. try { numVal = Convert.ToInt32 (input); } catch (FormatException e) { Console.WriteLine ("Input string is not a sequence of digits."); } catch (OverflowException e) { Console.WriteLine ("The number cannot fit in an Int32."); } finally { if (numVal < Int32.MaxValue) { Console.WriteLine ("The new value is {0}", numVal + 1); } else { Console.WriteLine ("numVal cannot be incremented beyond its current value"); } } Console.WriteLine ("Go again? Y/N"); string go = Console.ReadLine (); if (go == "Y" || go == "y") { repeat = true; } else { repeat = false; } } // Keep the console open in debug mode. Console.WriteLine ("Press any key to exit."); Console.ReadKey (); } // Sample Output: // Enter a number between -2,147,483,648 and +2,147,483,647 (inclusive). // 473 // The new value is 474 // Go again? Y/N // y // Enter a number between -2,147,483,648 and +2,147,483,647 (inclusive). // 2147483647 // numVal cannot be incremented beyond its current value // Go again? Y/N // Y // Enter a number between -2,147,483,648 and +2,147,483,647 (inclusive). // -1000 // The new value is -999 // Go again? Y/N // n // Press any key to exit.} 高级主题:字节数组转换为数字 BitConverter 此示例演示如何使用 BitConverter 类将字节数组转换为 int 然后又转换回字节数组。 例如,在从网络读取字节之后,可能需要将字节转换为内置数据类型。 除了示例中的 ToInt32(Byte[],Int32) 方法之外,下表还列出了 BitConverter 类中将字节(来自字节数组)转换为其他内置类型的方法。 返回类型 方法 bool ToBoolean(Byte[],Int32) char ToChar(Byte[],Int32) double ToDouble(Byte[],Int32) short ToInt16(Byte[],Int32) int ToInt32(Byte[],Int32) long ToInt64(Byte[],Int32) float ToSingle(Byte[],Int32) ushort ToUInt16(Byte[],Int32) uint ToUInt32(Byte[],Int32) ulong ToUInt64(Byte[],Int32) Returns a 32-bit signed integer converted from four bytes at a specified position in a byte array. 1public static int ToInt32 (byte[] value, int startIndex); 示例 BitConverter.ToInt32 此示例初始化字节数组,并在计算机体系结构为 ==little-endian==(即首先存储最低有效字节)的情况下==反转数组==,然后调用 ToInt32(Byte[],Int32) 方法以将数组中的四个字节转换为 int。 ToInt32(Byte[],Int32) 的第二个参数指定字节数组的起始索引。 12345678910byte[] bytes = { 0, 0, 0, 25 };// If the system architecture is little-endian (that is, little end first),// reverse the byte array.if (BitConverter.IsLittleEndian) Array.Reverse(bytes);int i = BitConverter.ToInt32(bytes, 0);Console.WriteLine("int: {0}", i);// Output: int: 25 示例 BitConverter.GetBytes 在本示例中,将调用 BitConverter 类的 GetBytes(Int32) 方法,将 int 转换为字节数组。 123byte[] bytes = BitConverter.GetBytes(201805978);Console.WriteLine("byte array: " + BitConverter.ToString(bytes));// Output: byte array: 9A-50-07-0C 高级主题:十六进制字符串与数值类型之间转换 以下示例演示如何执行下列任务: 获取字符串中每个字符的十六进制值。 获取与十六进制字符串中的每个值对应的 char。 将十六进制 string 转换为 int。 将十六进制 string 转换为 float。 将字节数组转换为十六进制 string。> - 示例 解析字符得到16进制值此示例输出 string 中每个字符的十六进制值。 123456789101112131415using System;public class Program { static void Main (string[] args) { string input = "你好 大世界!Hello World!"; for (int i = 0; i < input.Length; i++) { // 占位符 方式 //Console.Write ("{0} {1} ", input[i], ((int) input[i]).ToString ("X")); // 字符串内插 $ 方式 Console.Write ($"{input[i]} {((int)input[i]).ToString("X")} "); } System.Console.WriteLine (); Console.ReadKey (); }} 你 4F60 好 597D 20 大 5927 世 4E16 界 754C ! FF01 H 48 e 65 l 6C l 6C o 6F 20 W 57 o 6F r 72 l 6C d 64 ! 21 示例 从16进制值得到对应字符此示例分析十六进制值的 string 并输出对应于每个十六进制值的字符。 首先,调用 Split(Char[]) 方法以获取每个十六进制值作为数组中的单个 string。 然后,调用 ToInt32(String, Int32)将十六进制值转换为表示为 int 的十进制值。示例中演示了 2 种不同方法,用于获取对应于该字符代码的字符。 第 1 种方法是使用 ConvertFromUtf32(Int32),它将对应于整型参数的字符作为 string 返回。 第 2 种方法是将 int 显式转换为 char。 1234567891011121314151617181920212223using System;public class Program { static void Main (string[] args) { //将指定的 Unicode 码位转换为 UTF-16 编码字符串。 string hexValues = "5927 20 48 65 6C 6C 6F 20 57 6F 72 6C 64 21"; // 大 Hello World! string[] hexValuesSplit = hexValues.Split(' '); for (int i = 0; i < hexValuesSplit.Length; i++) { // 将 16 进制数值 转换为 整型(10进制) int value = Convert.ToInt32(hexValuesSplit[i], 16); // 得到 编码值对应的 字符 | 将指定的 Unicode 码位转换为 UTF-16 编码字符串。 string stringValule = char.ConvertFromUtf32(value); System.Console.Write(stringValule); //一句话表示,有点难于解读 //System.Console.Write(char.ConvertFromUtf32(Convert.ToInt32(hexValuesSplit[i],16))); } System.Console.WriteLine (); Console.ReadKey (); }} 输出结果 大 Hello World! 数组可以将同一类型的多个变量存储在一个数组数据结构中。 通过指定数组的元素类型来声明数组。 type[] arrayName; 数组具有以下属性: 数组可以是一维(==Single-Dimesional==)、多维(==Multidimesional==)或交错(==Jagged==)的。 创建数组实例时,将建立纬度数量和每个纬度的长度。这些值在实例的生存期内无法更改。 交错数组是数组的数组,因此其元素为引用类型且被初始化为null。 数组从零开始编制索引:包含 n 元素的数组从 0 索引到 n-1。 数组元素可以是任何类型,其中包括数组类型。 数组的维数使用 Rank 属性显示数组的维数。 1234// Declare and initialize an array:int[,] theArray = new int[5, 10];// 输出 这是一个 2 维数组。System.Console.WriteLine("这是一个 {0} 维数组。", theArray.Rank); 一维数组数组声明 一维 声明五个整数的一维数组,如以下示例所示: 1int[] array = new int[5]; 此数组包含从 array[0] 到 array[4] 的元素。 new 运算符用于创建数组并将数组元素初始化为其默认值。在此示例中,所有数组元素都将被==初始化为零==。 声明字符串数组 1string[] stringArray = new string[6]; 数组初始化 一维12int[] array1 = new int[] { 1, 3, 5, 7, 9 };string[] weekDays = new string[] { "Sun", "Mon", "Tue", "Wed", "Thu", "Fri", "Sat" }; 声明初始化的缩写(快捷)方式 12int[] array1 = { 1, 3, 5, 7, 9 };string[] weekDays = { "Sun", "Mon", "Tue", "Wed", "Thu", "Fri", "Sat" }; 可以在不初始化的情况下声明数组变量,但必须使用 new 运算符向此变量分配数组。 例如: 123int[] array3;array3 = new int[] { 1, 3, 5, 7, 9 }; // OK//array3 = {1, 3, 5, 7, 9}; // Error 多维数组 多为数组的每一维的大小都必须一致。而 ==交错数组== (jagged array)则不需要。 得到多维数组中某一维的长度,不是使用Length属性,而是使用数组的GetLength()示例方法。 123bool[,,] cells;cells = new bool[2,3,3];System.Console.WriteLine(cells.GetLength(0)); // Displays 2 数组声明 多维1234// 声明创建一个具有四行两列的二维数组。int[,] array = new int[4, 2];// 声明创建一个具有三个维度(4、2 和 3)的数组。int[, ,] array1 = new int[4, 2, 3]; 数组初始化 多维 声明后即可初始化数组,如以下示例所示。 12345678910111213141516171819202122232425262728293031323334353637383940414243// Two-dimensional array.int[,] array2D = new int[,] { { 1, 2 }, { 3, 4 }, { 5, 6 }, { 7, 8 } };// The same array with dimensions specified.int[,] array2Da = new int[4, 2] { { 1, 2 }, { 3, 4 }, { 5, 6 }, { 7, 8 } };// A similar array with string elements.string[,] array2Db = new string[3, 2] { { "one", "two" }, { "three", "four" }, { "five", "six" } };// Three-dimensional array.int[, ,] array3D = new int[,,] { { { 1, 2, 3 }, { 4, 5, 6 } }, { { 7, 8, 9 }, { 10, 11, 12 } } };// The same array with dimensions specified.int[, ,] array3Da = new int[2, 2, 3] { { { 1, 2, 3 }, { 4, 5, 6 } }, { { 7, 8, 9 }, { 10, 11, 12 } } };// Accessing array elements.System.Console.WriteLine(array2D[0, 0]);System.Console.WriteLine(array2D[0, 1]);System.Console.WriteLine(array2D[1, 0]);System.Console.WriteLine(array2D[1, 1]);System.Console.WriteLine(array2D[3, 0]);System.Console.WriteLine(array2Db[1, 0]);System.Console.WriteLine(array3Da[1, 0, 1]);System.Console.WriteLine(array3D[1, 1, 2]);// Getting the total count of elements or the length of a given dimension.var allLength = array3D.Length;var total = 1;for (int i = 0; i < array3D.Rank; i++) { total *= array3D.GetLength(i);}System.Console.WriteLine("{0} equals {1}", allLength, total);// Output:// 1// 2// 3// 4// 7// three// 8// 12// 12 equals 12 交错数组 jagged array 交错数组是元素为数组的数组。 交错数组元素的维度和大小可以不同。 交错数组有时称为“数组的数组”。 123456int[][] cells = { new int[]{1, 0, 2, 0}, new int[]{1, 2, 0}, new int[]{1, 2}, new int[]{1}}; 声明一个具有三个元素的一维数组,其中每个元素都是一维整数数组: 1int[][] jaggedArray = new int[3][]; 必须初始化 jaggedArray 的元素后才可使用它。 可按下方操作初始化元素: 123jaggedArray[0] = new int[5];jaggedArray[1] = new int[4];jaggedArray[2] = new int[2]; 每个元素都是一维整数数组。 第一个元素是由 5 个整数组成的数组,第二个是由 4 个整数组成的数组,而第三个是由 2 个整数组成的数组。 也可使用初始化表达式通过值来填充数组元素,这种情况下不需要数组大小。 例如: 123jaggedArray[0] = new int[] { 1, 3, 5, 7, 9 };jaggedArray[1] = new int[] { 0, 2, 4, 6 };jaggedArray[2] = new int[] { 11, 22 }; 还可在声明数组时将其初始化,如: 12345int[][] jaggedArray2 = new int[][] { new int[] { 1, 3, 5, 7, 9 }, new int[] { 0, 2, 4, 6 }, new int[] { 11, 22 }}; 可以使用下面的缩写形式。 请注意:不能从元素初始化中省略 new 运算符,因为不存在元素的默认初始化: 12345int[][] jaggedArray3 = { new int[] { 1, 3, 5, 7, 9 }, new int[] { 0, 2, 4, 6 }, new int[] { 11, 22 }}; 可以混合使用交错数组和多维数组。 下面声明和初始化一个包含大小不同的三个二维数组元素的一维交错数组。 123456int[][,] jaggedArray4 = new int[3][,] { new int[,] { {1,3}, {5,7} }, new int[,] { {0,2}, {4,6}, {8,10} }, new int[,] { {11,22}, {99,88}, {0,9} } }; 可以如本例所示访问个别元素,示例显示第一个数组的元素 [1,0] 的值(值为 5): 1System.Console.Write("{0}", jaggedArray4[0][1, 0]); 方法 Length 返回包含在交错数组中的数组的数目。例如,假定已声明了前一个数组,则下行返回值 3。 1System.Console.WriteLine(jaggedArray4.Length); 隐式类型的数组 可以创建隐式类型化的数组,其中数组实例的类型通过数组初始值设定项中指定的元素来推断。 针对隐式类型化变量的任何规则也适用于隐式类型化数组。 12345678910111213141516171819202122class ImplicitlyTypedArraySample{ static void Main() { var a = new[] { 1, 10, 100, 1000 }; // int[] var b = new[] { "hello", null, "world" }; // string[] // single-dimension jagged array var c = new[] { new[]{1,2,3,4}, new[]{5,6,7,8} }; // jagged array of strings var d = new[] { new[]{"Luca", "Mads", "Luke", "Dinesh"}, new[]{"Karen", "Suma", "Frances"} }; }} 在上个示例中,请注意对于隐式类型化数组,初始化语句的左侧没有使用方括号。 另请注意,和一维数组一样,通过使用 new [] 来初始化交错数组。 对数组使用循环 for foreach 一维数组 123456int[] numbers = { 4, 5, 6, 1, 2, 3, -2, -1, 0 };foreach (int i in numbers){ System.Console.Write("{0} ", i);}// Output: 4 5 6 1 2 3 -2 -1 0 多维数组 123456789int[,] numbers2D = new int[3, 2] { { 9, 99 }, { 3, 33 }, { 5, 55 } };// Or use the short form:// int[,] numbers2D = { { 9, 99 }, { 3, 33 }, { 5, 55 } };foreach (int i in numbers2D){ System.Console.Write("{0} ", i);}// Output: 9 99 3 33 5 55 交错数组 123456789101112int[][] jaggedArray3 = { new int[] { 1, 3, 5, 7, 9 }, new int[] { 0, 2, 4, 6 }, new int[] { 11, 22 }};foreach (var item in jaggedArray3) { for (int i = 0; i < item.Length; i++) { System.Console.Write ("{0} ", item[i]); }}// 输出:1 3 5 7 9 0 2 4 6 11 22 常见数组编码错误代码中包含双重大括号,hexo无法编译,用转义的话Markdown文件不利阅读,所以采用图片替代。 枚举类型声明定义enum 关键字用于声明枚举,一种包含一组被称为枚举数列表的已命名常数的不同类型。通常最好是直接在命名空间内定义枚举,以便命名空间中的所有类都可以同样方便地访问它。 但是,也可能会在类或结构中嵌套枚举。默认情况下,枚举中每个元素的==基础类型都为 int==。默认情况下,==第一个枚举数具有值 0==,并且每个连续枚举数的值将增加 1。 例如,在以下枚举中, Sat 的值为 0, Sun 的值为 1, Mon 的值为 2,依次类推。 1enum Days {Sat, Sun, Mon, Tue, Wed, Thu, Fri}; 枚举数可以使用初始值设定项来替代默认值,如下面的示例中所示。 1enum Days {Sat=1, Sun, Mon, Tue, Wed, Thu, Fri}; 在此枚举中,强制元素的序列从 1 开始,而不是 0。 但建议包括一个值为 0 的常量。 有关详细信息,请参阅枚举类型。 每个枚举类型都有一个基础类型,该基础类型可以是除 char 外的任何整型类型。 枚举元素的默认基础类型是 int。若要声明另一整型的枚举(如 byte),则请在后跟该类型的标识符后使用冒号,如以下示例所示。 1enum Days : byte {Sat=1, Sun, Mon, Tue, Wed, Thu, Fri}; 枚举的已批准类型有 byte、 sbyte、 short、 ushort、 int、 uint、 long或 ulong。有关可能的类型的完整列表,请参阅 enum(C# 参考)。 基础类型指定为每个枚举数分配多少存储空间。 但要将 enum 类型转换为整型,==则必须使用显示转换==。 例如,以下语句通过使用转换将 Sun 转换为 ,从而将枚举数 赋值为 enum int int类型的变量。 1int x = (int)Days.Sun; 可以为枚举类型的枚举器列表中的元素分配任何值,也==可以使用计算值==,计算因子必须是已经有确定的值的枚举元素,不能包含后面的值未确定的元素: 12345678910111213141516171819202122232425262728293031using System;public class EnumTest{ enum Day { Sunday=6, Monday=3, Tuesday = Sunday + Monday, Wednesday, Thursday, Friday, Saturday }; static void Main() { Console.WriteLine(Day.Sunday); Console.WriteLine(Day.Friday); Console.WriteLine((int)Day.Sunday); Console.WriteLine((int)Day.Monday); Console.WriteLine((int)Day.Tuesday); Console.WriteLine((int)Day.Wednesday); Console.WriteLine((int)Day.Thursday); Console.WriteLine((int)Day.Friday); Console.WriteLine((int)Day.Saturday); Console.ReadKey(); }}/* 输出SundayFriday63910111213*/ 使用 System.Enum 方法来发现和操作枚举值12345678910string s = Enum.GetName(typeof(Days), 16);Console.WriteLine(s);Console.WriteLine("The values of the Day Enum are:");foreach (int i in Enum.GetValues(typeof(Days))) Console.WriteLine(i);Console.WriteLine("The names of the Day Enum are:");foreach (string str in Enum.GetNames(typeof(Days))) Console.WriteLine(str); 输出结果 Thursday The values of the Day Enum are: 0 1 2 4 8 16 32 64 The names of the Day Enum are: None Sunday Monday Tuesday Wednesday Thursday Friday Saturday 高级主题:作为位标志的枚举类型创建==位标志枚举==的方法是,应用 FlagsAttribute 属性并适当定义一些值,以便可以对这些值执行 AND、OR、NOT 和 XOR 按位运算。 在位标志枚举中,包括一个值为零(表示“未设置任何标志”)的命名常量。 如果零值不表示“未设置任何标志”,请勿为标志指定零值。 在以下示例中,定义了名为 Days 枚举的另一个版本。 Days 具有 Flags 属性,且它的每个值都是 2 的若干次幂,指数依次递增。 这样,你就能够创建值为 Days.Tuesday | Days.Thursday 的 Days 变量。 12345678910111213141516[Flags]enum Days{ None = 0x0, Sunday = 0x1, Monday = 0x2, Tuesday = 0x4, Wednesday = 0x8, Thursday = 0x10, Friday = 0x20, Saturday = 0x40}class MyClass{ Days meetingDays = Days.Tuesday | Days.Thursday;} 若要在枚举上设置标志,请使用按位 OR 运算符,如以下示例所示: 12345678910111213// Initialize with two flags using bitwise OR.meetingDays = Days.Tuesday | Days.Thursday;// Set an additional flag using bitwise OR.meetingDays = meetingDays | Days.Friday;Console.WriteLine("Meeting days are {0}", meetingDays);// Output: Meeting days are Tuesday, Thursday, Friday// Remove a flag using bitwise XOR.meetingDays = meetingDays ^ Days.Tuesday;Console.WriteLine("Meeting days are {0}", meetingDays);// Output: Meeting days are Thursday, Friday 若要确定是否设置了特定标志,请使用按位 AND 运算,如以下示例所示: 1234// Test value of flags using bitwise AND.bool test = (meetingDays & Days.Thursday) == Days.Thursday;Console.WriteLine("Thursday {0} a meeting day.", test == true ? "is" : "is not");// Output: Thursday is a meeting day. 结尾]]></content>
<tags>
<tag>C#</tag>
<tag>C#本质论</tag>
<tag>Essential C#</tag>
</tags>
</entry>
<entry>
<title><![CDATA[C#本质论笔记 第1章 C#概述]]></title>
<url>%2F2017%2F12%2F13%2FCSharp%E6%9C%AC%E8%B4%A8%E8%AE%BA%E7%AC%94%E8%AE%B0-%E7%AC%AC1%E7%AB%A0-CSharp%E6%A6%82%E8%BF%B0%2F</url>
<content type="text"><![CDATA[名词解释 CLI Common Language Infrastructure 公共语言基础结构 CIL Common Intermediate Language 公共中间语言 BCL Base Class Library 基础类库 overload 重载:指的是同一个类中有两个或多个名字相同但是参数不同的方法,(注:返回值不能区别函数是否重载),重载没有关键字。 override 重写 过载:指子类对父类中虚函数或抽象函数的“覆盖”(这也就是有些书将过载翻译为覆盖的原因),但是这种“覆盖”和用new关键字来覆盖是有区别的。 new 覆盖:指的是不同类中(基类或派生类)有两个或多个返回类型、方法名、参数都相同,但是方法体不同的方法。但是这种覆盖是一种表面上的覆盖,所以也叫隐藏,被覆盖的父类方法是可以调用得到的。 HelloWorld本书中的例子1234567class HelloWorld{ static void Main() { System.Console.WriteLine("Hello World!"); }} 其他例子123456789using System;class Hello{ static void Main() { Console.WriteLine("Hello World!"); }} VS IDE自动生成例子12345678910111213141516using 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. 形参与实参12345678910111213141516namespace 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 方法。 不带参数的Main12345678class Hello{ static void Main() { System.Console.WriteLine("Hello World!"); System.Console.ReadKey(); }} 带参数的Main12345678class 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 例子12345678910111213class 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 单行多行语句一行包含多条语句1System.Console.WriteLine("1");System.Console.WriteLine("2"); 输出结果 1 2 一条语句跨越多行12System.Console.WriteLine( "Hello World!"); 输出结果 Hello World! 错误示例12System.Console.WriteLine("Hello World!"); 错误提示 HelloWorld.cs(5,31): error CS1010: 常量中有换行符 HelloWorld.cs(6,12): error CS1010: 常量中有换行符 缩进和空白 例子1 12345678class Hello{ static void Main() { System.Console.WriteLine("Hello World!"); System.Console.ReadKey(); }} 例子2 1class Hello{static void Main(){System.Console.WriteLine("Hello World!");System.Console.ReadKey();}} 上面两个例子输出结果相同,对编译器来说无差别。 变量声明与赋值1234567891011121314151617181920212223242526class 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()1234567891011121314151617class 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。为了获取实际字符,需要先将证书转型为字符,代码如下: 代码示例 11234567891011121314class 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 代码示例 21234567891011121314151617181920class 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() 将参数内容(后跟当前行终止符)写入标准输出流,输出后,光标切换到下一行。 123456789101112131415161718class 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. 交换索引占位符和对应变量 12System.Console.WriteLine( "Hello {1}, {0}.", firstName, lastName); 输出结果 Enter your first name: Jon Enter your last name: Snow Hello Snow, Jon. 代码注释1234567891011121314151617181920212223242526class 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 输出结果 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566D:\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反汇编工具 dnSpy (免费) 具备调试功能 ILSpy (免费) dotPeek (免费) 生成代码质量较高,Visual Studio风格界面 .Net Reflector (收费) 为了减少程序被别人轻松反编译,可以考虑使用混淆器(obfuscator)产品。这些混淆器会打开IL代码,将代码加密成一种功能不变但更难于理解的形式。 结尾]]></content>
<tags>
<tag>C#</tag>
<tag>C#本质论</tag>
<tag>Essential C#</tag>
</tags>
</entry>
</search>