6、區別各種不同的Equal方法
C#提供了以下四種方法來判斷兩個對象是否相等:
1.static bool ReferenceEquals(object left, object right);
2.static bool Equals(object left, object right);
3.virtual bool Equals(object right);
4.static bool operator ==(MyClass left, MyClass right);
當實現你自己的類的相等方法時,一般重寫第3個方法,在判斷有值類型的相等時重寫第4個方法,第1、2個方法永遠不要重寫它。
對象的相等應當滿足數學上相等的幾個性質:1.自身等於自身。2.可傳遞性,即a==b,b==c,那麼a==c。3.對稱性,即a==b,那麼b==a。
第一個方法ReferenceEquals,在當兩個變量指向同一個對象的時候返回True,否則返回False.
該方法只比較對象的標識,而不會比較對象的內容,所以當該方法應用在值類型的變量時,永遠返回False.--(因為此時會為每個值類型變量產生裝箱操作生成不同的對象),同時該方法執行的效率最塊,因為它只比較對象標識。
第二個方法Equals的實現就和如下代碼一樣:
public static new bool Equals(object left, object right)
{
// Check object identity
if (Object.ReferenceEquals(left, right) )
return true;
// both null references handled above
if (Object.ReferenceEquals(left, null) ||
Object.ReferenceEquals(right, null))
return false;
return left.Equals(right);
}
注意System.ValueType未重寫1、2方法,只重寫了第3個方法,故自定義值類型,也不建議重寫這兩個方法。(自定義值類型一般也會重寫第4個方法==)
但是重寫的Equals方法比較糟糕,因為它要比較所有的成員變量,而它又不知道這些成員的具體類型,所以在這裡它使用了反射技術,眾所周知值類型的反射是比較耗性能的(存在裝箱、拆箱操作)。因此我們在定義自己的值類型的時候,應當重寫Equals方法,這樣會提高效率。
而引用類型,盡量使用預定義的相等方法,在當你重寫了Equals()時,你將也需要實現IEquatable
自定義引用類型一般不建議重寫==方法。
7.GetHashCode()的陷阱
GetHashCode()只用在一個地方:在基於Hash的集合裡為Key定義Hash值,通常是在HashSet
GetHashCode()存在不少問題,對於引用類型,它效率低下;對於值類型,它通常又是不正確的。因此不建議重寫此函數,而且在使用這個函數時也需要加倍小心。
8.偏愛查詢語法而不是循環
對比下面兩塊代碼:
使用循環給數組賦值並輸出
int[] foo = new int[100];
for(int num = 0; num < foo.Length; num++)
foo[num] = num * num;
foreach (int i in foo)
Console.WriteLine(i.ToString());
使用查詢語法(LINQ)賦值及Lambda表達式來輸出
int[] foo = (from n in Enumerable.Range(0, 100)
select n * n).ToArray();
foo.ForAll((n) => Console.WriteLine(n.ToString()));
其中ForAll是在List
public static class Extensions
{
public static void ForAll
{
foreach (T item in sequence)
action(item);
}
}
完整程序如下:
using System;
using System.Diagnostics.Contracts;
using System.Collections.Generic;
using System.Linq;
public static class Extensions
{
public static void ForAll
上面是簡單的一組數組創建及輸出,似乎看不出查詢語法和循環的區別,下面來對比二維數組的排序操作:
使用循環創建二維數組後排序如下:
private static IEnumerable
{
var storage = new List
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));
return storage;
}
而使用查詢語法的排序如下:
private static IEnumerable
{
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);
}
現在很明顯了,查詢語法很簡單的實現了復雜的排序操作,而循環卻顯得很冗長,也不具有可讀性。
9、在你的API中應避免用戶自定義類型的轉換
自定義類型轉換如下:(隱式轉換)
static public implicit operator Ellipse(Circle c)
{
return new Ellipse(c.center, c.center,
c.radius, c.radius);
}
此時你可以隱式的將Circle對象轉換為Ellipse對象。這種隱式轉換是自動的,然後執行下面的方法:
public static void Flatten(Ellipse e)
{
e.R1 /= 2;
e.R2 *= 2;
}
// call it using a circle:
Circle c = new Circle(new PointF(3.0f, 0), 5.0f);
Flatten(c);
此時程序雖能正常進行轉換,但產生的臨時對象(e)被修改了從而成為了垃圾,而原對象卻沒得到修改。
而且一旦一個對象能轉換為另一個對象,那麼就是說彼此對象的內部成員可以得到訪問,從而失去了類的封裝性。
10、使用可選參數來減少方法的重載
帶缺省值的命名參數即是可選參數,你在調用方法時,可以只指定你需要用的參數。這顯然比多個重載方法要方便。實際上,使用4個可選參數的方法,如果用重載來完成將會需要15個不同的重載.