Διαδρομές
/
C#
C#
/
Ύλη
/
Ισότητα
Ισ

Ισότητα σε C#

5 ασκήσεις

Σχετικά με την έννοια Ισότητα

Η άσκηση προγραμματισμού παρουσιάζει μια σειρά από ιδιότητες της ισότητας στη C#:

Object.Equals()

Θέματα που καλύπτει η άσκηση προγραμματισμού

  • Οι απλοί τύποι (συμβολοσειρές και πρωτόγονοι τύποι) συνήθως ελέγχονται για ισότητα με τους τελεστές == και !=. Αυτό θεωρείται πιο ιδιωματικό από τη χρήση της μεθόδου Equals(), η οποία είναι επίσης διαθέσιμη σε αυτούς τους τύπους. Οι προγραμματιστές Java πρέπει να προσέχουν, όταν χειρίζονται συμβολοσειρές, ότι ο == συγκρίνει κατ' αξία στη C# αλλά κατ' αναφορά στη Java, όταν επιστρέφουν στην προηγούμενή τους γλώσσα.
  • Οι τύποι αναφοράς (στιγμιότυπα κλάσεων) συγκρίνονται με τη μέθοδο Equals() που κληρονομείται από το object. Αν ο στόχος του ελέγχου ισότητας είναι να διαπιστώσεις ότι δύο αντικείμενα είναι ακριβώς το ίδιο στιγμιότυπο, τότε αρκεί να βασιστείς στην υλοποίηση του object. Αν όχι, πρέπει να κάνεις override τη μέθοδο object.Equals().
  • Αν ξέρεις ότι όλα τα στιγμιότυπα της κλάσης σου δημιουργούνται σε ένα μόνο σημείο, για παράδειγμα χαρακτήρες σε κάποιο παιχνίδι ή προσομοίωση, τότε η ισότητα αναφοράς αρκεί. Ωστόσο, είναι πιθανό να δημιουργηθούν πολλά στιγμιότυπα της ίδιας οντότητας του πραγματικού κόσμου (για παράδειγμα, από μια βάση δεδομένων, από είσοδο χρήστη, μέσω ενός αιτήματος ιστού). Σε αυτή την περίπτωση, πρέπει να ελεγχθούν για ισότητα οι τιμές που προσδιορίζουν μοναδικά την οντότητα. Επομένως, πρέπει να κάνεις override τη μέθοδο Equals().
  • Μια μέθοδος Equals() που έχει γίνει override θα περιέχει ελέγχους ισότητας σε μέλη απλών τύπων με == και σε τύπους αναφοράς με αναδρομικές κλήσεις στη μέθοδο Equals().
class StatusBar
{
    private readonly int width = 200, height = 20;

    public override bool Equals(object other)
    {
        // ... null and type checks and performance optimisations
        return width == (other as StatusBar).width && height == (other as StatusBar).height;
    }
}
class Window
{
    private readonly string title = "Main";
    private readonly StatusBar statusBar = new StatusBar();

    public override bool Equals(object other)
    {
        // ... null and type checks and performance optimisations
        return title == (other as Window).title && statusBar.Equals((other as Window).statusBar);
    }
}

  • Η στατική μέθοδος object.ReferenceEquals() χρησιμοποιείται για τη σύγκριση δύο αντικειμένων ώστε να διαπιστωθεί αν είναι ένα και το αυτό στιγμιότυπο. Αυτό προσφέρει σαφήνεια και είναι απαραίτητο όταν έχουν γίνει override/overload οι Equals() και ==.
var winA = new Window(); // above code shows that all windows are equal
var winB = new Window();
ReferenceEquals(winA, winB);
// => false
var winC = winA;
ReferenceEquals(winA, winC);
// => true

Δευτερεύοντα θέματα

  • Εκτός από τη μέθοδο public override bool Equals(object obj), τα IDE συνήθως δημιουργούν και το overload protected bool Equals(FacialFeatures other) για χρήση όταν εμπλέκεται κληρονομικότητα. Μια παράγωγη κλάση μπορεί να καλέσει τη μέθοδο Equals() της βασικής κλάσης και μετά να προσθέσει τον δικό της έλεγχο.
  • Μην χρησιμοποιείς τον == εκτός αν έχεις κάνει overload στον τελεστή ==, καθώς και στη μέθοδο Equals() της κλάσης σου (δες την άσκηση operator-overloading) ή αν σε ενδιαφέρει μόνο αν οι αναφορές είναι ίσες.
  • Οι έλεγχοι ισότητας σε structs καλύπτονται στην άσκηση structs.
  • Η ισότητα των tuples καλύπτεται στην άσκηση tuples.
  • Πολλοί προγραμματιστές βασίζονται στα IDE τους για να τους παρέχουν υλοποίηση των μεθόδων ισότητας, αφού αυτά φροντίζουν για όλες τις λεπτομέρειες της ισότητας. Για παράδειγμα, το RIDER της JetBrains (έκδοση 2020.1) δημιουργεί τις παρακάτω μεθόδους ισότητας για μια κλάση:
protected bool Equals(T other)
{
    return field1 == other.field1 && field2.Equals(other.field2);
}

public override bool Equals(object obj)
{
    if (ReferenceEquals(null, obj)) return false;
    if (ReferenceEquals(this, obj)) return true;
    if (obj.GetType() != this.GetType()) return false;
    return Equals((T) obj);
}
  • Να είσαι προσεκτικός αν αποφασίσεις να βελτιώσεις τον κώδικα που δημιουργεί το IDE σου. Αυτός ο κώδικας είναι γενικά θωρακισμένος απέναντι σε null και σε αντικείμενα λανθασμένου τύπου και αρκετά καλά βελτιστοποιημένος.
  • Οι έλεγχοι για την ισότητα delegates δεν συζητούνται ειδικά σε αυτή την άσκηση.
  • Δεν υπάρχουν ενσωματωμένοι έλεγχοι ισότητας για πίνακες ούτε για τις περισσότερες συλλογές. Το LINQ (που συζητείται σε επόμενες ασκήσεις) παρέχει τη μέθοδο SequenceEquals(), αλλά χωρίς το LINQ το ζήτημα είναι να διατρέξεις και τις δύο συλλογές και να συγκρίνεις τα στοιχεία ένα προς ένα.
  • Για μια συζήτηση σχετικά με το πώς να χρησιμοποιείς τους == και != με τις δικές σου κλάσεις, δες την άσκηση operator-overloading.

Object.GetHashCode()

  • Η μέθοδος object.GetHashCode() επιστρέφει έναν κωδικό κατακερματισμού με τη μορφή ενός ακέραιου 32 bit. Ο κωδικός κατακερματισμού χρησιμοποιείται από κλάσεις λεξικών και συνόλων, όπως τα Dictionary<T> και HashSet<T>, για να αποθηκεύουν και να ανακτούν αντικείμενα με αποδοτικό τρόπο. Στην περίπτωση των λεξικών, ο κατακερματισμός σχετίζεται με τα κλειδιά.
  • Υπάρχει μια προσδοκία μεταξύ των προγραμματιστών C# ότι αν κάνεις override τη μέθοδο Equals() θα κάνεις override και τη GetHashCode(). Υπάρχει μια σχέση μεταξύ των Equals() και GetHashCode() που πρέπει να ισχύει για τη σωστή συμπεριφορά των κλάσεων λεξικών και συνόλων κατακερματισμού, καθώς και οποιωνδήποτε άλλων χρησιμοποιούν κωδικό κατακερματισμού. Αναμένεται να υλοποιήσεις τη μέθοδο έτσι ώστε να μην στήσεις παγίδες για τους συντηρητές που μπορεί αργότερα να προσθέσουν μια συλλογή βασισμένη σε κωδικό κατακερματισμού.
  • Η σχέση μεταξύ κωδικού κατακερματισμού και ισότητας είναι ότι αν δύο αντικείμενα είναι ίσα (η Equal() επιστρέφει την τιμή αληθής), τότε η GetHashCode() των δύο αντικειμένων πρέπει να επιστρέφει την ίδια τιμή. Αυτό δεν ισχύει στην αντίστροφη κατεύθυνση. Δεν είναι συμμετρικό. Φαντάσου μια συνάρτηση αναζήτησης που πρώτα πηγαίνει σε έναν "κάδο" με βάση τον κωδικό κατακερματισμού και μετά ξεχωρίζει το συγκεκριμένο στοιχείο χρησιμοποιώντας τον έλεγχο ισότητας.
  • Ο ευκολότερος τρόπος να δημιουργήσεις έναν κωδικό κατακερματισμού είναι να καλέσεις τη μέθοδο HashCode.Combine() περνώντας τις τιμές που χρησιμοποιούνται στον έλεγχο ισότητας (ή ένα υποσύνολό τους). Να έχεις υπόψη σου ότι όσο περισσότερες πληροφορίες δίνεις στη Combine(), τόσο πιο αποδοτική είναι πιθανό να είναι η υλοποίηση του κατακερματισμού.
public class Assessment
{
    private int rating;
    private Person boss;

    public override int GetHashCode()
    {
        return HashCode.Combine(rating, boss);
    }
}
  • Οι τιμές που χρησιμοποιούνται στον έλεγχο ισότητας πρέπει να είναι σταθερές όσο χρησιμοποιείται η συλλογή κατακερματισμού. Αν προσθέσεις ένα αντικείμενο στη συλλογή με ένα σύνολο τιμών και μετά αλλάξεις αυτές τις τιμές, ο κωδικός κατακερματισμού δεν θα δείχνει πλέον στον σωστό "κάδο". Στην πράξη, αυτό σημαίνει ότι το αντικείμενο πρέπει να είναι αμετάβλητο. Άλλες προσεγγίσεις κινδυνεύουν να δημιουργήσουν παγίδες για τους συντηρητές. Η αμεταβλητότητα συζητείται σε άλλες ασκήσεις.
  • Είναι πιθανό να μπορείς να σχεδιάσεις έναν καλύτερο κωδικό κατακερματισμού από αυτόν που παράγουν οι ρουτίνες της βιβλιοθήκης, αλλά είτε αυτό οφείλεται στο ότι έχεις λεπτομερή κατανόηση των χαρακτηριστικών των δεδομένων είτε στο ότι πρόκειται για μια πολύ απλή συλλογή όπου οι τιμές μπορούν να χρησιμοποιηθούν απευθείας χωρίς κατακερματισμό. Μπορεί να μην αξίζει τον επιπλέον κόπο.

Βελτιώσεις απόδοσης

Για να βελτιώσεις ελαφρώς την απόδοση, ειδικά όταν τα αντικείμενα ανήκουν σε συλλογές, μπορείς να προσθέσεις ένα overload, το public bool Equals(T other).

Αυτό θα γλιτώσει έναν ορισμένο αριθμό ελέγχων τύπου για τους τύπους αναφοράς και θα γλιτώσει ένα βήμα boxing για τους τύπους τιμών, καθώς δεν θα χρειάζεται να μετατραπούν σε αντικείμενο (boxing) ως όρισμα στη μέθοδο public override bool Equals(object other).

Αν προσθέσεις τη διεπαφή IEquatable<T> στην κλάση σου, αυτό θα απαιτήσει να υλοποιηθεί το overload. Εκτός αν ο κώδικάς σου περιέχει ρουτίνες που δέχονται αντικείμενα τύπου IEquatable<T> (και υποτίθεται ότι κάνουν κάτι ενδιαφέρον σχετικά με την ισότητα ανεξάρτητα από την κλάση που το υλοποιεί), δεν υπάρχει πραγματικά κανένας άλλος επιτακτικός λόγος να συμπεριλάβεις τη διεπαφή.

IEqualityComparer<T>

Αν έχεις μια κλάση που μπορεί να προσδιοριστεί μοναδικά με δύο διαφορετικούς τρόπους, για παράδειγμα μια κλάση Person που έχει ένα SSID και μια μοναδική διεύθυνση email, τότε το .NET παρέχει έναν τρόπο ώστε δύο διαφορετικές συλλογές να χρησιμοποιούν διαφορετικούς ελέγχους κωδικού κατακερματισμού και ισότητας. Η καθεμία μπορεί να έχει διαφορετική υλοποίηση του IEqualityComparer<T> με τη δική της μέθοδο Equals() και GetHashCode(). Μπορείς να έχεις ένα λεξικό με κλειδί το SSID και ένα άλλο με κλειδί τη διεύθυνση email.

Όπου χρησιμοποιείται το IEqualityComparer<T>, συνήθως θα κάνεις και πάλι override τις Equals() και GetHashCode() στην κλάση του στοιχείου σου, για να αποφύγεις προβλήματα έξω από τις κλάσεις συλλογών.

Μια σκέψη όταν χρησιμοποιείς το IEqualityComparer<T> είναι ότι οι ιδιωτικές μέθοδοι κ.λπ. δεν θα είναι διαθέσιμες για τον έλεγχο ισότητας.

Αν χρησιμοποιείται μόνο μία συλλογή κατακερματισμού, τότε μπορεί να είναι καλύτερα να αποφύγεις το IEqualityComparer<T> και να ενσωματώσεις την ισότητα και τον κωδικό κατακερματισμού στο ίδιο το αντικείμενο. Δεν είναι ιδανικό, καταρχάς, το ότι μια τόσο βασική εξάρτηση, όπως αυτή μεταξύ αντικειμένου και συλλογής κατακερματισμού, δεν μπορεί να επιβληθεί από τον μεταγλωττιστή, αλλά με τη λογική κατακερματισμού και σύγκρισης αλλού, θα ήταν ακόμα πιο εύκολο για έναν συντηρητή να αλλάξει τα μέλη μιας κλάσης χωρίς να λάβει υπόψη τις συνέπειες για τη συλλογή.

Σημείωση για την ισότητα κινητής υποδιαστολής

Ένας πρωτόγονος τύπος που μπορεί να δυσκολέψει τον απρόσεκτο προγραμματιστή είναι ο έλεγχος της ισότητας τιμών κινητής υποδιαστολής. Αυτό συζητείται στο έγγραφο about.md της έννοιας floating-point-numbers.

Ισότητα και κληρονομικότητα

Αυτό το άρθρο δείχνει μερικές από τις αποφάσεις που πρέπει να παρθούν σχετικά με την ισότητα και την κληρονομικότητα. Θα σε απασχολήσουν μόνο αν χειρίζεσαι στιγμιότυπα μιας κλάσης και μιας παράγωγης κλάσης και χρειάζεται να ελέγξεις την ισότητα μεταξύ των δύο.

Επεξεργασία μέσω GitHub Ο σύνδεσμος ανοίγει σε νέο παράθυρο ή καρτέλα

Μάθε την έννοια Ισότητα

Η εξάσκηση είναι κλειδωμένη

Ξεκλείδωσε 4 ακόμη ασκήσεις για να εξασκηθείς στην έννοια Ισότητα