Fork me on GitHub

12/23/2009

[C# 筆記] Interfaces 介面

  • 自訂型別可以實現內建 interface 來擴充自己的功能。
  • interface 只是一群 abstract members 的集合。interface 表達一種 behavior 可以讓 class 或 structure 選擇去 implement 這樣的 behavior。
  • interface 當中只有 property 和 method 成員,而且所有成員都是 abstract。
  • Interface Types 和 Abstract Base Class 的差異

    • Abstrace Base Class 可以定義 constructor, field 和 nonabstract members;但是 Interface 只能包含 abstract members。

    • 由 abstract parent class 所建構出的多型有一個主要的限制,就是只有 derived types 支援 abstract parent 定義的成員。因此無法要求在不同繼承階層下的 types 支援相同的多型介面。相反的,一旦定義了 interface,他可以被任何型別 implement,再任意的繼承階層之中,甚至任意的 namespace 或 assembly。

    • 假如有一個 method 叫做 CloneMe() 並且 take 一個 ICloneable interface 為輸入參數,我們就可以將任何實做了 ICloneable interface 的物件 (即使這些物件不屬於同一個繼承階層) 傳入 CloneMe() 方法當中。換句話說,透過 interface 可以讓不屬於同一個 inherence hierachy 的 class 也能實現多型。

    • abstract base class 另外一個限制是,所有 derived type 針對每一個 abstract member 都要提供具體的 implement,即使有時候 derived type 去實現那個 member 沒有甚麼意義的時候,也是得照做。interface 則可以避免這個缺點,它就像是一個隨插即用的 usb,需要他的時候在 implement 他即可。

  • Defining Custom Interfaces

    • 所有 interface members 都是隱含宣告為 public 和 abstract。

    • Interfaces 只是單純的 protocol 不能包含實做。

  • Implementing an Interface

    • 如果一個類別要繼承又要 plug-in interface 的話,在 : 後面要先寫繼承,然後再寫 interface。

    • 一旦 plug-in 了該 interface,就必須實現所有 interface 當中的 member。

  • Invoking Interface Members at the Object Level

    • 一旦你的 class plug-in 了某個 interface,接下來該怎麼使用 interface 提供的 functionality 呢? 直接的做法是,宣告一個物件,然後呼叫 class 當中實現完成的 interface function。但是除非你就是這個 class 的實作者,不然經常妳是不知道這個 class 是否 support 某個 interface,於是就產生了這個問題: How can we dynamically determine the set of interfaces supported by a type?

      • 其中一個解法是 explicit cast,觀察 Circle class 是否 support IPointy interfaceCircle

        c = new Circle("Lisa");
        IPointy itfPt = null;
        try {
        itfPt = (IPointy)c;
        }
        catch (InvalidCastException e) {

        }

      • 第二個方法則是使用 "as" 或 "is","as" 如果不 support 會 return "null";"is" 如果不 support 會 return "false"。





  • Interfaces As Parameters

    • Interface 是 valid .NET types 因此也可當作 methods 的參數。只要任何 implement 了該 interface 的物件都可以傳遞進 methods 當中。

  • Interfaces As Return Values



  • Arrays of Interface Types

  • Resolving Name Clashes via Explicit Interface Implementation

    • 由於 class 並沒有限制可以 plug 上多少的 interface,當一個 class plug 上多個 interface的時候,interfaces 當中的 member 可能會有重覆命名的情況發生 (Name Clashes)。

    • 若 A class plug-in 了 B 和 C interfaces,B 和 C 都有 D method,如果 A class 直接實現 D  方法,並無法鑑別出 B 和 C 有何差異。因此需要  explicit interface implementation: 就是在方法的前面明確的指出是哪一個 interface ( e.g. void B.D (arg) {...} )。

    • 並且注意,在使用 explicit interface implementation 時前方不需要加 access modifier,其將自動為 private。因此,這些成員將無法透過 object level 取得,也無法透過 . 來看到該成員。

    • 因此你必須透過 explicit casting 來取得該成員的功能。例如: B a_b = (B)A; a_b.D,先將 A 轉成 B interface a_b,再從 a_b 取出 D method。

    • 凡透過 explicit interface implementation 的 member 都將被設成 private,某種程度來說是一種封裝,而想要更進一步利用 interface 功能者,則要自行用 explicit casting 提取出來使用。

  • Interface Hierarchies

    • when an interface extends an existing interface, it inherits the abstract members defined by the parent type(s)

    • Interface hierarchies can be useful when you wish to extend the functionality of an existing interface without breaking existing code bases

    • we are able to invoke each method at the object level (as they are all public) as well as extract out a reference to each supported interface explicitly via casting

    • Multiple inheritance for interface types is a-okay

  • Interfaces can be extremely useful when

    • You have a single hierarchy where only a subset of the derived types support a common behavior

    • You need to model a common behavior that is found across multiple hierarchies with no common parent class beyond System.Object

  • Building Enumerable Types

    • C# 中的 foreach 可以 loop 陣列當中的每一個 element,其實真正的關鍵是任何型別只要支援一個方法 GetEnumerator 都可以使用 foreach。

    • 假如你自行創建了一個類別,並宣告了一個陣列當中存放該類別創造出的物件,那麼並無法使用 foreach 去 loop,compiler 會告訴你該類別並沒有實現 GetEnumerator 這個方法。

    • GetEnumerator 這個方法存在於 IEnumerable interface 當中,此介面潛藏於 System.Collections 當中,支援此 interface 的型別代表可以 expose sub items to caller。

    • 要使自定的物件可以使用 foreach,一個方式就是精確的在該類別當中清楚的定義 IEnumerable 當中每一個 member,或者比較簡單的方式,在類別當中宣告一個 GetEnumerator 方法,在方法的定義中使用 Array 本身的 GetEnumerator 方法當作 return 值。

    • 由於 GetEnumerator 方法的返回值是一個 IEnumerable 介面,如上述所做 object user 可以呼叫 GetEnumerator 方法取得 IEnumerable 介面進而使用該介面當中的其他方法取得陣列中的物件,若要避免 object user 取得 IEnumerable 介面可以使用 explicit interface implementation。

    • 另外一種方法就是利用 iterator,使用這種方式一樣要在類別當中宣告一個 GetEnumerator 方法並且返回值也是 IEnumerable 介面,不同的是該類別不需要 plug-in 任何 interface。




    • 也可以不使用 internal 的 foreach (如上圖),而使用 yield return 的好處是,可以將 class 當中的內部資料丟出去給 foreach 使用。

    • yield 這個 keyword 不一定要在GetEnumerator 中使用,可以用在任何 method 當中,這些方法技術上的名稱是 named iterators,建構這種方法必須清楚他的回傳值是 IEnumerable interface。

    • 這個方法的好處是可以讓一個單一的 container  定義多種方式去拿到他的回傳值,例如 順排 或 逆排。





  • Building Cloneable Objects (ICloneable)

    • public interface ICloneable
      {
        object Clone();
      }
    • value type 和 reference type 的不同就是當你 assign value type 的變數給另一個變數的時候是複製一份,而 reference type 的話則是指向同一個物件的記憶體位置。所以,當你想要讓自定的 class (reference type) 可以擁有像 value type 那樣的 "複製一份" 的功能,就需要實做  ICloneable interface,這個 interface 定義了一個方法 Clone()

    • 實做  Clone() 方法依 class 的定義各有不同,不過基本上就是將成員變數的值複製到一個新的物件,再將這個物件 return 給 user。注意: Clone() 方法 return 值是一般的物件型別,因此要 assign 的時候要用 explicit cast ! ( e.g. Point p4 = (Point)p3.Clone(); )

    • 當類別中並沒有任何 reference type 的成員,可以用更簡易的方法實做 Clone() 方法,

      public object Clone()
      {
        // Copy each field of the Point member by member.
        return this.MemberwiseClone();
      }

    • 但是,當類別當中有 reference type 的成員時,使用 MemberwiseClone() 會導致 shallow copy,即該 reference type 成員並沒有被複製一份,導致兩個不同物件之間更動一個物件的該 reference type 成員,另一物件的該 reference type 成員也會跟著更動。
    • 假若你希望達到 deep copy 的話,則必須在 Clone() 當中執行完 MemberwiseClone() 之後,針對 reference type 的成員,再另行宣告一個新的物件,將當前的值 copy 進去,然後再刷新 MemberwiseClone() return 出來的物件的該成員。
      public object Clone()
      {
        // First get a shallow copy.
        Point newPoint = (Point)this.MemberwiseClone();

        // Then fill in the gaps.
        PointDescription currentDesc = new PointDescription();
        currentDesc.petName = this.desc.petName;
        newPoint.desc = currentDesc;
        return newPoint;
      }

  • Building Comparable Objects (IComparable)

    • 此介面允許物件根據特定的 key 來排序。

      public interface IComparable
      {
        int CompareTo(object o);
      }


    • 假設你自定了一個類別 Car,並且宣告了一個陣列 CarArray 當中裝有該類別的物件,這時你呼叫 CarArray.Sort() 時會跳出 exception 告訴你需要實現 IComparable 介面。

      // The iteration of the Car can be ordered
      // based on the CarID.
      public class Car : IComparable
      {
      ...
        // IComparable implementation.
        int IComparable.CompareTo(object obj)
        {
          Car temp = (Car)obj;
          if(this.carID > temp.carID)
            return 1; (come after, 越大的排越後面)
          if(this.carID < temp.carID)
            return -1; (come before, 越小排越前面)
          else
            return 0;
        }
      }

    • 若你希望按照類別中的某個成員變數來排序,而該成員變數又是屬於 intrinsic type 的話,可以直接套用該變數的 CompareTo() 方法。

      int IComparable.CompareTo(object obj)
      {
        Car temp = (Car)obj;
        return this.carID.CompareTo(temp.carID);
      }

    • Specifying Multiple Sort Orders (IComparer),當你想要根據不只一個成員來做排序的話,那麼就要利用到另外一個 interface "IComparer", 該介面定義如下:

      // A generic way to compare two objects.
      interface IComparer
      {
        int Compare(object o1, object o2);
      }

    • IComparer 介面並非在你想要排序的型別中實現,通常是在 helper class 當中實現這個介面。而這個 helper class 剛好可以當作 System.Array 當中 Sort() 方法的一個輸入參數:

      // This helper class is used to sort an array of Cars by pet name.
      using System.Collections;
      public class PetNameComparer : IComparer
      {
        // Test the pet name of each object.
        int IComparer.Compare(object o1, object o2)
        {
          Car t1 = (Car)o1;
          Car t2 = (Car)o2;
          return String.Compare(t1.PetName, t2.PetName);
        }
      }

    • static void Main(string[] args)
    • {
    • ...
    • // Now sort by pet name.
    • Array.Sort(myAutos, new PetNameComparer());

    • }

    • Custom Properties,Custom Sort Types 也可以將 helper class 寫進該類別的 properties 當中,這樣就可以直接在 Sort() 的參數直接取 property,更為直覺。

No comments:

Post a Comment