Advanced  Services Interfaces 2

Das Interface IComparable

Eines der wichtigsten Interfaces aus der API ist das Interface IComparable. Es schafft für beliebige Klassen die Möglichkeit zwei Obhekte ihrer Größe nach zu vergleichen, wobei der Entwickler bestimmt, wie der Vergleich von zwei Objekten gleichen Typs aussehen soll. Das Interface stellt eine Methode zur Verfügung, die man selbst mit einem passenden Inhalt füllen muß. Das Interface IComparable tritt in zwei verschiedenen Formen auf, außer der jetzt behandelten Form gibt es noch eine Variante mit einem Platzhalter für Typen (generische Form). Nach dem Kapitel über Generics werden also nochmal Interfaces behandelt.

Das Interface IComparable hat in der nichtgenerischen Form die folgende Gestalt:

public interface IComparable
{
   int CompareTo(Object another) ;
}

Die Idee ist die folgende: Wir möchten ein Array der Klasse Person sortieren und dafür die Utilityklasse System.Array verwenden. Hier gibt es eine statische Methode Sort(), der man Arrays beliebigen Typs übergeben kann und die man dann sortiert zurückerhält. Das funktioniert allerdings nur, wenn die Methode Sort() Objektvergleiche anstellen kann und dazu muß für diese Objekte eine Vergleichsmethode existieren und eben diese Methode bekommen wir über das Interface IComparable.


Die klassische Implementierung eines Interfaces

Die klassische Implementierung funktioniert wie in Java. Im folgenden Beispiel implementiert die Klasse Person das Interface System.IComparable.

Wir implementieren dieses Interface

class Person : IComparable
{
   public String Vorname { get; set; }
   public String Nachname { get; set; }

   public Person() : this("","")
   { }

   public Person(String vorname, String nachname)
   {
      this.Vorname = vorname;
      this.Nachname = nachname;
   }

   // Methoden
   public virtual void Println()
   {
      //Console.WriteLine("Println Person");
      string name = this.Vorname.Equals("") ? this.Nachname : this.Vorname + " " + this.Nachname;
      Console.WriteLine(name);
   }

   // Implementierung der Methode CompareTo des Interfaces IComparable
   public int CompareTo(Object another)
   {
      Console.WriteLine("CompareTo");
      return Nachname.CompareTo(((Person)another).Nachname);
   }

} // end Person

Die obige einfache Implementierung vergleicht nur die Nachnamen und stützt sich auf die CompareTo-Methode für Strings. Eine einfacher Testlauf kann so aussehen.

class Program
{
   static void Main(string[] args)
   {
      Person[] arr = { new Person("Adam", "Riese"), new Person("Leonhard", "Euler"), new Person("Carl Friedrich", "Gauss") };
      Array.Sort(arr);

      foreach (Person p in arr)
         p.Println();
   }
}

Es funktioniert. Die Methode Sort der Klasse Array weiß im Grunde nicht, was sie sortiert und braucht es auch nicht zu wissen. Die Methode verläßt sich darauf, daß die Elemente das übergebene Array vom Typ IComparable sind und verwendet zum Sortieren die zugehörige Methode CompareTo(). Wenn man in der CompareTo()-Methode eine Konsolmeldung ergänzt, dann sieht man die Aufrufe dieser Methode.

Es reicht nicht in Person nur die Methode zu schreiben ohne das Interface zu implementieren. Die Methode Sort() aus Array prüft nämlich, ob die Elemente des übergebenen Arrays vom Typ IComparable sind. Ist dies nicht der Fall so wird eine System.InvalidOperationException geworfen.


Die explizite (aber versteckte) Implementierung

C# bietet eine weitere Möglichkeit an, eine Schnittstelle zu implementieren. Dabei wird der Name des Interfaces explizit übernommen. Bei dieser Implementierung sind keine Modifier vor dem Returntyp zulässig (weder public, noch protected, noch private, noch internal, noch virtual). Ihre Verwendung führt zu einer Fehlermeldung des Compilers. Unser Beispiel zeigt, daß beide Implementierungen nebeneinander existieren können.

class Person : IComparable
{
   public String Vorname { get; set; }
   public String Nachname { get; set; }

   public Person() : this("","")
   { }

   public Person(String vorname, String nachname)
   {
      this.Vorname = vorname;
      this.Nachname = nachname;
   }

   // klassische Implementierung des Interfaces IComparable
   public int CompareTo(Object another)
   {
      Console.WriteLine("klassisch");
      return Nachname.CompareTo(((Person2)another).Nachname);
   }

   // explizite Implementierung mit dem Namen des Interfaces, kein Accessmodifier zulässig!
   int IComparable.CompareTo(object another)
   {
      Console.WriteLine("explizit");
      return Nachname.CompareTo(((Person2)another).Nachname);
   }
} // end Person

Für Objekte der Klasse Person ist lediglich die klassische Implementierung sichtbar. Die explizite Implementierung ist nur über einen kleinen Umweg sichtbar. Über einen Cast zum Typ der Schnittstelle kann man die Methode dann doch verwenden. Das folgende einfache Programm dokumentiert dies.

class Program
{
   static void Main(string[] args)
   {
      Person2 p1 = new Person2("Emmy","Noether");
      Person2 p2 = new Person2("Stefan", "Banach");
      Console.WriteLine(p1.CompareTo(p2));

      IComparable ic1 = p1;
      IComparable ic2 = p2;
      Console.WriteLine( ic1.CompareTo(ic2) );
   }
}

Das Programm macht die folgende Ausgabe:

Wenn wir jetzt nochmal ein Array von Person anlegen und das Array mit Hilfe der Methode Sort() aus der Klasse Array sortieren, so sehen wir, daß Sort() explizite Implementierung verwendet und geben so einen indirekten Beweis, daß Sort() die einzelnen Objekte auf den Typ IComparable hochcastet.

class Program
{
   static void Main(string[] args)
   {
      Person[] arr = { new Person("Adam", "Riese"), new Person("Leonhard", "Euler"), new Person("Carl Friedrich", "Gauss") };
      Array.Sort(arr);
      foreach (Person p in arr)
         p.Println();
   }
}

Das Programm macht die folgende Ausgabe:


Wozu gibt es die explizite Implementierung

Bedingt durch die Entwicklung von C# kommt es vor, daß ein generisches Interface von einem nichtgenerischen Interface erbt. Das ist etwa bei den Interfaces IEnumerable und IEnumerator der Fall. Hier erbt das generische Interface jeweils vom nichtgenerischen und im generischen Fall muß man daher beide Interfaces implementieren. Hier ist es nützlich, die nichtgenerische Variante explizit zu implementieren.

Das Konzept der expliziten Interfaceimplementierung wird ausführlich auf stackoverflow diskutiert. stackoverflow.com/questions/143405/c-interfaces-implicit-and-explicit-implementation


Properties in Interfaces

Da Properties im Hintergrund durch Methoden realisiert werden lieg es nahe, daß man in einem Interface auch Properties vereinbaren kann. Diese müssen dann durch eine nachfolgende Klasse realisiert werden.

public interface IClubMember
{
   String ID { get; set; }     // Modifier nicht erlaubt
   // get und set sind beide public, dies kann bei der Implementierung nicht mehr geändert werden.
}

Realisierung mit Visualstudio: Visualstudio kann die Implementierung automatisch generieren. Dazu geht man m it dem Cursor auf den Namen des Interfaces und drückt Ctrl .

Das sieht dann so aus.

public class Vip : IClubMember
{
   // klassisch
   public string ID
   {
      get
      {
         throw new NotImplementedException();
      }
      set
      {
         throw new NotImplementedException();
      }
   }

   // oder explizit
   string IClubMember.ID
   {
      get
      {
         throw new NotImplementedException();
      }
      set
      {
         throw new NotImplementedException();
      }
   }
}

Fast einfacher ist jedoch, automatische Properties zu schreiben

class Vip2 : IClubMember
{
  public string ID { get; set; }
}