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

改善C#編程的50個建議(21-25)

編輯:C#入門知識

21 限制類型的可見性
不是每個類型都需要Public。你應給你的類型最少的可見度來達到你的目的,內部或私有類能實現公共的接口。很多獨立的類應當創建為內部的。你可以使用protected或私有嵌套類進一步來限制其可見性,可見性越小,當你之後更新時,整個系統的更改也就越少,越少的地方訪問你的代碼,你修改的地方也就越少。
只暴露那些需要暴露的。對於可見性小的類,嘗試使用公共接口來暴露。

// For illustration, not complete source
public class List : IEnumerable
{
    private class Enumerator : IEnumerator
    {
        // Contains specific implementation of
        // MoveNext(), Reset(), and Current.
        public Enumerator(List storage)
        {
            // elided
        }
    }
    public IEnumerator GetEnumerator()
    {
        return new Enumerator(this);
    }
    // other List members.
}

客戶端代碼不需要知道類的Enumerator
.NET很多集合類也是這樣設計的,如Dictionary包含了私有的DictionaryEnumerator,Queue包含了QueueEnumerator.
創建internal類是一種被忽視的限制類型范圍的方法。缺省情況下,大部分程序員總是創建Public類,沒有去考慮其他,因為這些都是VS .NET向導做的事。在接收默認形式的時候,你應留心你的新創建的類會用在哪裡?是所有客戶端還是主要在程序集內部使用?
使用接口暴露你的函數使你更容易創建internal類,如:
public interface IPhoneValidator
{
  bool ValidateNumber(PhoneNumber ph);
}
internal class USPhoneValidator : IPhoneValidator
{
	public bool ValidateNumber(PhoneNumber ph)
	{
	  // perform validation.
	  // Check for valid area code, exchange.
	  return true;
	}
}


22 偏愛定義和實現接口而不是繼承
抽象基類提供了類層次的祖先。一個接口描述了一個能被類實現的原子功能。
接口以一種契約的方式來設計:一個實現了接口的類必須提供一個預期的方法實現。抽象基類為一系列相關的類型提供了一個公共抽象方法。繼承意思是是什麼,接口意思是行為像什麼.這都是老生常談的問題,但也是它們的工作。它們描述兩個不同的概念:基類描述的是對象是什麼,接口描述的是對象的一種行為方式。
接口描述了一組功能或者說是一個契約。你可以在接口中創建方法、屬性、索引器、事件等。你也可以創建接口所沒有的方法,因為方法沒有具體的數據成員,你可以使用擴展方法來擴充接口功能。如擴展泛型接口IEnumerable:
    public static class Extensions
   {
	public static void ForAll(
		this IEnumerable sequence,
		Action action)
	{
		foreach (T item in sequence)
		action(item);
	}
   }
        // usage
foo.ForAll((n) => Console.WriteLine(n.ToString()));

抽象基類可以為它的派生類提供一些公共的實現方法,它通過virtual方法來為派生類指定數據成員、具體方法等。這個基類實現帶了一個好處:如果你給基類增加一個方法,那麼它的所有派生類都自動隱式地增加.但是給接口增加一個方法將會導致實現它的所有類都產生錯誤.選擇一個抽象基類還是一個接口,這涉及到如何能最好地總是支持你的抽象功能.接口總是固定的:你發行一個接口作為一系列任何類都能實現的功能的契約.基類總是能被擴展的.這些擴展也會成為其派生類的一部分.
這兩種模型是可以混合使用的.一個例子是.NET框架中的IEnumerable接口和System.Linq.Enumerable類.System.Linq.Enumerable類包含了基於System.Collections.Generic.IEnumerable接口的大量擴展方法。
接口可以被任意數量的不相關類型來實現。編寫接口給編寫基類的開發者提供了非常大的靈活性。因為.NET環境中只支持單繼承,而接口解決了多繼承問題。使用接口還可以使你偶爾能減少structs類型拆箱的代價。如:
public struct URLInfo : IComparable, IComparable
{
    private string URL;
    private string description;
    #region IComparable Members
    public int CompareTo(URLInfo other)
    {
        return URL.CompareTo(other.URL);
    }
    #endregion
    #region IComparable Members
    int IComparable.CompareTo(object obj)
    {
        if (obj is URLInfo)
        {
            URLInfo other = (URLInfo)obj;
            return CompareTo(other);
        }
        else
            throw new ArgumentException(
            "Compared object is not URLInfo");
    }
    #endregion
}

任何基於IComparable的代碼將會減少裝箱和拆箱,因為可以調用IComparable.CompareTo方法而不用拆箱對象.


23 理解接口方法和虛擬方法的不同
剛開始,可能以為實現一個接口和重寫一個虛擬方法一樣,但實際上實現一個接口和重寫虛擬方法是大不相同的.首先在接口中的成員方法並不是默認以virtual聲明的.派生類不能重寫基類中已實現的接口成員,接口可以顯示實現,這樣可以隱藏從類繼承而來的公共接口。它們是在不同的用途是不同的概念。
你可以在派生類中修改接口的實現。如:
interface IMsg
{
    void Message();
}
public class MyClass : IMsg
{
    public void Message()
    {
        Console.WriteLine("MyClass");
    }
}
Message()方法現在是MyClass類的公共接口。Message也可以通過IMsg指針來訪問。現在我們來增加一個派生類:
public class MyDerivedClass : MyClass
{
    public void Message()
    {
        Console.WriteLine("MyDerivedClass");
    }
}

因為MyClass.Message()不是虛擬的,派生類是不能提供一個重寫的Message()版本的。此時派生類創建了一個新的Message()方法,它把基類的Message()方法給隱藏了,相當於new void Message()。通過IMsg引用MyClass.Message仍然是可以用的:
MyDerivedClass d = new MyDerivedClass();
d.Message(); // prints "MyDerivedClass".
IMsg m = d as IMsg;
m.Message(); // prints "MyClass"
當你實現一個接口,你實際在類中聲明了一個特殊契約的具體實現。但是通常我們想在基類創建、實現接口,然後在派生類中修改它:
    public class MyClass : IMsg
    {
        public virtual void Message()
        {
            Console.WriteLine("MyClass");
        }
    }
    public class MyDerivedClass : MyClass
    {
        public override void Message()
        {
            Console.WriteLine("MyDerivedClass");
        }
    }

MyDerivedClass以及所有從MyClass派生的類都能聲明它們自己的Message方法。這個重寫版本能被派生類引用訪問、能被接口引用訪問、能被基類引用訪問,如果你不喜歡虛函數的概念,你可以這樣來重新定義基類:
public abstract class MyClass : IMsg
{
public abstract void Message();
}
對,你可以在基類中不實際實現一個接口,聲明它為抽象方法。這樣所有派生類必須實現這個接口方法。派生類可以通過密封該方法來阻止它自己的派生重寫它:
    public class MyDerivedClass2 : MyClass
    {
        public sealed override void Message()
        {
            Console.WriteLine("MyDerivedClass");
        }
    }

另一種解決方法是實現接口,然後包含一個調用該虛方法的方法,如:
    public class MyClass2 : IMsg
    {
        protected virtual void OnMessage()
        {
        }
        public void Message()
        {
            OnMessage();
            Console.WriteLine("MyClass");
        }
    }

任何派生類都能重寫OnMessage方法,還可以在Message方法中增加它們自己的工作。.NET事件方法幾乎都用這種模式。
基類能提供一個缺省的接口方法實現,然後派生類可以通過繼承基類聲明並實現接口方法,如:
    public class DefaultMessageGenerator
    {
        public void Message()
        {
            Console.WriteLine("This is a default message");
        }
    }
    public class AnotherMessageGenerator :
    DefaultMessageGenerator, IMsg
    {
        // No explicit Message() method needed.
    }

注意派生類能聲明這個接口作為它自己契約的一部分,即使它沒有提供IMsg方法的實現,它通過繼承基類的方法,隱式實現了該接口方法。


24 使用委托來表示回調
回調函數用來從服務器向客戶端異步地提供一個反饋。它們可能涉及多線程,或者簡單提供一個同步更新的入口點。回調函數在C#中通過委托來表現。
委托提供一個類型安全的回調函數定義。雖然委托用的最多的是事件,但這不是委托唯一能用的地方。委托讓你在運行時配置目標,並且通知多個客戶端。一個委托就是一個包含方法引用的對象。包含的方法可以是靜態方法或者實例方法。使用委托,你可以在運行時與一個或多個客戶端對象交流、配置。
C#提供了一個緊湊的語法(lambda表達式)來表達委托。.NET框架庫定義了很多常見的委托形式,如:Predicate, Action<>, and Func<>.predicate是一個用來測試條件的布爾函數。Func<>需要一些數值參數,然後產生一個結果。也就是Func和Predicate有相同的形式。Action<>輸入任何數量的參數,然後返回一個void類型。LINQ就是建立在這些概念上的。
List類也包含了很多利用回調函數的方法,如:
List numbers = Enumerable.Range(1, 200).ToList();
var oddNumbers = numbers.Find(n => n % 2 == 1);
var test = numbers.TrueForAll(n => n < 50);
numbers.RemoveAll(n => n % 2 == 0);
numbers.ForEach(item => Console.WriteLine(item));
Find()方法接收一個委托,以Predicate的形式來測試列表中的每一個元素。這是一種簡單的回調。List.ForEach()方法在每個列表元素中執行指定的動作。實際上編譯器將lambda表達式轉換為一個方法,然後創建一個委托來指向該方法.所有的LINQ都是建立在委托上的。在WPF和Windows Form中,回調是用來處理跨線程的封送處理的。
由於歷史原因,所有的委托都是多播委托。多播委托封裝所有的目標函數然後添加在一個調用裡。這種構造存在兩個問題:一個是在面對異常時不安全,另一個是返回值將是最後一個目標函數調用的返回值。


25 實現事件模式的通知
.NET的事件模式,無非是觀察者模式的一個語法轉換而已。事件定義了你的類的通知。事件基於委托提供了一個類型安全的函數簽名。
考慮一個簡單的例子。你已經建立了一個log類,它是一個應用的所有消息的調度器。它將接收應用的所有消息,並且調度這些消息到對其感興趣的監聽者上。這些監聽者可能是依附在控制台、數據庫、系統日志等等,定義如下:
public class LoggerEventArgs : EventArgs
    {
        public string Message { get; private set; }
        public int Priority { get; private set; }
        public LoggerEventArgs(int p, string m)
        {
            Priority = p;
            Message = m;
        }
    }
    public class Logger
    {
        static Logger()
        {
            theOnly = new Logger();
        }
        private Logger()
        {
        }
        private static Logger theOnly = null;
        public static Logger Singleton
        {
            get { return theOnly; }
        }
        // Define the event:
        public event EventHandler Log;
        // add a message, and log it.
        public void AddMsg(int priority, string msg)
        {
            // This idiom discussed below.
            EventHandler l = Log;
            if (l != null)
                l(this, new LoggerEventArgs(priority, msg));
        }
    }

AddMsg方法展示了合適的拋出異常的方式。在多線程程序的靜態條件上引用log事件句柄的臨時變量是很重要的保護措施。C#編譯器為事件創建了add和remove訪問器:
public event EventHandler Log
{
add { log = log + value; }
remove { log = log - value; }
}
log的使用:
    class ConsoleLogger
    {
        static ConsoleLogger()
        {
            Logger.Singleton.Log += (sender, msg) =>
            {
                Console.Error.WriteLine("{0}:\t{1}",
                msg.Priority.ToString(),
                msg.Message);
            };
        }
    }

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