(成对的)括号是各种编程语言的核心要素。很多年前就想着写这样一篇专门关于各种括号的技术文章。一直未动笔,因为总想着偷懒,但凡有一个人写了,就无需我动手了。可惜的是,等了十多年,也没有出现比较理想的文章。
一、圆括号()
圆括号的主要有下面5种常见和不常见的使用方法:
1.1 函数、函数的参数及函数调用
圆括号 ( ) 用于约束函数定义、函数的参数列表、函数的调用。
比如:
// 函数体
private void function_name() { }
// 函数体及函数列表
private void function_name(int a,ref int b,out double c) { }
// 函数调用
function_name(10,ref b, out double c);
1.2 用于表达式(子表达式)的计算优先级
对于计算公式来说,括号具有最高优先级。
虽然各种语言具有约定的计算优先级。比如:
int b = c * d + e;
我们知道 * 比 + 具有更高的优先级,因而上述表达式是先乘积,再相加。
然而,这样的表达式写法并不符合工业软件的商业化要求,稍微复杂的表达式很容易出问题,特别不利于代码的维护。严格的表达式写法是:
int c = (c * d) + e;
这样是多余的吗?如果你没有参与过大型商品化工业软件的开发,没有权利反对上述说法。
如果表达式更复杂,应该分段写。比如:
double f = Math.Abs(Math.Sqrt((a * b) + c) - Math.Pow(d, 3.0) + e);
// 某些情况下,也可以写成:
double f1 = Math.Sqrt((a * b) + c);
double f2 = Math.Pow(d, 3.0);
double f3 = e;
double f = Math.Abs(f1 - f2 + f2);
建议你从今天开始培养良好的圆括号 ( ) 使用习惯,你会受益匪浅。
1.3 强制数据转换
数据转换是编程时常用的一种方法。准确无误地定义、使用数据与变量是商业化工业软件的重要标志。实验室的程序会这样写:
var a = 1;
而商业化工业软件一般情况下必须这样写,你必须明确告知自己及团队成员,a 是什么数据类型:
int a = 1;
数据转换的例子很多,这里写一点体会一下:
int a = 16;
// 显式转换,圆括号作为强制类型转换符号
byte b = (byte)a;
好的程序员应该记住:强制转换越少越好!
1.4 关键语句的约束
在一些语句中,圆括号用于约束循环条件、结束条件及新的数据实体等等。
if(a < 10) { ... }
for(int i=0; i<10; i++) { ... }
foreach(int a in list) { ... }
while(i < 10) { ... }
do { ... } while(i < 10);
switch(a) { ... }
try { ... } catch(Exception x) { ... }
using(Log log = new Log()) { ... }
int min = (a < b) ? a : b;

1.5 数据的个性标识
利于搜索某些特定的数据。
int a = (1023);
string name = ("panda");
二、方括号[]
方括号 [ ] 用于数组(指针)、索引器和属性。
2.1 数组 Array
程序员一般都是从数组开始认识方括号的。
// 定义一个(整数)数组,并且给出初值
int[] a = new int[10] { 0,1,2,3,4,5,6,7,8,9 };
// 访问数组的第4个元素,第一个元素从0开始
int first = a[0];
int a4 = a[3];
以上是一维数组,二维及多维数组如下:
// 定义二维(整数数组)
int[,] b = new int[2,10] {
{ 1,2,3,4,5,6,7,8,9,10},
{ 11,12,13,14,15,16,17,18,19,20}
};
// 访问第2列第3行的元素
int u = b[1, 2];
2.2 数据集合 Collections
数据集合,比如列表等,也完全可是使用 方括号[索引] 进行数据元素访问。
// 定义一个(整数列表),并初始化
List<int> c = new List<int>() { 1, 2, 3};
// 访问第2个元素
int c2 = c[1];
2.3 索引器 Indexer
使用索引器可以类似于数组的方式为对象建立索引,访问与赋值相应数据。数组下标可以理解为索引器的一种简化版本的索引器。
// 定义了索引器的类
class SampleIndxer
{
// 可供索引器使用的容器(数组或其他数据类型)
private string[] sampleStrArr = new string[10];
// 创建索引器 ***
public string this[int i]
{
get { return sampleStrArr[i]; }
set { sampleStrArr[i] = value; }
}
}
// 使用索引器访问数据
public void Drive()
{
// 简单索引器测试
SampleIndxer sit = new SampleIndxer();
sit[0] = “测试数据0”;
sit[1] = “测试数据1”;
}
除了 int,你也可以用 string 及其他类型数据 作为索引(值)。
2.4 词典、哈希 Dictionary & Hashtable
方括号也是访问字典类、哈希表数据的一种常用形式。
Hashtable hash = new Hashtable();
hash.Add("number_01", 1);
int va = (int)hash["number_01"];
字典,类似。
// 字典的定义
Dictionary<string, string> app = new Dictionary<string, string>();
// 添加字典元素
app.Add("txt", "notepad.exe");
app.Add("bmp", "paint.exe");
app.Add("dib", "paint.exe");
app.Add("rtf", "wordpad.exe");
app.Add("png", "picture.exe");
// 访问字典元素
string a = app["png"]; // "picture.exe"
2.5 属性定义 Attribute
属性定义提供功能强大的方法以将声明信息与C#代码(类型、方法、属性等)相关联。一旦属性与程序实体关联,即可在运行时使用名为反射的技术对属性进行查询。
属性以两种形式存在:一种是在公共语言运行库的基类库中定义的属性,另一种是可以创建,可以向代码中添加附加信息的自定义属性。此信息可在以后以编程方式检索。
[Serializable]
public class HumanInfo
{
public int Id { get; set; } = -1;
public string Name { get; set; } = "";
public int Age { get; set; } = 0;
public int Gender { get; set; } = 1;
}

三、大括号{}
大括号 { } 在C# 中扮演着几种极其重要的、不同的角色。
一般情况下,看看作者使用大括号的习惯,即可知道其真实的编程水平。
3.1 约定代码层次关系(如定义命名空间、类时使用的大括号)
大括号的首要功能是严格划分程序的层次,维护没有层次的代码是极其昂贵的。一对花括号括起来的部分为一个层次。
大括号划分的层次,也同时限定了局部变量的范围,这个极其!极其!极其!重要!在该层次定义的变量只能用于本对花括号内,而不能用在花括号之外。
下面这段代码,无法通过编译!报错!
for(int i=0; i<10; i++)
{
int x = i * 2;
int y = i * 3;
}
int x = 20;
int y = 30;
应该写成:
for(int i=0; i<10; i++)
{
int x = i * 2;
int y = i * 3;
}
{
int x = 20;
int y = 30;
}
同样的情况,常常出现于 switch,比如下面的代码编译错误 :
switch(a)
{
case 1:
int x = a * 10;
break;
case 2:
int x = a * 20;
break;
}
两个 x 的定义是冲突的。下面的代码就可以:
switch(a)
{
case 1:
{
int x = a * 10;
break;
}
case 2:
{
int x = a * 20;
break;
}
}
有一些编程语言放弃了大括号,其代码的可维护性极其低下,团队开发效率极低,导致无法在商业化的工业软件中获得深度认可。
3.2 表示复合语句(如if、for中的大括号)
这个没什么要说的,看代码吧:
if(a < b)
{
return a;
}
for(int i=0; i<10; i++)
{
x += i;
}
3.3数组、列表等数据集的初始化(元素)
看代码(数组、列表等等均可以这样初始化)。
string[] words = new string[3] { "hello", "world", "!" };
3.4 格式字符串
格式字符串是数据按格式生成的描述。
string result = String.Format("a = {0}", a);
各种数据在输出的时候,都需要按一定格式予以处置。格式化的方法很多,尤其是时间、浮点数等细节较多,建议搜索其他文章阅读。
3.5 内插(插入字符串InsertString)
$ 特殊字符将字符串文本标识为内插字符串。 内插字符串是可能包含内插表达式的字符串文本。 将内插字符串解析为结果字符串时,带有内插表达式的项会替换为表达式结果的字符串表示形式。 此功能在 C# 6 及该语言的更高版本中可用。与使用字符串复合格式设置功能创建格式化字符串相比,字符串内插提供的语法更具可读性,且更加方便。
string name = "Earth";
DateTime date = DateTime.Now;
MessageBox.Show($"Hello, {name}!\ntime:{date}");
四、尖括号<>
尖括号<> 用于指定类、泛型集合 和 xml注释信息。
4.1 泛型
泛型允许我们延迟编写类或者方法中的编程元素的数据类型的规范,直到实际在程序中使用他的时候。(也就是说泛型是可以与任何数据类型一起工作的类或方法)模块内高 内聚,模块间低耦合。在C#中提供了5种泛型:类、结构、接口、委托和方法,前面的4种是类型,而方法是成员。泛型的使用:当我们的类/方法不需要关注调用者传递的实体是什么(公共基类工具类)的时候,就可以使用泛型。
大家可以简单理解泛型是一种数据模板。
// 单泛型
public static void ShowClass<T>(T t) where T : class
{
}
// 多泛型
public static void ShowClass<S, T>(S s, T t) where S : Person
{
}
4.2 XML注释
大家都知道,尖括号<> 是 XML 的基本字符框架。C#中 XML 用于注释。
除了C风格的注释外,C#还可以根据特定的注释自动创建XML格式的文档说明。这些注释都是单行注释,但都以3个斜杠 /// 开头,而不是通常的两个斜杠。在这些注释中,可以把包含类型和类型成员的文档说明的XML标识符放在代码中。编译器可以识别的标识符如下:
标识符 说明
<c> 把行中的文本标记为代码,例如:<c>int i=10;</c>
<code> 把多行标记为代码
<example> 标记为一个代码示例
<exception> 说明一个异常类(编译器要验证其语法)
<include> 包含其他文档说明文件的注释(编译器要验证其语法)
<list>把列表插入到文档说明中
<param> 标记方法的参数(编译器要验证其语法)
<paramref> 表示一个单词是方法的参数(编译器要验证其语法)
<permission> 说明对成员的访问(编译器要验证其语法)
<remarks> 给成员添加描述
<returns> 说明方法的返回值
<see> 提供对另一个参数的交叉引用(编译器要验证其语法)
<seealso> 提供描述中的“参见” 部分(编译器要验证其语法)
<summary> 提供类型或成员的简短小结
<value> 描述属性
/// <summary>
/// 计算 block 实际有效范围(begin...end)
/// block is in {} or {};
/// </summary>
/// <param name="begin"></param>
/// <param name="end"></param>
private void Segment_Region(ref int begin, ref int end)
{
if (begin < 0) begin = 0;
Token tx = Tokens[begin];
while (tx != Tokens[begin] && tx != null && tx.Buffer != "{") tx = tx.Right;
if (tx.Buffer == "{") begin = tx.Right.Id;
if (end <= begin) return;
Token ty = Tokens[end];
while (ty != Tokens[begin] && ty != null && ty.Buffer != "}") ty = ty.Left;
if (ty.Buffer == "}") end = ty.Left.Id;
}
C#编译器可以把XML元素从特定的注释中提取出来,并使用它们生成一个XML文件。
没想到哩哩啦啦写了这么多话,自己都嫌啰嗦,大家凑合看吧。
遗漏的请大家补充则个。