Galileo Computing <openbook>
Galileo Computing - Programming the Net
Galileo Computing - Programming the Net


C# von Eric Gunnerson
Die neue Sprache für Microsofts .NET-Plattform
C# - Zum Katalog
gp Kapitel 29 System.Array und die Auflistungsklassen
  gp 29.1 Sortieren und Suchen
  gp 29.2 ICloneable
  gp 29.3 Entwurfsrichtlinien

Kapitel 29 System.Array und die Auflistungsklassen

In diesem Kapitel erhalten Sie einen Überblick darüber, welche Klassen verfügbar sind. Anschließend werden die einzelnen Klassen besprochen, und es wird anhand von Beispielen aufgezeigt, welche Schnittstellen und Funktionen für bestimmte Features benötigt werden.


Galileo Computing

29.1 Sortieren und Suchen  downtop

Die Auflistungsklassen der Frameworks unterstützen einige nützliche Funktionen für die Sortierung und binäre Suchläufe. Die Array-Klasse bietet die gleiche Funktionalität, jedoch über statische Funktionen, nicht durch Mitgliedsfunktionen.

Das Sortieren eines Arrays aus integer-Werten kann sehr einfach sein:

using System;
class Test
{
    public static void Main()
    {
        int[]    arr = {5, 1, 10, 33, 100, 4};
        Array.Sort(arr);
        foreach (int v in arr)
            Console.WriteLine("Element: {0}", v);
    }
}

Dieser Code erzeugt die folgende Ausgabe:

1
4
5
10
33
100

Dies ist sehr bequem für die integrierten Typen, funktioniert jedoch nicht für Klassen oder Strukturen, da die Routine die richtige Sortierreihenfolge nicht kennt.


Galileo Computing

29.1.1 Implementieren von IComparable  downtop

Die Frameworks halten für Klassen oder Strukturen einige sehr gute Methoden zur Sortierung der Klassen- oder Strukturinstanzen bereit. Bei der einfachsten Methode implementiert das Objekt die IComparable-Schnittstelle:

using System;
public class Employee: IComparable
{
    public Employee(string name, int id)
    {
        this.name = name;
        this.id = id;
    }

    int IComparable.CompareTo(object obj)
    {
        Employee emp2 = (Employee) obj;
        if (this.id > emp2.id)
            return(1);
        if (this.id < emp2.id)
            return(-1);
        else
            return(0);
    }

    public override string ToString()
    {
        return(String.Format("{0}:{1}", name, id));
    }

    string    name;
    int    id;
}
class Test
{
    public static void Main()
    {
        Employee[] arr = new Employee[4];
        arr[0] = new Employee("George", 1);
        arr[1] = new Employee("Fred", 2);
        arr[2] = new Employee("Tom", 4);
        arr[3] = new Employee("Bob", 3);

        Array.Sort(arr);
        foreach (Employee emp in arr)
            Console.WriteLine("Employee: {0}", emp);
    }
}

Dieses Programm erzeugt die folgende Ausgabe:

Employee: George:1
Employee: Fred:2
Employee: Bob:3
Employee: Tom:4

Diese Implementierung ermöglicht lediglich eine Sortierung; die Klasse könnte so definiert werden, dass eine auf Mitarbeiter-ID oder Name basierende Sortierung vorgenommen wird, dem Benutzer kann jedoch die Auswahl der Sortierreihenfolge nicht ermöglicht werden.


Galileo Computing

29.1.2 Verwenden von IComparer  downtop

Die Designer der Frameworks haben die Fähigkeit zur Definition mehrerer Sortierreihenfolgen bereitgestellt. Jede Sortierreihenfolge wird durch die IComparer-Schnittstelle ausgedrückt, die geeignete Schnittstelle wird der Sortier- oder Suchfunktion übergeben.

Die IComparer-Schnittstelle kann für Employee jedoch nicht implementiert werden, da jede Klasse eine Schnittstelle nur einmal implementieren kann. Es könnte also nur eine Sortierreihenfolge bereitgestellt werden. Es ist für jede Sortierung eine separate Klasse erforderlich, die IComparer implementiert. Die Klasse ist sehr einfach, da nur die Compare()-Funktion implementiert wird:

using System;
using System.Collections;
class Employee
{
    public string name;
}
class SortByNameClass: IComparer
{
    public int Compare(object obj1, object obj2)
    {
        Employee emp1 = (Employee) obj1;
        Employee emp2 = (Employee) obj2;

        return(String.Compare(emp1.name, emp2.name));
    }
}

Das Compare()-Mitglied trägt zwei Objekte als Parameter. Da die Klasse nur zur Mitarbeitersortierung eingesetzt werden sollte, werden die object-Parameter per Typumwandlung zu Employee. Anschließend wird die in string enthaltene Compare()-Funktion für den Vergleich verwendet.

Die Employee-Klasse wird demnach folgendermaßen überarbeitet. Die Klassen für die Sortierungen werden innerhalb der Employee-Klasse verschachtelt:

using System;
using System.Collections;

public class Employee: IComparable
{
    public Employee(string name, int id)
    {
        this.name = name;
        this.id = id;
    }

    int IComparable.CompareTo(object obj)
    {
        Employee emp2 = (Employee) obj;
        if (this.id > emp2.id)
            return(1);
        if (this.id < emp2.id)
            return(-1);
        else
            return(0);
    }

    public override string ToString()
    {
        return(name + ":" + id);
    }

    public class SortByNameClass: IComparer
    {
        public int Compare(object obj1, object obj2)
        {
            Employee emp1 = (Employee) obj1;
            Employee emp2 = (Employee) obj2;

            return(String.Compare(emp1.name, emp2.name));
        }
    }

    public class SortByIdClass: IComparer
    {
        public int Compare(object obj1, object obj2)
        {
            Employee emp1 = (Employee) obj1;
            Employee emp2 = (Employee) obj2;

            return(((IComparable) emp1).CompareTo(obj2));
        }
    }

    string    name;
    int    id;
}
class Test
{
    public static void Main()
    {
        Employee[] arr = new Employee[4];
        arr[0] = new Employee("George", 1);
        arr[1] = new Employee("Fred", 2);
        arr[2] = new Employee("Tom", 4);
        arr[3] = new Employee("Bob", 3);

        Array.Sort(arr, (IComparer) new Employee.SortByNameClass());
            // Mitarbeiter sind jetzt nach Name sortiert

        foreach (Employee emp in arr)
            Console.WriteLine("Employee: {0}", emp);

        Array.Sort(arr, (IComparer) new Employee.SortByIdClass());
            // Mitarbeiter sind jetzt nach ID sortiert

        foreach (Employee emp in arr)
            Console.WriteLine("Employee: {0}", emp);

        ArrayList arrList = new ArrayList();
        arrList.Add(arr[0]);
        arrList.Add(arr[1]);
        arrList.Add(arr[2]);
        arrList.Add(arr[3]);
        arrList.Sort((IComparer) new Employee.SortByNameClass());

        foreach (Employee emp in arrList)
            Console.WriteLine("Employee: {0}", emp);

        arrList.Sort();    // Standard = nach ID

        foreach (Employee emp in arrList)
            Console.WriteLine("Employee: {0}", emp);
    }
}

Der Benutzer kann nun die Sortierung angeben und bei Bedarf zwischen den unterschiedlichen Sortierreihenfolgen wechseln. Dieses Beispiel zeigt, wie die gleichen Funktionen bei Verwendung der ArrayList-Klasse arbeiten, auch wenn Sort() eine Mitgliedsfunktion und keine statische Funktion ist.


Galileo Computing

29.1.3 IComparer als Eigenschaft  downtop

Das Sortieren mit der Employee-Klasse ist weiterhin ein wenig merkwürdig, da der Benutzer eine Instanz der geeigneten Sortierungsklasse erstellen und in IComparer umwandeln muss. Dem Benutzer kann diese Aufgabe durch den Einsatz statischer Eigenschaften abgenommen werden:

using System;
using System.Collections;

public class Employee: IComparable
{
    public Employee(string name, int id)
    {
        this.name = name;
        this.id = id;
    }

    int IComparable.CompareTo(object obj)
    {
        Employee emp2 = (Employee) obj;
        if (this.id > emp2.id)
            return(1);
        if (this.id < emp2.id)
            return(-1);
        else
            return(0);
    }

    public static IComparer SortByName
    {
        get
        {
            return((IComparer) new SortByNameClass());
        }
    }

    public static IComparer SortById
    {
        get
        {
            return((IComparer) new SortByIdClass());
        }
    }

    public override string ToString()
    {
        return(name + ":" + id);
    }

    class SortByNameClass: IComparer
    {
        public int Compare(object obj1, object obj2)
        {
            Employee emp1 = (Employee) obj1;
            Employee emp2 = (Employee) obj2;

            return(String.Compare(emp1.name, emp2.name));
        }
    }

    class SortByIdClass: IComparer
    {
        public int Compare(object obj1, object obj2)
        {
            Employee emp1 = (Employee) obj1;
            Employee emp2 = (Employee) obj2;

            return(((IComparable) emp1).CompareTo(obj2));
        }
    }

    string    name;
    int    id;
}
class Test
{
    public static void Main()
    {
        Employee[] arr = new Employee[4];
        arr[0] = new Employee("George", 1);
        arr[1] = new Employee("Fred", 2);
        arr[2] = new Employee("Tom", 4);
        arr[3] = new Employee("Bob", 3);

        Array.Sort(arr, Employee.SortByName);
            // Mitarbeiter sind jetzt nach Name sortiert

        foreach (Employee emp in arr)
            Console.WriteLine("Employee: {0}", emp);

        Array.Sort(arr, Employee.SortById);
            // Mitarbeiter sind jetzt nach ID sortiert

        foreach (Employee emp in arr)
            Console.WriteLine("Employee: {0}", emp);

        ArrayList arrList = new ArrayList();
        arrList.Add(arr[0]);
        arrList.Add(arr[1]);
        arrList.Add(arr[2]);
        arrList.Add(arr[3]);
        arrList.Sort(Employee.SortByName);

        foreach (Employee emp in arrList)
            Console.WriteLine("Employee: {0}", emp);

        arrList.Sort();    // Standard = nach ID

        foreach (Employee emp in arrList)
            Console.WriteLine("Employee: {0}", emp);
    }
}

Die statischen Eigenschaften SortByName und SortById erstellen eine Instanz der geeigneten Sortierungsklasse, wandeln sie in IComparer um und geben sie an den Benutzer zurück. Dies vereinfacht das Benutzermodell erheblich; die Eigenschaften SortByName und SortById geben IComparer zurück, es ist also offensichtlich, dass diese zur Sortierung eingesetzt werden können. Alles, was der Benutzer dann noch tun muss, ist die geeignete Sortierungseigenschaft für den IComparer-Parameter anzugeben.


Galileo Computing

29.1.4 Überladen relationaler Operatoren  downtop

Weist eine Klasse eine Reihenfolge auf, die in IComparable ausgedrückt wird, kann es sinnvoll sein, die weiteren relationalen Operatoren zu überladen. Wie = und != müssen weitere Operatoren paarweise deklariert werden, < und > stellen ein Paar, >= und <= das andere Paar dar:

using System;
public class Employee: IComparable
{
    public Employee(string name, int id)
    {
        this.name = name;
        this.id = id;
    }

    int IComparable.CompareTo(object obj)
    {
        Employee emp2 = (Employee) obj;
        if (this.id > emp2.id)
            return(1);
        if (this.id < emp2.id)
            return(-1);
        else
            return(0);
    }
    public static bool operator <(
    Employee emp1,
    Employee emp2)
    {
        IComparable    icomp = (IComparable) emp1;
        return(icomp.CompareTo (emp2) < 0);
    }
    public static bool operator >(
    Employee emp1,
    Employee emp2)
    {
        IComparable    icomp = (IComparable) emp1;
        return(icomp.CompareTo (emp2) > 0);
    }
    public static bool operator <=(
    Employee emp1,
    Employee emp2)
    {
        IComparable    icomp = (IComparable) emp1;
        return(icomp.CompareTo (emp2) <= 0);
    }
    public static bool operator >=(
    Employee emp1,
    Employee emp2)
    {
        IComparable    icomp = (IComparable) emp1;
        return(icomp.CompareTo (emp2) >= 0);
    }

    public override string ToString()
    {
        return(name + ":" + id);
    }

    string    name;
    int    id;
}
class Test
{
    public static void Main()
    {
        Employee george = new Employee("George", 1);
        Employee fred = new Employee("Fred", 2);
        Employee tom = new Employee("Tom", 4);
        Employee bob = new Employee("Bob", 3);

        Console.WriteLine("George < Fred: {0}", george < fred);
        Console.WriteLine("Tom >= Bob: {0}", tom >= bob);
    }
}

Dieser Code erzeugt die folgende Ausgabe:

George < Fred: false
Tom >= Bob: true

Galileo Computing

29.1.5 Erweiterte Verwendung von Hashes  downtop

In einigen Situationen kann es wünschenswert sein, mehr als einen Hashcode für ein spezifisches Objekt zu definieren. Diese Methode kann beispielsweise eingesetzt werden, um basierend auf Mitarbeiter-ID oder Mitarbeitername nach einem Mitarbeiter zu suchen. Sie erreichen dies durch Implementierung der IHashCodeProvider-Schnittstelle, wodurch eine alternative Hashfunktion bereitgestellt wird. Des Weiteren ist eine passende Implementierung von IComparer erforderlich. Diese neuen Implementierungen werden an die Erstellungsroutine der Hashtable übergeben:

using System;
using System.Collections;

public class Employee: IComparable
{
    public Employee(string name, int id)
    {
        this.name = name;
        this.id = id;
    }

    int IComparable.CompareTo(object obj)
    {
        Employee emp2 = (Employee) obj;
        if (this.id > emp2.id)
            return(1);
        if (this.id < emp2.id)
            return(-1);
        else
            return(0);
    }
    public override int GetHashCode()
    {
        return(id);
    }
    public static IComparer SortByName
    {
        get
        {
            return((IComparer) new SortByNameClass());
        }
    }

    public static IComparer SortById
    {
        get
        {
            return((IComparer) new SortByIdClass());
        }
    }
    public static IHashCodeProvider HashByName
    {
        get
        {
            return((IHashCodeProvider) new HashByNameClass());
        }
    }
    public override string ToString()
    {
        return(name + ":" + id);
    }

    class SortByNameClass: IComparer
    {
        public int Compare(object obj1, object obj2)
        {
            Employee emp1 = (Employee) obj1;
            Employee emp2 = (Employee) obj2;

            return(String.Compare(emp1.name, emp2.name));
        }
    }

    class SortByIdClass: IComparer
    {
        public int Compare(object obj1, object obj2)
        {
            Employee emp1 = (Employee) obj1;
            Employee emp2 = (Employee) obj2;

            return(((IComparable) emp1).CompareTo(obj2));
        }
    }
    class HashByNameClass: IHashCodeProvider
    {
        public int GetHashCode(object obj)
        {
            Employee emp = (Employee) obj;
            return(emp.name.GetHashCode());
        }
    }

    string    name;
    int    id;
}
class Test
{
    public static void Main()
    {
        Employee herb = new Employee("Herb", 555);
        Employee george = new Employee("George", 123);
        Employee frank = new Employee("Frank", 111);
        Hashtable employees =
            new Hashtable(Employee.HashByName, Employee.SortByName);
        employees.Add(herb, "414 Evergreen Terrace");
        employees.Add(george, "2335 Elm Street");
        employees.Add(frank, "18 Pine Bluff Road");
        Employee herbClone = new Employee("Herb", 000);
        string address =(string) employees[herbClone];
        Console.WriteLine("{0} lives at {1}", herbClone, address);
    }
}

Diese Methode sollte nur sparsam eingesetzt werden. Es ist häufig einfacher, einen Wert offen zu legen (beispielsweise den Mitarbeiternamen als Eigenschaft) und diesen als Hashschlüssel zu verwenden.


Galileo Computing

29.2 ICloneable  downtop

Die Funktion object.MemberWiseClone() kann zum Erstellen von Klonen eines Objekts eingesetzt werden. Die Standardimplementierung dieser Funktion erzeugt eine »flache« Objektkopie (shallow copy); die Objektfelder werden exakt kopiert, nicht dupliziert. Sehen Sie sich folgendes Beispiel an:

using System;
class ContainedValue
{
    public ContainedValue(int count)
    {
        this.count = count;
    }
    public int count;
}
class MyObject
{
    public MyObject(int count)
    {
        this.contained = new ContainedValue(count);
    }
    public MyObject Clone()
    {
        return((MyObject) MemberwiseClone());
    }
    public ContainedValue contained;
}
class Test
{
    public static void Main()
    {
        MyObject    my = new MyObject(33);
        MyObject    myClone = my.Clone();
        Console.WriteLine(    "Values: {0} {1}",
                    my.contained.count,
                    myClone.contained.count);
        myClone.contained.count = 15;
        Console.WriteLine(    "Values: {0} {1}",
                    my.contained.count,
                    myClone.contained.count);
    }
}

Dieser Code erzeugt die folgende Ausgabe:

Values: 33 33
Values: 15 15

Da es sich bei der von MemberWiseClone() erzeugten Kopie um eine flache Kopie handelt, ist der in contained enthaltene Wert bei beiden Objekten gleich, und das Ändern eines Wertes innerhalb des ContainedValue-Objekts wirkt sich auf beide Instanzen von MyObject aus.

Wir benötigen eine »tiefe« Kopie (deep copy), bei der für die neue Instanz von MyObject eine neue Instanz von ContainedValue erzeugt wird. Dies wird durch Implementieren der ICloneable-Schnittstelle erreicht:

using System;
class ContainedValue
{
    public ContainedValue(int count)
    {
        this.count = count;
    }
    public int count;
}
class MyObject: ICloneable
{
    public MyObject(int count)
    {
        this.contained = new ContainedValue(count);
    }
    public object Clone()
    {
        Console.WriteLine("Clone");
        return(new MyObject(this.contained.count));
    }
    public ContainedValue contained;
}
class Test
{
    public static void Main()
    {
        MyObject    my = new MyObject(33);
        MyObject    myClone = (MyObject) my.Clone();
        Console.WriteLine(    "Values: {0} {1}",
                    my.contained.count,
                    myClone.contained.count);
        myClone.contained.count = 15;
        Console.WriteLine(    "Values: {0} {1}",
                    my.contained.count,
                    myClone.contained.count);
    }
}

Dieser Code erzeugt die folgende Ausgabe:

Values: 33 33
Values: 33 15

Der Aufruf von MemberWiseClone() führt nun zu einer neuen Instanz von ContainedValue, und die Inhalte dieser Instanz können geändert werden, ohne dass dies Auswirkungen auf die Inhalte von my hat.

Im Gegensatz zu anderen Schnittstellen, die für ein Objekt definiert werden können, wird ICloneable nicht von der Laufzeit aufgerufen; es wird lediglich sichergestellt, dass die Clone()-Funktion über die geeignete Signatur verfügt.


Galileo Computing

29.3 Entwurfsrichtlinien  downtop

Die implementierten virtuellen Funktionen und Schnittstellen sollten sich nach dem beabsichtigten Zweck eines Objekts richten. Die folgende Tabelle bietet hierzu einige Richtlinien:

Objektverwendung Funktion oder Schnittstelle
Allgemein ToString()
Arrays oder Auflistungen Equals(), operator==(), operator!=(), GetHashCode()
Sortierung oder binäre Suche IComparable
Mehrere Sortierreihenfolgen IComparer
Mehrere Hashsuchläufe IhashCodeProvider


Galileo Computing

29.3.1 Funktionen und Schnittstellen der Framework-Klassen  toptop

In der folgenden Tabelle wird zusammengefasst, welche Objektfunktionen oder -schnittstellen von den einzelnen Auflistungsklassen verwendet werden.

Array

Funktion Verwendung
IndexOf() Equals()
LastIndexOf() Equals()
Contains() Equals()
Sort() Equals(), IComparable
BinarySearch() Equals(), IComparable

ArrayList

Funktion Verwendung
IndexOf() Equals()
LastIndexOf() Equals()
Contains() Equals()
Sort() Equals(), IComparable
BinarySearch() Equals(), IComparable

Hashtable

Funktion Verwendung
HashTable() IHashCodeProvider, IComparable (optional)
Contains() GetHashCode(), Equals()
Item GetHashCode(), Equals()

SortedList

Funktion Verwendung
SortedList() IComparable
Contains() IComparable
ContainsKey() IComparable
ContainsValue() Equals
IndexOfKey() IComparable
IndexOfValue() Equals
Item Icomparable






1    Es wäre möglich, mit IComparable eine Sortierung und mit IComparer eine andere Sortierung zu implementieren, dies wäre jedoch sehr verwirrend für den Benutzer.

   

Select * from SQL Server 2000




Copyright © Galileo Press GmbH 2001 - 2002
Für Ihren privaten Gebrauch dürfen Sie die Online-Version natürlich ausdrucken. Ansonsten unterliegt das <openbook> denselben Bestimmungen, wie die gebundene Ausgabe: Das Werk einschließlich aller seiner Teile ist urheberrechtlich geschützt. Alle Rechte vorbehalten einschließlich der Vervielfältigung, Übersetzung, Mikroverfilmung sowie Einspeicherung und Verarbeitung in elektronischen Systemen.


[Galileo Computing]

Galileo Press GmbH, Gartenstraße 24, 53229 Bonn, fon: 0228.42150.0, fax 0228.42150.77, info@galileo-press.de