C#/Net代码精简优化技巧(1)中已经介绍了5个小技巧,本篇将再介绍5个。

1 string.IsNullOrEmpty() and string.IsNullOrWhiteSpace()

在Net2.0中String类型有一个静态方法IsNullOrEmpty,到了Net4.0中String类又增加了一个新的静态方法IsNullOrWhiteSpace。这两个方法看名称也可以知道IsNullOrEmpty是判断空引用和空字符串,而IsNullOrWhiteSpace是判断空引用和字符串中的每一个字符是否是空格。
在有这两个方法之前,我们要进行这样的判断,需要些如下代码
InBlock.gif public string GetFileName( string fullPathFileName)
InBlock.gif{
InBlock.gif         if (fullPathFileName == null || fullPathFileName.Length == 0)
InBlock.gif        {
InBlock.gif                 throw new ArgumentNullException(fullPathFileName);
InBlock.gif        }
InBlock.gif         //...
InBlock.gif}
使用IsNullOrEmpty
InBlock.gif public string GetFileName( string fullPathFileName)
InBlock.gif{
InBlock.gif         if ( string.IsNullOrEmpty(fullPathFileName))
InBlock.gif        {
InBlock.gif
InBlock.gif             throw new ArgumentNullException(fullPathFileName);
InBlock.gif        }
InBlock.gif         //...
InBlock.gif}
下面又了新的需求,需要将三个名字连接在一起,并且希望中间名字不为空字符串和不出现多余的空格,我们会写出下面的代码
InBlock.gif public string GetFullName( string firstName, string middleName, string lastName)
InBlock.gif{
InBlock.gif         if (middleName == null || middleName.Trim().Length == 0)
InBlock.gif        {
InBlock.gif                 return string.Format( "{0} {1}", firstName, lastName);
InBlock.gif        }
InBlock.gif         return string.Format( "{0} {1} {2}", firstName, middleName, lastName);
InBlock.gif}
上面的代码中使用了Trim来去掉空格然后判断其长度是否为0,代码也非常的清晰简洁,但是会产生额外的String对象以至于影响性能,这时就应该使用Net4.0中的IsNullOrWhiteSpace方法
InBlock.gif public string GetFullName( string firstName, string middleName, string lastName)
InBlock.gif{
InBlock.gif         if ( string.IsNullOrWhiteSpace(middleName))
InBlock.gif        {
InBlock.gif                 return string.Format( "{0} {1}", firstName, lastName);
InBlock.gif        }
InBlock.gif         return string.Format( "{0} {1} {2}", firstName, middleName, lastName);
InBlock.gif}
上面的代码非常简洁,而且也不用担心会产生额外的String对象没有及时的进行垃圾回收而影响性能。

2 string.Equals()

string.Equals方法有很多的重载供我们使用,但是其中有些常常会被我们忽视掉。通常我们比较字符串会使用下面的方法
InBlock.gif public Order CreateOrder( string orderType, string product, int quantity, double price)
InBlock.gif{
InBlock.gif         if (orderType.Equals( "equity"))
InBlock.gif        {
InBlock.gif        }
InBlock.gif         // ...
InBlock.gif}
如果orderType为null会抛出 NullReferenceException异常,所以为了不抛出异常,在判断之前先要进行null的判断,如下:
InBlock.gif if (orderType != null && orderType.Equals( "equity"))
相当于每次都要做两次判断,很麻烦而且有时还有可能遗忘,如果使用string.Equals就可以解决这个问题,代码如下:
InBlock.gif if ( string.Equals(orderType, "equity"))
上面的代码当orderType为null时不会抛出异常而是直接返回false。
判断字符串相等的时候有时会需要区分大小写,很多人的习惯做法是先转换成大小或是小些在进行比较(建议转换成大写,因为编译器做了优化可以提高性能),但是当转换成大写或是小写时又会创建的的字符串,使性能降低。这时应该使用StringComparison.InvariantCultureIgnoreCase,代码如下
InBlock.gif if (orderType.Equals( "equity", StringComparison.InvariantCultureIgnoreCase))
如果要考虑到null的情况,还是应该使用string.Equal
InBlock.gif if ( string.Equals(orderType, "equity", StringComparison.InvariantCultureIgnoreCase))

3 using语句

我们都知道using最常用的地方就是在类中引用命名空间。除此之外还可以用作设置别名和应用在一些实现了 IDisposable 借口的对象实例上,可以使这些对象在using的作用范围内自动释放资源。下面的代码示例是没有使用using的情况:
InBlock.gif public IEnumerable<Order> GetOrders()
InBlock.gif{
InBlock.gif        var orders = new List<Order>();
InBlock.gif        var con = new SqlConnection( "some connection string");
InBlock.gif        var cmd = new SqlCommand( "select * from orders", con);
InBlock.gif        var rs = cmd.ExecuteReader();
InBlock.gif         while (rs.Read())
InBlock.gif        {
InBlock.gif                 // ...
InBlock.gif        }
InBlock.gif        rs.Dispose();
InBlock.gif        cmd.Dispose();
InBlock.gif        con.Dispose();
InBlock.gif         return orders;
InBlock.gif}
上面的代码不怎么好看,而且也没有对异常的处理,如果在代码执行过程中出现了异常将会导致有些资源不能及时释放,尽管最终还是会被垃圾回收,但还是会影响性能呢。下面的代码添加了异常处理
InBlock.gif public IEnumerable<Order> GetOrders()
InBlock.gif{
InBlock.gif        SqlConnection con = null;
InBlock.gif        SqlCommand cmd = null;
InBlock.gif        SqlDataReader rs = null;
InBlock.gif        var orders = new List<Order>();
InBlock.gif         try
InBlock.gif        {
InBlock.gif                con = new SqlConnection( "some connection string");
InBlock.gif                cmd = new SqlCommand( "select * from orders", con);
InBlock.gif                rs = cmd.ExecuteReader();
InBlock.gif                 while (rs.Read())
InBlock.gif                {
InBlock.gif                         // ...
InBlock.gif                }
InBlock.gif        }
InBlock.gif         finally
InBlock.gif        {
InBlock.gif                rs.Dispose();
InBlock.gif                cmd.Dispose();
InBlock.gif                con.Dispose();
InBlock.gif        }
InBlock.gif         return orders;
InBlock.gif}
上面的代码仍然存在不足,如果 SqlCommand对象创建失败或是抛出了异常,rs就会为null,那么最后在执行rs.Dispose()时就会抛出异常,会导致con.Dispose不能被调用,所以我们应该避免这种情况的发生
InBlock.gif public IEnumerable<Order> GetOrders()
InBlock.gif{
InBlock.gif        var orders = new List<Order>();
InBlock.gif         using (var con = new SqlConnection( "some connection string"))
InBlock.gif        {
InBlock.gif                 using (var cmd = new SqlCommand( "select * from orders", con))
InBlock.gif                {
InBlock.gif                         using (var rs = cmd.ExecuteReader())
InBlock.gif                        {
InBlock.gif                                 while (rs.Read())
InBlock.gif                                {
InBlock.gif                                         // ...
InBlock.gif                                }
InBlock.gif                        }
InBlock.gif                }
InBlock.gif        }
InBlock.gif         return orders;
InBlock.gif}
上面的代码中的using嵌套了好几层,看起来很繁琐,而且可读性也不是很好,我们可以像下面这样改进
InBlock.gif public IEnumerable<Order> GetOrders()
InBlock.gif{
InBlock.gif        var orders = new List<Order>();
InBlock.gif
InBlock.gif         using (var con = new SqlConnection( "some connection string"))
InBlock.gif         using (var cmd = new SqlCommand( "select * from orders", con))
InBlock.gif         using (var rs = cmd.ExecuteReader())
InBlock.gif        {
InBlock.gif                 while (rs.Read())
InBlock.gif                {
InBlock.gif                         // ...
InBlock.gif                }
InBlock.gif        }
InBlock.gif         return orders;
InBlock.gif}

4 静态类(Static)

很多人在创建类的时候没有使用过Static修饰符,可能他们并不知道Static修饰符的作用,Static修饰符所做的一些限制可以在其他开发人员使用我们代码的时候使我们的代码变得更加安全。比如我们现在写一个XmlUtility类,这个类的作用是实现XML的序列化,代码如下:
InBlock.gif public class XmlUtility
InBlock.gif{
InBlock.gif         public string ToXml( object input)
InBlock.gif        {
InBlock.gif                var xs = new XmlSerializer(input.GetType());
InBlock.gif                 using (var memoryStream = new MemoryStream())
InBlock.gif                 using (var xmlTextWriter = new XmlTextWriter(memoryStream, new UTF8Encoding()))
InBlock.gif                {
InBlock.gif                        xs.Serialize(xmlTextWriter, input);
InBlock.gif                         return Encoding.UTF8.GetString(memoryStream.ToArray());
InBlock.gif                }
InBlock.gif        }
InBlock.gif}
上面的是很典型的XML序列化代码,但是我们在使用时需要先实例化这个类的对象,然后用对象来调用方法
InBlock.gifvar xmlUtil = new XmlUtility();
InBlock.gif string result = xmlUtil.ToXml(someObject);
这样显然很麻烦,不过我们可以给方法加上static修饰符,然后给类加上私有的构造函数防止类实例化来使类的使用变得简单
InBlock.gif public class XmlUtility
InBlock.gif{
InBlock.gif         private XmlUtility()
InBlock.gif        {    
InBlock.gif
InBlock.gif        }
InBlock.gif         public static string ToXml( object input)
InBlock.gif        {
InBlock.gif                var xs = new XmlSerializer(input.GetType());
InBlock.gif                 using (var memoryStream = new MemoryStream())
InBlock.gif                 using (var xmlTextWriter = new XmlTextWriter(memoryStream, new UTF8Encoding()))
InBlock.gif                {
InBlock.gif                        xs.Serialize(xmlTextWriter, input);
InBlock.gif                         return Encoding.UTF8.GetString(memoryStream.ToArray());
InBlock.gif                }
InBlock.gif        }
InBlock.gif}
上面的代码可以实现类直接调用方法,但是给类设置私有构造函数的做法不是很好,当我们给类误添加了非静态方法时,类不能实例化,添加的非静态方法就形同虚设了
InBlock.gif public T FromXml<T>( string xml) { ... }
所以我们需要将类设置成静态的,这样当类中有非静态方法时编译时就会抛出异常,告诉我们类中只能包含静态成员
InBlock.gif public static class XmlUtility
InBlock.gif{
InBlock.gif         public static string ToXml( object input)
InBlock.gif        {
InBlock.gif                var xs = new XmlSerializer(input.GetType());
InBlock.gif                 using (var memoryStream = new MemoryStream())
InBlock.gif                 using (var xmlTextWriter = new XmlTextWriter(memoryStream, new UTF8Encoding()))
InBlock.gif                {
InBlock.gif                        xs.Serialize(xmlTextWriter, input);
InBlock.gif                         return Encoding.UTF8.GetString(memoryStream.ToArray());
InBlock.gif                }
InBlock.gif        }
InBlock.gif}
给类添加Static修饰符,该类就只能包含静态成员,并且不能被实例化,我们也不可能随便就给添加了一个非静态的成员,否则是不能编译通过的。

5 对象和集合初始化器

在C#3.0及以上版本中增加了对象和集合初始化器的新特性,会使代码看起来更加简洁,还有可能带来更高的性能。初始化器其实就是一个语法糖。看下面的例子,给出的是一个结构
InBlock.gif public struct Point
InBlock.gif{
InBlock.gif         public int X { get; set; }
InBlock.gif         public int Y { get; set; }
InBlock.gif}
普通初始化如下
InBlock.gifvar startingPoint = new Point();
InBlock.gifstartingPoint.X = 5;
InBlock.gifstartingPoint.Y = 13;
使用初始化器初始化
InBlock.gifvar startingPoint = new Point() { X = 5, Y = 13 };
代码的确精简了不少,从三行减到了一行,可以让我们少敲很多字。
下面再来看看集合的初始化,假设我们在一个集合List中添加5个整数
InBlock.gifvar list = new List< int>();
InBlock.giflist.Add(1);
InBlock.giflist.Add(7);
InBlock.giflist.Add(13);
InBlock.giflist.Add(42);
使用集合初始化器,代码如下
InBlock.gifvar list = new List< int> { 1, 7, 13, 42 };
如果我们事先知道要加载的数量,可以给List设置默认的容量值,如下
InBlock.gifvar list = new List< int>(4) { 1, 7, 13, 42 };
下面来看一个通常情况下对象和集合一起使用的例子
InBlock.gifvar list = new List<Point>();
InBlock.gifvar point = new Point();
InBlock.gifpoint.X = 5;
InBlock.gifpoint.Y = 13;
InBlock.giflist.Add(point);
InBlock.gifpoint = new Point();
InBlock.gifpoint.X = 42;
InBlock.gifpoint.Y = 111;
InBlock.giflist.Add(point);
InBlock.gifpoint = new Point();
InBlock.gifpoint.X = 7;
InBlock.gifpoint.Y = 9;
InBlock.giflist.Add(point);
下面为使用了初始化器的代码,可以对比一下区别
InBlock.gifvar list = new List<Point>
InBlock.gif{
InBlock.gif         new Point { X = 5, Y = 13 },
InBlock.gif         new Point { X = 42, Y = 111 },
InBlock.gif         new Point { X = 7, Y = 9 }
InBlock.gif};
使用对象或集合初始化器给我们带来了非常简洁的代码,尽管有时一条语句会占用多行,但是可读性是非常好的。
有些时候在性能上也会带来提升,看下面两个类
InBlock.gif public class BeforeFieldInit
InBlock.gif{
InBlock.gif         public static List< int> ThisList = new List< int>() { 1, 2, 3, 4, 5 };
InBlock.gif}
InBlock.gif
InBlock.gif public class NotBeforeFieldInit
InBlock.gif{
InBlock.gif         public static List< int> ThisList;
InBlock.gif
InBlock.gif         static NotBeforeFieldInit()
InBlock.gif        {
InBlock.gif                ThisList = new List< int>();
InBlock.gif                ThisList.Add(1);
InBlock.gif                ThisList.Add(2);
InBlock.gif                ThisList.Add(3);
InBlock.gif                ThisList.Add(4);
InBlock.gif                ThisList.Add(5);
InBlock.gif        }
InBlock.gif}
这两个类都是做同样的事情,都是创建一个静态的List字段,然后添加了1到5五个整数。不同的是第一个类在生成的IL代码中类上会添加 beforefieldinit标记,对比两个类生成的IL代码
InBlock.gif. class public auto ansi beforefieldinit BeforeFieldInit
InBlock.gif             extends [mscorlib]System.Object
InBlock.gif{
InBlock.gif} // end of class BeforeFieldInit    
InBlock.gif
InBlock.gif. class public auto ansi NotBeforeFieldInit
InBlock.gif             extends [mscorlib]System.Object
InBlock.gif{
InBlock.gif} // end of class NotBeforeFieldInit
有关静态构造函数的性能问题可以参考 CLR Via C# 学习笔记(5) 静态构造函数的性能

总结

本文是参考老外系列博客的第二篇写的,并不是直译,原文见下面链接。希望本文对大家有所帮助。