Η άσκηση προγραμματισμού παρουσιάζει μια σειρά από ιδιότητες της ισότητας στη C#:
Object.Equals()== και !=. Αυτό θεωρείται πιο ιδιωματικό από τη χρήση της μεθόδου Equals(), η οποία είναι επίσης διαθέσιμη σε αυτούς τους τύπους. Οι προγραμματιστές Java πρέπει να προσέχουν, όταν χειρίζονται συμβολοσειρές, ότι ο == συγκρίνει κατ' αξία στη C# αλλά κατ' αναφορά στη Java, όταν επιστρέφουν στην προηγούμενή τους γλώσσα.Equals() που κληρονομείται από το object. Αν ο στόχος του ελέγχου ισότητας είναι να διαπιστώσεις ότι δύο αντικείμενα είναι ακριβώς το ίδιο στιγμιότυπο, τότε αρκεί να βασιστείς στην υλοποίηση του object. Αν όχι, πρέπει να κάνεις override τη μέθοδο object.Equals().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.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);
}
SequenceEquals(), αλλά χωρίς το LINQ το ζήτημα είναι να διατρέξεις και τις δύο συλλογές και να συγκρίνεις τα στοιχεία ένα προς ένα.== και != με τις δικές σου κλάσεις, δες την άσκηση operator-overloading.Object.GetHashCode()object.GetHashCode() επιστρέφει έναν κωδικό κατακερματισμού με τη μορφή ενός ακέραιου 32 bit. Ο κωδικός κατακερματισμού χρησιμοποιείται από κλάσεις λεξικών και συνόλων, όπως τα Dictionary<T> και HashSet<T>, για να αποθηκεύουν και να ανακτούν αντικείμενα με αποδοτικό τρόπο. Στην περίπτωση των λεξικών, ο κατακερματισμός σχετίζεται με τα κλειδιά.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.
Αυτό το άρθρο δείχνει μερικές από τις αποφάσεις που πρέπει να παρθούν σχετικά με την ισότητα και την κληρονομικότητα. Θα σε απασχολήσουν μόνο αν χειρίζεσαι στιγμιότυπα μιας κλάσης και μιας παράγωγης κλάσης και χρειάζεται να ελέγξεις την ισότητα μεταξύ των δύο.