文章目錄
- 1 string.IsNullOrEmpty() and string.IsNullOrWhiteSpace()
- 2 string.Equals()
- 3 using語句
- 4 靜態類(Static)
- 5 對象和集合初始化器
- 總結
在C#/Net代碼精簡最佳化技巧(1)中已經介紹了5個小技巧,本篇將再介紹5個。
1 string.IsNullOrEmpty() and string.IsNullOrWhiteSpace()
在Net2.0中String類型有一個靜態方法IsNullOrEmpty,到了Net4.0中String類又增加了一個新的靜態方法IsNullOrWhiteSpace。這兩個方法看名稱也可以知道IsNullOrEmpty是判斷Null 參考和Null 字元串,而IsNullOrWhiteSpace是判斷Null 參考和字串中的每一個字元是否是空格。
在有這兩個方法之前,我們要進行這樣的判斷,需要些如下代碼
public string GetFileName(string fullPathFileName){ if (fullPathFileName == null || fullPathFileName.Length == 0) { throw new ArgumentNullException(fullPathFileName); } //...}
使用IsNullOrEmpty
public string GetFileName(string fullPathFileName){ if (string.IsNullOrEmpty(fullPathFileName)) { throw new ArgumentNullException(fullPathFileName); } //...}
下面又了新的需求,需要將三個名字串連在一起,並且希望中間名字不為空白字串和不出現多餘的空格,我們會寫出下面的代碼
public string GetFullName(string firstName, string middleName, string lastName){ if (middleName == null || middleName.Trim().Length == 0) { return string.Format("{0} {1}", firstName, lastName); } return string.Format("{0} {1} {2}", firstName, middleName, lastName);}
上面的代碼中使用了Trim來去掉空格然後判斷其長度是否為0,代碼也非常的清晰簡潔,但是會產生額外的String對象以至於影響效能,這時就應該使用Net4.0中的IsNullOrWhiteSpace方法
public string GetFullName(string firstName, string middleName, string lastName){ if (string.IsNullOrWhiteSpace(middleName)) { return string.Format("{0} {1}", firstName, lastName); } return string.Format("{0} {1} {2}", firstName, middleName, lastName);}
上面的代碼非常簡潔,而且也不用擔心會產生額外的String對象沒有及時的進行記憶體回收而影響效能。
2 string.Equals()
string.Equals方法有很多的重載供我們使用,但是其中有些常常會被我們忽視掉。通常我們比較字串會使用下面的方法
public Order CreateOrder(string orderType, string product, int quantity, double price){ if (orderType.Equals("equity")) { } // ...}
如果orderType為null會拋出NullReferenceException異常,所以為了不拋出異常,在判斷之前先要進行null的判斷,如下:
if (orderType != null && orderType.Equals("equity"))
相當於每次都要做兩次判斷,很麻煩而且有時還有可能遺忘,如果使用string.Equals就可以解決這個問題,代碼如下:
if (string.Equals(orderType, "equity"))
上面的代碼當orderType為null時不會拋出異常而是直接返回false。
判斷字串相等的時候有時會需要區分大小寫,很多人的習慣做法是先轉換成大小或是小些在進行比較(建議轉換成大寫,因為編譯器做了最佳化可以提高效能),但是當轉換成大寫或是小寫時又會建立的的字串,使效能降低。這時應該使用StringComparison.InvariantCultureIgnoreCase,代碼如下
if (orderType.Equals("equity", StringComparison.InvariantCultureIgnoreCase))
如果要考慮到null的情況,還是應該使用string.Equal
if (string.Equals(orderType, "equity", StringComparison.InvariantCultureIgnoreCase))
3 using語句
我們都知道using最常用的地方就是在類中引用命名空間。除此之外還可以用作設定別名和應用在一些實現了IDisposable 借口的對象執行個體上,可以使這些對象在using的作用範圍內自動釋放資源。下面的程式碼範例是沒有使用using的情況:
public IEnumerable<Order> GetOrders(){ var orders = new List<Order>(); var con = new SqlConnection("some connection string"); var cmd = new SqlCommand("select * from orders", con); var rs = cmd.ExecuteReader(); while (rs.Read()) { // ... } rs.Dispose(); cmd.Dispose(); con.Dispose(); return orders;}
上面的代碼不怎麼好看,而且也沒有對異常的處理,如果在代碼執行過程中出現了異常將會導致有些資源不能及時釋放,儘管最終還是會被記憶體回收,但還是會影響效能呢。下面的代碼添加了異常處理
public IEnumerable<Order> GetOrders(){ SqlConnection con = null; SqlCommand cmd = null; SqlDataReader rs = null; var orders = new List<Order>(); try { con = new SqlConnection("some connection string"); cmd = new SqlCommand("select * from orders", con); rs = cmd.ExecuteReader(); while (rs.Read()) { // ... } } finally { rs.Dispose(); cmd.Dispose(); con.Dispose(); } return orders;}
上面的代碼仍然存在不足,如果SqlCommand對象建立失敗或是拋出了異常,rs就會為null,那麼最後在執行rs.Dispose()時就會拋出異常,會導致con.Dispose不能被調用,所以我們應該避免這種情況的發生
public IEnumerable<Order> GetOrders(){ var orders = new List<Order>(); using (var con = new SqlConnection("some connection string")) { using (var cmd = new SqlCommand("select * from orders", con)) { using (var rs = cmd.ExecuteReader()) { while (rs.Read()) { // ... } } } } return orders;}
上面的代碼中的using嵌套了好幾層,看起來很繁瑣,而且可讀性也不是很好,我們可以像下面這樣改進
public IEnumerable<Order> GetOrders(){ var orders = new List<Order>(); using (var con = new SqlConnection("some connection string")) using (var cmd = new SqlCommand("select * from orders", con)) using (var rs = cmd.ExecuteReader()) { while (rs.Read()) { // ... } } return orders;}
4 靜態類(Static)
很多人在建立類的時候沒有使用過Static修飾符,可能他們並不知道Static修飾符的作用,Static修飾符所做的一些限制可以在其他開發人員使用我們代碼的時候使我們的代碼變得更加安全。比如我們現在寫一個XmlUtility類,這個類的作用是實現XML的序列化,代碼如下:
public class XmlUtility{ public string ToXml(object input) { var xs = new XmlSerializer(input.GetType()); using (var memoryStream = new MemoryStream()) using (var xmlTextWriter = new XmlTextWriter(memoryStream, new UTF8Encoding())) { xs.Serialize(xmlTextWriter, input); return Encoding.UTF8.GetString(memoryStream.ToArray()); } }}
上面的是很典型的XML序列化代碼,但是我們在使用時需要先執行個體化這個類的對象,然後用對象來調用方法
var xmlUtil = new XmlUtility();string result = xmlUtil.ToXml(someObject);
這樣顯然很麻煩,不過我們可以給方法加上static修飾符,然後給類加上私人的建構函式防止類執行個體化來使類的使用變得簡單
public class XmlUtility{ private XmlUtility() { } public static string ToXml(object input) { var xs = new XmlSerializer(input.GetType()); using (var memoryStream = new MemoryStream()) using (var xmlTextWriter = new XmlTextWriter(memoryStream, new UTF8Encoding())) { xs.Serialize(xmlTextWriter, input); return Encoding.UTF8.GetString(memoryStream.ToArray()); } }}
上面的代碼可以實作類別直接調用方法,但是給類設定私人建構函式的做法不是很好,當我們給類誤添加了非靜態方法時,類不能執行個體化,添加的非靜態方法就形同虛設了
public T FromXml<T>(string xml) { ... }
所以我們需要將類設定成靜態,這樣當類中有非靜態方法時編譯時間就會拋出異常,告訴我們類中只能包含靜態成員
public static class XmlUtility{ public static string ToXml(object input) { var xs = new XmlSerializer(input.GetType()); using (var memoryStream = new MemoryStream()) using (var xmlTextWriter = new XmlTextWriter(memoryStream, new UTF8Encoding())) { xs.Serialize(xmlTextWriter, input); return Encoding.UTF8.GetString(memoryStream.ToArray()); } }}
給類添加Static修飾符,該類就只能包含靜態成員,並且不能被執行個體化,我們也不可能隨便就給添加了一個非靜態成員,否則是不能編譯通過的。
5 對象和集合初始化器
在C#3.0及以上版本中增加了對象和集合初始化器的新特性,會使代碼看起來更加簡潔,還有可能帶來更高的效能。初始化器其實就是一個文法糖。看下面的例子,給出的是一個結構
public struct Point{ public int X { get; set; } public int Y { get; set; }}
普通初始化如下
var startingPoint = new Point();startingPoint.X = 5;startingPoint.Y = 13;
使用初始化器初始化
var startingPoint = new Point() { X = 5, Y = 13 };
代碼的確精簡了不少,從三行減到了一行,可以讓我們少敲很多字。
下面再來看看集合的初始化,假設我們在一個集合List中添加5個整數
var list = new List<int>();list.Add(1);list.Add(7);list.Add(13);list.Add(42);
使用集合初始化器,代碼如下
var list = new List<int> { 1, 7, 13, 42 };
如果我們事Crowdsourced Security Testing道要載入的數量,可以給List設定預設的容量值,如下
var list = new List<int>(4) { 1, 7, 13, 42 };
下面來看一個通常情況下對象和集合一起使用的例子
var list = new List<Point>();var point = new Point();point.X = 5;point.Y = 13;list.Add(point);point = new Point();point.X = 42;point.Y = 111;list.Add(point);point = new Point();point.X = 7;point.Y = 9;list.Add(point);
下面為使用了初始化器的代碼,可以對比一下區別
var list = new List<Point>{ new Point { X = 5, Y = 13 }, new Point { X = 42, Y = 111 }, new Point { X = 7, Y = 9 }};
使用對象或集合初始化器給我們帶來了非常簡潔的代碼,儘管有時一條語句會佔用多行,但是可讀性是非常好的。
有些時候在效能上也會帶來提升,看下面兩個類
public class BeforeFieldInit{ public static List<int> ThisList = new List<int>() { 1, 2, 3, 4, 5 };}public class NotBeforeFieldInit{ public static List<int> ThisList; static NotBeforeFieldInit() { ThisList = new List<int>(); ThisList.Add(1); ThisList.Add(2); ThisList.Add(3); ThisList.Add(4); ThisList.Add(5); }}
這兩個類都是做同樣的事情,都是建立一個靜態List欄位,然後添加了1到5五個整數。不同的是第一個類在產生的IL代碼中類上會添加beforefieldinit標記,對比兩個類產生的IL代碼
.class public auto ansi beforefieldinit BeforeFieldInit extends [mscorlib]System.Object{} // end of class BeforeFieldInit .class public auto ansi NotBeforeFieldInit extends [mscorlib]System.Object{} // end of class NotBeforeFieldInit
有關靜態建構函式的效能問題可以參考CLR Via C# 學習筆記(5) 靜態建構函式的效能
總結
本文是參考老外系列部落格的第二篇寫的,並不是直譯,原文見下面連結。希望本文對大家有所協助。
原文連結:http://geekswithblogs.net/BlackRabbitCoder/archive/2010/09/02/c.net-five-more-little-wonders-that-make-code-better-2.aspx
C#/Net代碼精簡最佳化技巧(1)
C#/Net代碼精簡最佳化技巧(2)
C#/Net代碼精簡最佳化技巧(3)