程序師世界是廣大編程愛好者互助、分享、學習的平台,程序師世界有你更精彩!
首頁
編程語言
C語言|JAVA編程
Python編程
網頁編程
ASP編程|PHP編程
JSP編程
數據庫知識
MYSQL數據庫|SqlServer數據庫
Oracle數據庫|DB2數據庫
 程式師世界 >> 編程語言 >> .NET網頁編程 >> C# >> C#入門知識 >> 改善C#編程的50個建議(6-10)

改善C#編程的50個建議(6-10)

編輯:C#入門知識

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接口,因為不實現泛型接口,就會存在一個類型轉換的問題,派生類轉換為基類可能正常,也就是派生類對象==基類對象,但是基類無法轉換為派生類,所以基類對象不等於派生類對象,因此違背了數學意義上的相等。所以我們應當通過實現IEquatable接口方法,來讓基類和派生類都轉換為接口對象,這樣它們就能相互進行轉換再判斷是否相等了。
自定義引用類型一般不建議重寫==方法。


7.GetHashCode()的陷阱
GetHashCode()只用在一個地方:在基於Hash的集合裡為Key定義Hash值,通常是在HashSet和Dictionary容器裡。
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(this IEnumerable sequence,Action action)
{
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(
    this IEnumerable sequence,
    Action action)
    {
        foreach (T item in sequence)
            action(item);
    }
}
class App
{
    static void Main()
    {
        int[] foo = (from n in Enumerable.Range(0, 100)
                     select n * n).ToArray();
        foo.ForAll((n) => Console.WriteLine(n.ToString()));
        Console.ReadKey();
    }
}


上面是簡單的一組數組創建及輸出,似乎看不出查詢語法和循環的區別,下面來對比二維數組的排序操作:
使用循環創建二維數組後排序如下:
private static IEnumerable> ProduceIndices()
{
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> 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);
}
現在很明顯了,查詢語法很簡單的實現了復雜的排序操作,而循環卻顯得很冗長,也不具有可讀性。


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個不同的重載.

  1. 上一頁:
  2. 下一頁:
Copyright © 程式師世界 All Rights Reserved