1. 程式人生 > 程式設計 >C# 擴充套件方法小結

C# 擴充套件方法小結

在使用面向物件的語言進行專案開發的過程中,較多的會使用到“繼承”的特性,但是並非所有的場景都適合使用“繼承”特性,在設計模式的一些基本原則中也有較多的提到。

繼承的有關特性的使用所帶來的問題:物件的繼承關係實在編譯時就定義好了,所以無法在執行時改變從父類繼承的實現。子類的實現與它父類有非常緊密的依賴關係,以至於父類實現中的任何變化必然會導致子類發生變化。當你需要複用子類時,如果繼承下來的實現不適合解決新的問題,則父類必須重寫它或被其他更適合的類替換,這種依賴關係限制了靈活性並最終限制了複用性。替代繼承特性的方式,較多的會採用 合成/聚合複用原則,“合成/聚合複用原則”:儘量使用合成/聚合,儘量不要使用類繼承。

如果在新型別的物件應當攜帶有關額外行為的細節,在使用繼承特性時,有時可能不太適合,例如:處理指型別,密封類,或者介面時。在面對這些要求時,我們有時候會寫一些靜態類包含一些靜態方法。但是過多的靜態方法會造成額外的不必要的開銷。

一.擴充套件方法概述:

面對以上的有關“繼承”的問題,以及在面對專案的一些需求時,我們需要解決這些問題的方式就是“擴充套件方法”。在C#3.0中引入了“擴充套件方法”,既有靜態方法的優點,又使呼叫它們的程式碼的可讀性得到了提高。在使用擴充套件方法時,可以像呼叫例項方法那樣呼叫靜態方法。

1.擴充套件方法的基本原則:

(1).C#只支援擴充套件方法,不支援擴充套件屬性、擴充套件事件、擴充套件操作符等。

(2).擴充套件方法(第一個引數前面是this的方法)必須在非泛型的靜態類中宣告,擴充套件方法必須有一個引數,而且只有第一個引數使用this標記。

(3).C#編譯器查詢靜態類中的擴充套件方法時,要求這些靜態類本身必須具有檔案作用域。

(4).C#編譯要求“匯入”擴充套件方法。(靜態方法可以任意命名,C#編譯器在尋找方法時,需要花費時間進行查詢,需要檢查檔案作用域中的所有的靜態類,並掃描它們的所有靜態方法來查詢一個匹配)

(5).多個靜態類可以定義相同的擴充套件方法。

(6).用一個擴充套件方法擴充套件一個型別時,同時也擴充套件了派生型別。

2.擴充套件方法宣告:

(1).必須在一個非巢狀的、非泛型的靜態類中(所以必須是一個靜態方法)

(2).至少有一個引數。

(3).第一個引數必須附加this關鍵字做字首。

(4).第一個引數不能有其他任何修飾符(如ref或out)。

(5).第一個引數的型別不能是指標型別。

以上的兩個分類說明中,對擴充套件方法的基本特性和宣告方式做了一個簡單的介紹,有關擴充套件方法的使用方式,會在後面的程式碼樣例中進行展示,再次就不再多做說明。

二.擴充套件方法原理解析:

“擴充套件方法”是C#獨有的一種方法,在擴充套件方法中會使用ExtensionAttribute這個attribute。

C#一旦使用this關鍵字標記了某個靜態方法的第一個引數,編譯器就會在內部向該方法應用一個定製的attribute,這個attribute會在最終生成的檔案的元資料中永續性的儲存下來,此屬性在System.Core dll程式集中。

任何靜態類只要包含了至少一個擴充套件方法,它的元資料中也會應用這個attribute,任何一個程式集包含了至少一個符合上述特點的靜態類,它的元資料也會應用這個attribute。如果程式碼用了一個不存在的例項方法,編譯器會快速的掃描引用的所有程式集,判斷它們哪些包含了擴充套件方法,然後,在這個程式集中,可以掃描包含了擴充套件方法的靜態類。

如果同一個名稱空間中的兩個類含有擴充套件型別相同的方法,就沒有辦法做到只用其中一個類中的擴充套件方法。為了通過型別的簡單名稱(沒有名稱空間字首)來使用型別,可以匯入該型別所有在的名稱空間,但這樣做的時候,你沒有辦法阻止那個名稱空間中的擴充套件方法也被匯入進來。

三..NET3.5的擴充套件方法Enumerable和Queryable:

在框架中,擴充套件方法最大的用途就是為LINQ服務,框架提供了輔助的擴充套件方法,位於System.Linq名稱空間下的Enumerable和Queryable類。Enumerable大多數擴充套件是IEnumerable<T>,Queryable大多數擴充套件是IQueryable<T>。

1.Enumerable類中的常用方法:

(1).Range():一個引數是起始數,一個是要生成的結果數。

public static IEnumerable<int> Range(int start,int count) { 
      long max = ((long)start) + count - 1;
      if (count < 0 || max > Int32.MaxValue) throw Error.ArgumentOutOfRange("count"); 
      return RangeIterator(start,count);
    }

    static IEnumerable<int> RangeIterator(int start,int count) { 
      for (int i = 0; i < count; i++) yield return start + i;
    } 

(2).Where():對集合進行過濾的一個方式,接受一個謂詞,並將其應用於原始集合中的每個元素。

 public static IEnumerable<TSource> Where<TSource>(this IEnumerable<TSource> source,Func<TSource,bool> predicate) {
      if (source == null) throw Error.ArgumentNull("source"); 
      if (predicate == null) throw Error.ArgumentNull("predicate"); 
      if (source is Iterator<TSource>) return ((Iterator<TSource>)source).Where(predicate);
      if (source is TSource[]) return new WhereArrayIterator<TSource>((TSource[])source,predicate); 
      if (source is List<TSource>) return new WhereListIterator<TSource>((List<TSource>)source,predicate);
      return new WhereEnumerableIterator<TSource>(source,predicate);
    }

 public WhereEnumerableIterator(IEnumerable<TSource> source,bool> predicate) { 
        this.source = source;
        this.predicate = predicate; 
      }

以上分別介紹了Range()和Where()兩個方法,該類中還主要包含select()、orderby()等等方法。

2.Queryable類中的常用方法:

(1).IQueryable介面:

 /// <summary>
 /// 提供對未指定資料型別的特定資料來源的查詢進行計算的功能。
 /// </summary>
 /// <filterpriority>2</filterpriority>
 public interface IQueryable : IEnumerable
 {
  /// <summary>
  /// 獲取與 <see cref="T:System.Linq.IQueryable"/> 的例項關聯的表示式目錄樹。
  /// </summary>
  /// 
  /// <returns>
  /// 與 <see cref="T:System.Linq.IQueryable"/> 的此例項關聯的 <see cref="T:System.Linq.Expressions.Expression"/>。
  /// </returns>
  Expression Expression { get; }
  /// <summary>
  /// 獲取在執行與 <see cref="T:System.Linq.IQueryable"/> 的此例項關聯的表示式目錄樹時返回的元素的型別。
  /// </summary>
  /// 
  /// <returns>
  /// 一個 <see cref="T:System.Type"/>,表示在執行與之關聯的表示式目錄樹時返回的元素的型別。
  /// </returns>
  Type ElementType { get; }
  /// <summary>
  /// 獲取與此資料來源關聯的查詢提供程式。
  /// </summary>
  /// 
  /// <returns>
  /// 與此資料來源關聯的 <see cref="T:System.Linq.IQueryProvider"/>。
  /// </returns>
  IQueryProvider Provider { get; }
 }

(2).Where():

 public static IQueryable<TSource> Where<TSource>(this IQueryable<TSource> source,Expression<Func<TSource,bool>> predicate) { 
      if (source == null)
        throw Error.ArgumentNull("source"); 
      if (predicate == null)
        throw Error.ArgumentNull("predicate");
      return source.Provider.CreateQuery<TSource>(
        Expression.Call( 
          null,((MethodInfo)MethodBase.GetCurrentMethod()).MakeGenericMethod(typeof(TSource)),new Expression[] { source.Expression,Expression.Quote(predicate) } 
          ));
    } 

(3).Select():

 public static IQueryable<TResult> Select<TSource,TResult>(this IQueryable<TSource> source,TResult>> selector) {
      if (source == null)
        throw Error.ArgumentNull("source");
      if (selector == null) 
        throw Error.ArgumentNull("selector");
      return source.Provider.CreateQuery<TResult>( 
        Expression.Call( 
          null,((MethodInfo)MethodBase.GetCurrentMethod()).MakeGenericMethod(typeof(TSource),typeof(TResult)),Expression.Quote(selector) }
          ));
    }

以上是對擴充套件方法中兩個類進行了一個簡單的解析。

四.擴充套件方法例項:

由於擴充套件方法實際是對一個靜態方法的呼叫,所以CLR不會生成程式碼對呼叫方法的表示式的值進行null值檢查

1.異常處理程式碼:

  /// <summary>
  /// 為引數驗證提供有用的方法
  /// </summary>
  public static class ArgumentValidator
  {
    /// <summary>
    /// 如果argumentToValidate為空,則丟擲一個ArgumentNullException異常
    /// </summary>
    public static void ThrowIfNull(object argumentToValidate,string argumentName)
    {
      if (null == argumentName)
      {
        throw new ArgumentNullException("argumentName");
      }

      if (null == argumentToValidate)
      {
        throw new ArgumentNullException(argumentName);
      }
    }

    /// <summary>
    /// 如果argumentToValidate為空,則丟擲一個ArgumentException異常
    /// </summary>
    public static void ThrowIfNullOrEmpty(string argumentToValidate,string argumentName)
    {
      ThrowIfNull(argumentToValidate,argumentName);

      if (argumentToValidate == string.Empty)
      {
        throw new ArgumentException(argumentName);
      }
    }

    /// <summary>
    /// 如果condition為真,則丟擲ArgumentException異常
    /// </summary>
    /// <param name="condition"></param>
    /// <param name="msg"></param>
    public static void ThrowIfTrue(bool condition,string msg)
    {
      ThrowIfNullOrEmpty(msg,"msg");

      if (condition)
      {
        throw new ArgumentException(msg);
      }
    }

    /// <summary>
    /// 如果指定目錄存在該檔案則丟擲FileNotFoundException異常
    /// </summary>
    /// <param name="fileSytemObject"></param>
    /// <param name="argumentName"></param>
    public static void ThrowIfDoesNotExist(FileSystemInfo fileSytemObject,String argumentName)
    {
      ThrowIfNull(fileSytemObject,"fileSytemObject");
      ThrowIfNullOrEmpty(argumentName,"argumentName");

      if (!fileSytemObject.Exists)
      {
        throw new FileNotFoundException("'{0}' not found".Fi(fileSytemObject.FullName));
      }
    }

    public static string Fi(this string format,params object[] args)
    {
      return FormatInvariant(format,args);
    }

    /// <summary>
    /// 格式化字串和使用<see cref="CultureInfo.InvariantCulture">不變的文化</see>.
    /// </summary>
    /// <remarks>
    /// <para>這應該是用於顯示給使用者的任何字串時使用的“B”>“B”>“”。它意味著日誌
    ///訊息,異常訊息,和其他型別的資訊,不使其進入使用者介面,或不會
    ///無論如何,對使用者都有意義;).</para>
    /// </remarks>
    public static string FormatInvariant(this string format,params object[] args)
    {
      ThrowIfNull(format,"format");

      return 0 == args.Length ? format : string.Format(CultureInfo.InvariantCulture,format,args);
    }

    /// <summary>
    /// 如果時間不為DateTimeKind.Utc,則丟擲ArgumentException異常
    /// </summary>
    /// <param name="argumentToValidate"></param>
    /// <param name="argumentName"></param>
    public static void ThrowIfNotUtc(DateTime argumentToValidate,String argumentName)
    {
      ThrowIfNullOrEmpty(argumentName,"argumentName");

      if (argumentToValidate.Kind != DateTimeKind.Utc)
      {
        throw new ArgumentException("You must pass an UTC DateTime value",argumentName);
      }
    }
  }

2.列舉擴充套件方法:

 public static class EnumExtensions
  {
    /// <summary>
    /// 獲取名字
    /// </summary>
    /// <param name="e"></param>
    /// <returns></returns>
    public static string GetName(this Enum e)
    {
      return Enum.GetName(e.GetType(),e);
    }

    /// <summary>
    /// 獲取名字和值
    /// </summary>
    /// <param name="enumType">列舉</param>
    /// <param name="lowerFirstLetter">是否轉化為小寫</param>
    /// <returns></returns>
    public static Dictionary<string,int> GetNamesAndValues( this Type enumType,bool lowerFirstLetter)
    {
      //由於擴充套件方法實際是對一個靜態方法的呼叫,所以CLR不會生成程式碼對呼叫方法的表示式的值進行null值檢查
      ArgumentValidator.ThrowIfNull(enumType,"enumType");
      //獲取列舉名稱陣列
      var names = Enum.GetNames(enumType);
      //獲取列舉值陣列
      var values = Enum.GetValues(enumType);

      var d = new Dictionary<string,int>(names.Length);

      for (var i = 0; i < names.Length; i++)
      {
        var name = lowerFirstLetter ? names[i].LowerFirstLetter() : names[i];
        d[name] = Convert.ToInt32(values.GetValue(i));
      }

      return d;
    }

    /// <summary>
    /// 轉換為小寫
    /// </summary>
    /// <param name="s"></param>
    /// <returns></returns>
    public static string LowerFirstLetter(this string s)
    {
      ArgumentValidator.ThrowIfNull(s,"s");

      return char.ToLowerInvariant(s[0]) + s.Substring(1);
    }
  }

五.總結:

在本文中,主要對擴充套件方法進行了一些規則說明、宣告方式,使用方式,以及對擴充套件方法的意義和擴充套件方法的原理進行了簡單的解答。並在本文的最後給了一個列舉的擴充套件方法程式碼。

以上就是C# 擴充套件方法小結的詳細內容,更多關於C# 擴充套件方法的資料請關注我們其它相關文章!