CSharp高效编程摘记

C#
本文总阅读量:
  1. 1. 查询语法(query syntax)的一个用处
  2. 2. 使用具名参数(named parameter)减少重载
  3. 3. 理解几个等同性判断之间的关系
  4. 4. 运行时常量(readonly)和编译期常量(const)

查询语法(query syntax)的一个用处

循环创建集合|数组时,用查询语法(query syntax)会比普通控制流程的结构好点,

主要体现在:

  • 命令式的版本有时非常难以理解,要是没有注释或文档,后续维护人员将要重读整段代码才能进行开发。
  • 另外,查询语法比循环结构能提供更具组合性的API。查询语法将很自然的把代码分解成小块代码,每一块仅仅对序列中元素进行单一的操作。查询语法的延迟执行模型也让开发者能将这些单一的操作组合成多步操作,且在一次遍历序列时完整执行。

例子:用二元组生成坐标,返回的二元组按照其离远点距离的逆序排列

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
private static IEnumerable<Tuple<int, int>> ProduceIndices()
{
var storage = new List<Tuple<int, int>>();

for(int x = 0; x < 100; x++)
for(int y = 0; y < 100; y++)
if(x + y < 100)
storage.Add(Tuple.Create(x, y));

storage.Sort((point1, point2) =>
(
point2.Item1 * point2.Item1 + point2.Item2 * point2.Item2
).CompareTo(
point1.Item1 * point1.Item1 + point1.Item2 * point1.Item2
));
}

private static IEnumerable<Tuple<int, int>> QueryIndices()
{
return from x in Enumerable.Range(0, 100)
from y in Enumerable.Range(0, 100)
where x + y < 100
orderby (x*x + y*y) descending
select Tuple.Create(x, y);
}

使用具名参数(named parameter)减少重载

1
2
3
4
5
6
static void Test2(string firstName,string secondName)
{
Console.WriteLine($"{firstName}{secondName}");
}

Test2(secondName: "78", firstName: "luox");//luox78

理解几个等同性判断之间的关系

当创建自定义类型时(无论是class还是struct),应为类型定义”等同性”的含义。C#提供了4种不同的函数来判断两个对象是否”相等”:

1
2
3
4
public static bool ReferenceEquals(object left, object right);
public static bool Equals(object left, object right);
public virtual bool Equals(object right);
public static bool operator ==(MyClass left, MyClass right);

Object.ReferenceEquals()Object.Equals()这两个系统提供的静态方法,永远都不需要重新定义。

Object.ReferenceEquals()判断的是对象引用,判断的是否拥有同样的对象标识(object identity),所以若将一个值类型与它自身进行比较,方法返回的是false,因为值类型会进行装箱操作,造成引用地址不同。

Object.Equals()对于引用类型默认使用对象标识判断,即跟Object.ReferenceEquals()一样,但对于值类型,因为System.ValueType重写了Object.Equals()方法,所以比较的是值是否相等(主要是struct),但System.ValueType是所有值类型的基类,故实现比较时,用的是反射,效率并不高。

综上所述,自定义类型实现自己的比较方法就比较重要了。

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
public class Student : IEquatable<Student>
{
public int Id { get; set; }
public string Name { get; set; }

public override bool Equals(object obj)
{
if (Object.ReferenceEquals(obj, null))
return false;
if (Object.ReferenceEquals(this, obj))
return true;
if (this.GetType() != obj.GetType())
return false;

return this.Equals(obj as Student);
}

public bool Equals(Student other)
{
if (this.Id != other.Id)
return false;
if (this.Name != other.Name)
return false;

return true;
}
}

注意,重写Equals方法时,需要同时重写GetHashCode()方法,详细可查看条目7。

operator==()则相对简单。只要创建的是值类型,都必须重定义operator==()。理由和重写System.ValueType的Equals是一样的。而引用类型则应该避免重写operator==()

运行时常量(readonly)和编译期常量(const)

C#有两种类型的常量:编译期常量运行时常量。两者有截然不同的行为,使用不当的话,会造成性能问题,如果没法确定,则使用慢点,但能保证正确的运行时常量。
运行时常量使用readonly关键字声明,编译期常量则使用const关键字声明:

1
2
3
4
//声明编译期常量
public const int Millennium = 2000;
//声明运行时常量
public static readonly int ThisYear = 2017;

二者最重要的区别在于,readonly值是运行时解析的,而const是在生成IL码就已经确定。

const声明的常量必须不能改变,若改变了,则需要重新编译所有引用的程序集。