Γ # είναι μία από τις πολλές γλώσσες που στοχεύουν τη Microsoft Διάρκεια κοινής γλώσσας (CLR). Οι γλώσσες που στοχεύουν το CLR επωφελούνται από λειτουργίες όπως ενοποίηση μεταξύ γλωσσών και χειρισμό εξαιρέσεων, βελτιωμένη ασφάλεια, ένα απλοποιημένο μοντέλο αλληλεπίδρασης στοιχείων και υπηρεσίες εντοπισμού σφαλμάτων και προφίλ. Από τις σημερινές γλώσσες CLR, το C # χρησιμοποιείται ευρύτερα για πολύπλοκες, επαγγελματικές αναπτυξιακά έργα που στοχεύουν σε περιβάλλοντα υπολογιστή, κινητού ή διακομιστή των Windows.
Το C # είναι μια αντικειμενοστρεφής, έντονα δακτυλογραφημένη γλώσσα. Ο αυστηρός έλεγχος τύπου στο C #, τόσο στους χρόνους μεταγλώττισης όσο και στους χρόνους εκτέλεσης, οδηγεί στην αναφορά της πλειονότητας των τυπικών σφαλμάτων προγραμματισμού C # όσο το δυνατόν νωρίτερα και οι θέσεις τους επισημαίνονται με ακρίβεια. Αυτό μπορεί να εξοικονομήσει πολύ χρόνο C Sharp προγραμματισμός , σε σύγκριση με τον εντοπισμό της αιτίας των προβληματικών σφαλμάτων που μπορεί να προκύψουν πολύ μετά την παράβαση της λειτουργίας σε γλώσσες που είναι πιο φιλελεύθερες με την επιβολή της ασφάλειας τύπου. Ωστόσο, πολλοί κωδικοποιητές C # ακούσια (ή απρόσεκτα) απορρίπτουν τα οφέλη αυτής της ανίχνευσης, γεγονός που οδηγεί σε ορισμένα από τα θέματα που συζητούνται σε αυτό το σεμινάριο C #.
Αυτό το σεμινάριο περιγράφει 10 από τα πιο συνηθισμένα λάθη προγραμματισμού C # ή προβλήματα που πρέπει να αποφεύγονται από τους προγραμματιστές C # και τους παρέχει βοήθεια.
Ενώ τα περισσότερα από τα λάθη που συζητούνται σε αυτό το άρθρο είναι C # συγκεκριμένα, ορισμένα σχετίζονται επίσης με άλλες γλώσσες που στοχεύουν το CLR ή κάνουν χρήση του Βιβλιοθήκη κλάσης πλαισίου (FCL).
Οι προγραμματιστές του C ++, και πολλές άλλες γλώσσες, έχουν συνηθίσει να ελέγχουν εάν οι τιμές που εκχωρούν σε μεταβλητές είναι απλά τιμές ή είναι αναφορές σε υπάρχοντα αντικείμενα. Ωστόσο, στον προγραμματισμό C Sharp, αυτή η απόφαση λαμβάνεται από τον προγραμματιστή που έγραψε το αντικείμενο, όχι από τον προγραμματιστή που δημιουργεί το αντικείμενο και το εκχωρεί σε μια μεταβλητή. Αυτό είναι ένα κοινό «gotcha» για όσους προσπαθούν να μάθουν C # προγραμματισμό.
Εάν δεν γνωρίζετε εάν το αντικείμενο που χρησιμοποιείτε είναι τύπος τιμής ή τύπος αναφοράς, θα μπορούσατε να αντιμετωπίσετε κάποιες εκπλήξεις. Για παράδειγμα:
Point point1 = new Point(20, 30); Point point2 = point1; point2.X = 50; Console.WriteLine(point1.X); // 20 (does this surprise you?) Console.WriteLine(point2.X); // 50 Pen pen1 = new Pen(Color.Black); Pen pen2 = pen1; pen2.Color = Color.Blue; Console.WriteLine(pen1.Color); // Blue (or does this surprise you?) Console.WriteLine(pen2.Color); // Blue
Όπως μπορείτε να δείτε, τόσο το Point
και Pen
αντικείμενα δημιουργήθηκαν με τον ίδιο ακριβώς τρόπο, αλλά η τιμή point1
παρέμεινε αμετάβλητο όταν ένα νέο X
Η τιμή συντεταγμένης ανατέθηκε σε point2
, ενώ η τιμή pen1
ήταν τροποποιήθηκε όταν ανατέθηκε ένα νέο χρώμα στο pen2
. Μπορούμε λοιπόν συμπεραίνω ότι point1
και point2
το καθένα περιέχει το δικό του αντίγραφο ενός Point
αντικείμενο, ενώ pen1
και pen2
περιέχει αναφορές στο ίδιο Pen
αντικείμενο. Αλλά πώς μπορούμε να το γνωρίζουμε χωρίς να κάνουμε αυτό το πείραμα;
Η απάντηση είναι να δούμε τους ορισμούς των τύπων αντικειμένων (που μπορείτε εύκολα να κάνετε στο Visual Studio τοποθετώντας τον κέρσορα πάνω από το όνομα του τύπου αντικειμένου και πατώντας F12):
public struct Point { ... } // defines a “value” type public class Pen { ... } // defines a “reference” type
Όπως φαίνεται παραπάνω, στον προγραμματισμό C #, το struct
Η λέξη-κλειδί χρησιμοποιείται για τον καθορισμό ενός τύπου τιμής, ενώ το class
Η λέξη-κλειδί χρησιμοποιείται για τον ορισμό ενός τύπου αναφοράς. Για όσους έχουν φόντο C ++, οι οποίοι γοητεύτηκαν σε μια ψευδή αίσθηση ασφάλειας από τις πολλές ομοιότητες μεταξύ των λέξεων-κλειδιών C ++ και C #, αυτή η συμπεριφορά πιθανότατα προκαλεί έκπληξη που μπορεί να σας ζητήσει βοήθεια από ένα σεμινάριο C #.
Εάν πρόκειται να βασιστείτε σε κάποια συμπεριφορά που διαφέρει μεταξύ της τιμής και των τύπων αναφοράς - όπως η ικανότητα να περάσετε ένα αντικείμενο ως παράμετρος μεθόδου και να αλλάξετε αυτή την μέθοδο την κατάσταση του αντικειμένου - βεβαιωθείτε ότι αντιμετωπίζετε το σωστός τύπος αντικειμένου για την αποφυγή προβλημάτων προγραμματισμού C #.
Στο C #, οι τύποι τιμών δεν μπορούν να είναι μηδενικοί. Εξ ορισμού, οι τύποι τιμών έχουν μια τιμή και ακόμη και οι μη αρχικοποιημένες μεταβλητές των τύπων τιμών πρέπει να έχουν μια τιμή. Αυτό ονομάζεται η προεπιλεγμένη τιμή για αυτόν τον τύπο. Αυτό οδηγεί στο ακόλουθο, συνήθως απροσδόκητο αποτέλεσμα κατά τον έλεγχο εάν μια μεταβλητή δεν έχει αρχικοποιηθεί:
class Program { static Point point1; static Pen pen1; static void Main(string[] args) { Console.WriteLine(pen1 == null); // True Console.WriteLine(point1 == null); // False (huh?) } }
Γιατί δεν είναι point1
μηδενικό? Η απάντηση είναι ότι Point
είναι ένας τύπος τιμής και η προεπιλεγμένη τιμή για ένα Point
είναι (0,0), όχι μηδενικό. Η αποτυχία αναγνώρισης αυτού είναι ένα πολύ εύκολο (και κοινό) λάθος στο C #.
Πολλοί (αλλά όχι όλοι) τύποι τιμών έχουν IsEmpty
ιδιότητα την οποία μπορείτε να ελέγξετε για να δείτε εάν είναι ίση με την προεπιλεγμένη τιμή της:
Console.WriteLine(point1.IsEmpty); // True
Όταν κάνετε έλεγχο για να δείτε αν μια μεταβλητή έχει αρχικοποιηθεί ή όχι, βεβαιωθείτε ότι γνωρίζετε τι αξία θα έχει μια μη αρχικοποιημένη μεταβλητή αυτού του τύπου από προεπιλογή και μην βασίζεστε σε αυτήν την τιμή.
Υπάρχουν πολλοί διαφορετικοί τρόποι σύγκρισης συμβολοσειρών στο C #.
Αν και πολλοί προγραμματιστές χρησιμοποιούν το ==
χειριστή για σύγκριση συμβολοσειρών, είναι στην πραγματικότητα ένα από τα ελάχιστα επιθυμητές μεθόδους για χρήση, κυρίως επειδή δεν προσδιορίζει ρητά στον κώδικα ποιος τύπος σύγκρισης είναι επιθυμητός.
Αντίθετα, ο προτιμώμενος τρόπος δοκιμής για ισότητα συμβολοσειρών στον προγραμματισμό C # είναι με το Equals
μέθοδος:
public bool Equals(string value); public bool Equals(string value, StringComparison comparisonType);
Η πρώτη υπογραφή μεθόδου (δηλαδή, χωρίς την παράμετρο comparisonType
), είναι στην πραγματικότητα η ίδια με τη χρήση του ==
χειριστή, αλλά έχει το πλεονέκτημα ότι εφαρμόζεται ρητά στις συμβολοσειρές Εκτελεί μια κανονική σύγκριση των χορδών, η οποία είναι βασικά μια σύγκριση byte-by-byte. Σε πολλές περιπτώσεις, αυτός είναι ακριβώς ο τύπος σύγκρισης που θέλετε, ειδικά όταν συγκρίνετε συμβολοσειρές των οποίων οι τιμές ορίζονται μέσω προγραμματισμού, όπως ονόματα αρχείων, μεταβλητές περιβάλλοντος, χαρακτηριστικά κ.λπ. Σε αυτές τις περιπτώσεις, αρκεί μια κανονική σύγκριση να είναι πράγματι ο σωστός τύπος σύγκρισης για αυτήν την κατάσταση, το μόνο μειονέκτημα για τη χρήση του Equals
μέθοδος χωρίς | | + _ | είναι ότι κάποιος που διαβάζει τον κώδικα μπορεί να μην ξέρει τι είδους σύγκριση κάνετε.
Χρησιμοποιώντας το comparisonType
υπογραφή μεθόδου που περιλαμβάνει Equals
Κάθε φορά που συγκρίνετε συμβολοσειρές, ωστόσο, όχι μόνο θα κάνει τον κώδικα σαφέστερο, αλλά θα σας κάνει να σκεφτείτε ρητά για τον τύπο σύγκρισης που πρέπει να κάνετε. Αυτό είναι κάτι που αξίζει να κάνετε, γιατί ακόμα κι αν τα Αγγλικά δεν παρέχουν πολλές διαφορές μεταξύ συγκρίσεων που είναι ευαίσθητες στην τακτική και στον πολιτισμό, άλλες γλώσσες παρέχουν πολλές και η παραβίαση της πιθανότητας άλλων γλωσσών ανοίγει τον εαυτό σας σε πολλές δυνατότητες για λάθη στο δρόμο. Για παράδειγμα:
comparisonType
Η ασφαλέστερη πρακτική είναι να παρέχετε πάντα ένα string s = 'strasse'; // outputs False: Console.WriteLine(s == 'straße'); Console.WriteLine(s.Equals('straße')); Console.WriteLine(s.Equals('straße', StringComparison.Ordinal)); Console.WriteLine(s.Equals('Straße', StringComparison.CurrentCulture)); Console.WriteLine(s.Equals('straße', StringComparison.OrdinalIgnoreCase)); // outputs True: Console.WriteLine(s.Equals('straße', StringComparison.CurrentCulture)); Console.WriteLine(s.Equals('Straße', StringComparison.CurrentCultureIgnoreCase));
παράμετρος στο comparisonType
μέθοδος. Ακολουθούν ορισμένες βασικές οδηγίες:
Equals
ή CurrentCulture
).CurrentCultureIgnoreCase
ή Ordinal
).OrdinalIgnoreCase
και InvariantCulture
γενικά δεν πρέπει να χρησιμοποιούνται εκτός από πολύ περιορισμένες περιστάσεις, επειδή οι συνηθισμένες συγκρίσεις είναι πιο αποτελεσματικές. Εάν είναι απαραίτητη μια σύγκριση με γνώμονα την κουλτούρα, θα πρέπει συνήθως να γίνεται ενάντια στην τρέχουσα κουλτούρα ή σε άλλη συγκεκριμένη κουλτούρα.Εκτός από το InvariantCultureIgnoreCase
μέθοδος, οι συμβολοσειρές παρέχουν επίσης το Equals
μέθοδος, η οποία σας παρέχει πληροφορίες σχετικά με τη σχετική σειρά των συμβολοσειρών αντί για μια δοκιμή για ισότητα. Αυτή η μέθοδος είναι προτιμότερη από τα Compare
, <
, <=
και >
χειριστές, για τους ίδιους λόγους όπως συζητήθηκε παραπάνω - για την αποφυγή προβλημάτων C #.
Στο C # 3.0, η προσθήκη του Ερώτημα ολοκληρωμένης γλώσσας (LINQ) στη γλώσσα άλλαξε για πάντα τον τρόπο με τον οποίο συλλέγονται και χειρίζονται οι συλλογές. Έκτοτε, εάν χρησιμοποιείτε επαναλαμβανόμενες δηλώσεις για χειρισμό συλλογών, δεν χρησιμοποιήσατε το LINQ όταν μάλλον θα έπρεπε.
Μερικοί προγραμματιστές C # δεν γνωρίζουν καν την ύπαρξη του LINQ, αλλά ευτυχώς αυτός ο αριθμός γίνεται όλο και μικρότερος. Πολλοί εξακολουθούν να πιστεύουν ότι, λόγω της ομοιότητας μεταξύ των λέξεων-κλειδιών LINQ και των δηλώσεων SQL, η μόνη χρήση του είναι στον κώδικα που ερωτά τις βάσεις δεδομένων.
Ενώ το ερώτημα βάσης δεδομένων είναι μια πολύ διαδεδομένη χρήση των δηλώσεων LINQ, στην πραγματικότητα λειτουργούν σε οποιαδήποτε αναρίθμητη συλλογή (δηλαδή, οποιοδήποτε αντικείμενο που εφαρμόζει τη διεπαφή IEnumerable). Έτσι, για παράδειγμα, εάν είχατε μια σειρά από λογαριασμούς, αντί να γράψετε μια λίστα C # foreach:
>=
θα μπορούσατε απλά να γράψετε:
decimal total = 0; foreach (Account account in myAccounts) { if (account.Status == 'active') { total += account.Balance; } }
Ενώ αυτό είναι ένα πολύ απλό παράδειγμα για το πώς να αποφύγετε αυτό το κοινό πρόβλημα προγραμματισμού C #, υπάρχουν περιπτώσεις όπου μια μόνο δήλωση LINQ μπορεί εύκολα να αντικαταστήσει δεκάδες δηλώσεις σε έναν επαναληπτικό βρόχο (ή ένθετους βρόχους) στον κώδικά σας. Και λιγότερο γενικός κώδικας σημαίνει λιγότερες ευκαιρίες για εισαγωγή σφαλμάτων. Λάβετε υπόψη, ωστόσο, ότι μπορεί να υπάρξει αντιστάθμιση όσον αφορά την απόδοση. Σε σενάρια κρίσιμης απόδοσης, ειδικά όταν ο επαναληπτικός κώδικας σας μπορεί να κάνει υποθέσεις σχετικά με τη συλλογή σας που δεν μπορεί να κάνει το LINQ, φροντίστε να κάνετε μια σύγκριση απόδοσης μεταξύ των δύο μεθόδων.
Το LINQ είναι ιδανικό για την αφαίρεση του έργου χειρισμού συλλογών, είτε πρόκειται για αντικείμενα στη μνήμη, πίνακες βάσης δεδομένων ή έγγραφα XML. Σε έναν τέλειο κόσμο, δεν θα χρειαστεί να γνωρίζετε ποια είναι τα υποκείμενα αντικείμενα. Αλλά το σφάλμα εδώ υποθέτει ότι ζούμε σε έναν τέλειο κόσμο. Στην πραγματικότητα, πανομοιότυπες δηλώσεις LINQ μπορούν να επιστρέψουν διαφορετικά αποτελέσματα όταν εκτελούνται με τα ίδια ακριβώς δεδομένα, εάν αυτά τα δεδομένα έχουν διαφορετική μορφή.
Για παράδειγμα, εξετάστε την ακόλουθη δήλωση:
decimal total = (from account in myAccounts where account.Status == 'active' select account.Balance).Sum();
Τι συμβαίνει εάν ένα από τα αντικείμενα decimal total = (from account in myAccounts where account.Status == 'active' select account.Balance).Sum();
ισούται με 'Ενεργό' (σημειώστε το κεφάλαιο Α); Λοιπόν, εάν account.Status
ήταν ένα myAccounts
αντικείμενο (το οποίο είχε ρυθμιστεί με την προεπιλεγμένη διαμόρφωση χωρίς κεφαλαία), το DbSet
η έκφραση θα ταιριάζει με αυτό το στοιχείο. Ωστόσο, εάν where
βρισκόταν σε έναν πίνακα στη μνήμη, δεν ταιριάζει, και επομένως θα αποφέρει ένα διαφορετικό αποτέλεσμα για το σύνολο.
Αλλά περιμένετε ένα λεπτό. Όταν μιλήσαμε νωρίτερα για σύγκριση συμβολοσειρών, είδαμε ότι το myAccounts
χειριστής πραγματοποίησε μια κανονική σύγκριση των χορδών. Γιατί λοιπόν σε αυτήν την περίπτωση είναι το ==
ο χειριστής εκτελεί μια σύγκριση χωρίς ευαισθητοποίηση;
Η απάντηση είναι ότι όταν τα υποκείμενα αντικείμενα σε μια δήλωση LINQ είναι αναφορές σε δεδομένα πίνακα SQL (όπως συμβαίνει με το αντικείμενο Entity Framework DbSet σε αυτό το παράδειγμα), η δήλωση μετατρέπεται σε δήλωση T-SQL. Οι χειριστές ακολουθούν τους κανόνες προγραμματισμού T-SQL και όχι τους κανόνες προγραμματισμού C #, οπότε η σύγκριση στην παραπάνω περίπτωση καταλήγει να είναι αδιάφορη.
Σε γενικές γραμμές, παρόλο που το LINQ είναι ένας χρήσιμος και συνεπής τρόπος για να υποβάλετε ερωτήματα σε συλλογές αντικειμένων, στην πραγματικότητα πρέπει ακόμα να γνωρίζετε εάν η δήλωσή σας θα μεταφραστεί σε κάτι διαφορετικό από το C # κάτω από την κουκούλα για να διασφαλίσετε ότι η συμπεριφορά του κώδικα σας θα να είναι όπως αναμενόταν κατά το χρόνο εκτέλεσης.
Όπως αναφέρθηκε προηγουμένως, οι δηλώσεις LINQ λειτουργούν σε οποιοδήποτε αντικείμενο που εφαρμόζει το IEnumerable. Για παράδειγμα, η ακόλουθη απλή συνάρτηση θα προσθέσει τα υπόλοιπα σε οποιαδήποτε συλλογή λογαριασμών:
==
Στον παραπάνω κώδικα, ο τύπος της παραμέτρου myAccounts δηλώνεται ως public decimal SumAccounts(IEnumerable myAccounts) { return myAccounts.Sum(a => a.Balance); }
. Από IEnumerable
αναφορές a myAccounts
μέθοδος (το C # χρησιμοποιεί τη γνωστή 'σημείωση σημείων' για αναφορά μιας μεθόδου σε μια τάξη ή διεπαφή), αναμένουμε να δούμε μια μέθοδο που ονομάζεται Sum
σχετικά με τον ορισμό του Sum()
διεπαφή. Ωστόσο, ο ορισμός του IEnumerable
, δεν αναφέρεται σε κανένα IEnumerable
μέθοδο και απλά μοιάζει με αυτό:
Sum
Πού είναι λοιπόν το public interface IEnumerable : IEnumerable { IEnumerator GetEnumerator(); }
καθορισμένη μέθοδος; Το C # είναι έντονα δακτυλογραφημένο, οπότε αν η αναφορά στο Sum()
Η μέθοδος δεν ήταν έγκυρη, ο μεταγλωττιστής C # σίγουρα θα τον σηματοδότησε ως σφάλμα. Γνωρίζουμε λοιπόν ότι πρέπει να υπάρχει, αλλά πού; Επιπλέον, πού είναι οι ορισμοί όλων των άλλων μεθόδων που παρέχει το LINQ για την αναζήτηση ή τη συγκέντρωση αυτών των συλλογών;
Η απάντηση είναι ότι Sum
δεν είναι μια μέθοδος που ορίζεται στο Sum()
διεπαφή. Αντίθετα, είναι μια στατική μέθοδος (που ονομάζεται «μέθοδος επέκτασης») που ορίζεται στο IEnumerable
τάξη:
System.Linq.Enumerable
Λοιπόν, τι κάνει μια μέθοδο επέκτασης διαφορετική από οποιαδήποτε άλλη στατική μέθοδο και τι μας επιτρέπει να έχουμε πρόσβαση σε άλλες κατηγορίες;
Το διακριτικό χαρακτηριστικό μιας μεθόδου επέκτασης είναι το namespace System.Linq { public static class Enumerable { ... // the reference here to “this IEnumerable source” is // the magic sauce that provides access to the extension method Sum public static decimal Sum(this IEnumerable source, Func selector); ... } }
τροποποιητής στην πρώτη του παράμετρο. Αυτή είναι η «μαγεία» που την αναγνωρίζει στον μεταγλωττιστή ως μέθοδο επέκτασης. Ο τύπος της παραμέτρου που τροποποιεί (σε αυτήν την περίπτωση this
) δηλώνει την κλάση ή τη διεπαφή που θα εμφανιστεί στη συνέχεια να εφαρμόζει αυτήν τη μέθοδο.
(Ως δευτερεύον σημείο, δεν υπάρχει τίποτα μαγικό για την ομοιότητα μεταξύ του ονόματος της διεπαφής IEnumerable
και του ονόματος της κλάσης IEnumerable
στην οποία ορίζεται η μέθοδος επέκτασης. Αυτή η ομοιότητα είναι απλώς μια αυθαίρετη στυλιστική επιλογή .)
Με αυτήν την κατανόηση, μπορούμε επίσης να δούμε ότι το Enumerable
Η συνάρτηση που παρουσιάσαμε παραπάνω θα μπορούσε αντ 'αυτού να είχε εφαρμοστεί ως εξής:
sumAccounts
Το γεγονός ότι θα μπορούσαμε να το εφαρμόσουμε με αυτόν τον τρόπο δημιουργεί το ερώτημα γιατί έχουν καθόλου μεθόδους επέκτασης; Μέθοδοι επέκτασης είναι ουσιαστικά μια ευκολία της γλώσσας προγραμματισμού C # που σας επιτρέπει να 'προσθέσετε' μεθόδους σε υπάρχοντες τύπους χωρίς να δημιουργήσετε έναν νέο παράγωγο τύπο, να μεταγλωττιστεί εκ νέου ή να τροποποιήσετε με άλλο τρόπο τον αρχικό τύπο.
Οι μέθοδοι επέκτασης περιλαμβάνονται στο πεδίο συμπεριλαμβάνοντας ένα public decimal SumAccounts(IEnumerable myAccounts) { return Enumerable.Sum(myAccounts, a => a.Balance); }
δήλωση στην κορυφή του αρχείου. Πρέπει να γνωρίζετε ποιος χώρος ονομάτων C # περιλαμβάνει τις μεθόδους επέκτασης που αναζητάτε, αλλά είναι πολύ εύκολο να προσδιορίσετε μόλις γνωρίζετε τι είναι αυτό που αναζητάτε.
Όταν ο μεταγλωττιστής C # συναντά μια κλήση μεθόδου σε μια παρουσία ενός αντικειμένου και δεν βρίσκει αυτήν τη μέθοδο που ορίζεται στην κλάση αντικειμένου αναφοράς, τότε εξετάζει όλες τις μεθόδους επέκτασης που βρίσκονται εντός του πεδίου για να προσπαθήσει να βρει μια που ταιριάζει με την απαιτούμενη μέθοδο υπογραφή και τάξη. Εάν εντοπίσει ένα, θα μεταβιβάσει την αναφορά παρουσίας ως το πρώτο όρισμα σε αυτήν τη μέθοδο επέκτασης, και στη συνέχεια τα υπόλοιπα ορίσματα, εάν υπάρχουν, θα μεταφερθούν ως επόμενα ορίσματα στη μέθοδο επέκτασης. (Εάν ο μεταγλωττιστής C # δεν εντοπίσει αντίστοιχη μέθοδο επέκτασης εντός του πεδίου, θα εμφανίσει σφάλμα.)
Οι μέθοδοι επέκτασης είναι ένα παράδειγμα «συντακτικής ζάχαρης» από την πλευρά του μεταγλωττιστή C #, που μας επιτρέπει να γράφουμε κώδικα που είναι (συνήθως) πιο καθαρός και πιο διατηρήσιμος. Σαφέστερο, δηλαδή εάν γνωρίζετε τη χρήση τους. Διαφορετικά, μπορεί να είναι λίγο συγκεχυμένο, ειδικά στην αρχή.
Παρόλο που σίγουρα υπάρχουν πλεονεκτήματα στη χρήση μεθόδων επέκτασης, μπορούν να προκαλέσουν προβλήματα και μια κραυγή για βοήθεια προγραμματισμού C # για εκείνους τους προγραμματιστές που δεν τους γνωρίζουν ή δεν τους καταλαβαίνουν σωστά. Αυτό ισχύει ιδιαίτερα όταν εξετάζετε δείγματα κώδικα στο διαδίκτυο ή σε οποιονδήποτε άλλο προ-γραπτό κώδικα. Όταν ένας τέτοιος κώδικας παράγει σφάλματα μεταγλωττιστή (επειδή χρησιμοποιεί μεθόδους που σαφώς δεν καθορίζονται στις τάξεις στις οποίες γίνεται επίκληση), η τάση είναι να πιστεύουμε ότι ο κώδικας ισχύει για διαφορετική έκδοση της βιβλιοθήκης ή σε διαφορετική βιβλιοθήκη εντελώς. Πολύς χρόνος μπορεί να αφιερωθεί αναζητώντας μια νέα έκδοση, ή φανταστική 'λείπει βιβλιοθήκη', που δεν υπάρχει.
Ακόμη και προγραμματιστές που είναι εξοικειωμένοι με τις μεθόδους επέκτασης εξακολουθούν να παγιδεύονται περιστασιακά, όταν υπάρχει μια μέθοδος με το ίδιο όνομα στο αντικείμενο, αλλά η υπογραφή της μεθόδου διαφέρει με λεπτό τρόπο από αυτήν της μεθόδου επέκτασης. Μπορεί να σπαταληθεί πολύς χρόνος αναζητώντας τυπογραφικό λάθος ή σφάλμα που δεν υπάρχει.
Η χρήση μεθόδων επέκτασης σε βιβλιοθήκες C # γίνεται όλο και πιο διαδεδομένη. Εκτός από το LINQ, το Μπλοκ Εφαρμογών Unity και το Πλαίσιο API Ιστού είναι παραδείγματα δύο πολύ χρησιμοποιούμενων σύγχρονων βιβλιοθηκών από τη Microsoft που χρησιμοποιούν επίσης μεθόδους επέκτασης και υπάρχουν πολλές άλλες. Όσο πιο σύγχρονο το πλαίσιο, τόσο πιθανότερο είναι να ενσωματώσει μεθόδους επέκτασης.
Φυσικά, μπορείτε να γράψετε και τις δικές σας μεθόδους επέκτασης. Συνειδητοποιήστε, ωστόσο, ότι ενώ οι μέθοδοι επέκτασης φαίνεται να επικαλούνται ακριβώς όπως οι κανονικές μέθοδοι εμφάνισης, αυτό είναι πραγματικά μια ψευδαίσθηση. Συγκεκριμένα, οι μέθοδοι επέκτασής σας δεν μπορούν να αναφέρουν ιδιωτικά ή προστατευμένα μέλη της τάξης που επεκτείνουν και επομένως δεν μπορούν να χρησιμεύσουν ως πλήρης αντικατάσταση για πιο παραδοσιακή κληρονομιά τάξης.
Το C # παρέχει μια μεγάλη ποικιλία αντικειμένων συλλογής, με τα ακόλουθα να είναι μόνο μια μερική λίστα: using [namespace];
, Array
, ArrayList
, BitArray
, BitVector32
, Dictionary
, HashTable
, HybridDictionary
, List
, NameValueCollection
, OrderedDictionary
, Queue, Queue
, SortedList
, Stack, Stack
, StringCollection
.
Ενώ μπορεί να υπάρχουν περιπτώσεις όπου πάρα πολλές επιλογές είναι τόσο κακές όσο όχι αρκετές επιλογές, αυτό δεν συμβαίνει με αντικείμενα συλλογής. Ο αριθμός των διαθέσιμων επιλογών μπορεί σίγουρα να λειτουργήσει προς όφελός σας. Αφιερώστε λίγο επιπλέον χρόνο εκ των προτέρων για έρευνα και επιλέξτε τον βέλτιστο τύπο συλλογής για το σκοπό σας. Αυτό θα οδηγήσει πιθανώς σε καλύτερη απόδοση και λιγότερο περιθώριο για λάθη.
Εάν υπάρχει ένας τύπος συλλογής που στοχεύει ειδικά στον τύπο του στοιχείου που έχετε (όπως συμβολοσειρά ή bit), προτιμάτε να το χρησιμοποιήσετε πρώτα. Η εφαρμογή είναι γενικά πιο αποτελεσματική όταν στοχεύει σε έναν συγκεκριμένο τύπο στοιχείου.
Για να επωφεληθείτε από την ασφάλεια τύπου C #, συνήθως προτιμάτε μια γενική διεπαφή από μια μη γενική. Τα στοιχεία μιας γενικής διεπαφής είναι του τύπου που καθορίζετε όταν δηλώνετε το αντικείμενο σας, ενώ τα στοιχεία των μη γενικών διεπαφών είναι αντικειμένου τύπου. Όταν χρησιμοποιείτε μια μη γενική διεπαφή, ο μεταγλωττιστής C # δεν μπορεί να ελέγξει τον κωδικό σας. Επίσης, όταν ασχολείστε με συλλογές τύπων πρωτόγονης αξίας, η χρήση μιας μη γενικής συλλογής θα έχει ως αποτέλεσμα την επανάληψη πυγμαχία / απεμπλοκή αυτών των τύπων, τα οποία μπορούν να οδηγήσουν σε σημαντική αρνητική επίπτωση στην απόδοση σε σύγκριση με μια γενική συλλογή του κατάλληλου τύπου.
Ένα άλλο κοινό πρόβλημα C # είναι να γράψετε το δικό σας αντικείμενο συλλογής. Αυτό δεν σημαίνει ότι δεν είναι ποτέ κατάλληλο, αλλά με μια τόσο ολοκληρωμένη επιλογή όπως αυτή που προσφέρει το .NET, πιθανότατα μπορείτε να εξοικονομήσετε πολύ χρόνο χρησιμοποιώντας ή επεκτείνοντας αυτόν που υπάρχει ήδη, αντί να ανακαλύψετε ξανά τον τροχό. Συγκεκριμένα, η βιβλιοθήκη γενικών συλλογών C5 για C # και CLI προσφέρει ένα ευρύ φάσμα πρόσθετων συλλογών 'εκτός του πλαισίου', όπως επίμονες δομές δεδομένων δέντρων, ουρές προτεραιότητας βάσει σωρού, λίστες συστοιχιών ευρετηρίου κατακερματισμού, συνδεδεμένες λίστες και πολλά άλλα.
Το περιβάλλον CLR χρησιμοποιεί έναν συλλέκτη σκουπιδιών, επομένως δεν χρειάζεται να απελευθερώσετε ρητά τη μνήμη που δημιουργήθηκε για οποιοδήποτε αντικείμενο. Στην πραγματικότητα, δεν μπορείτε. Δεν υπάρχει ισοδύναμο με το C ++ StringDictionary
χειριστή ή το delete
λειτουργία σε C. Αλλά αυτό δεν σημαίνει ότι μπορείτε να ξεχάσετε όλα τα αντικείμενα αφού τελειώσετε με τη χρήση τους. Πολλοί τύποι αντικειμένων ενσωματώνουν κάποιον άλλο τύπο πόρου συστήματος (π.χ. ένα αρχείο δίσκου, σύνδεση βάσης δεδομένων, υποδοχή δικτύου κ.λπ.). Αν αφήσετε αυτούς τους πόρους ανοιχτούς μπορεί να εξαντληθεί γρήγορα ο συνολικός αριθμός πόρων του συστήματος, υποβαθμίζοντας την απόδοση και τελικά οδηγώντας σε σφάλματα του προγράμματος.
Ενώ μια μέθοδος καταστροφής μπορεί να οριστεί σε οποιαδήποτε κλάση C #, το πρόβλημα με τους καταστροφείς (ονομάζεται επίσης οριστικοποιητής στο C #) είναι ότι δεν μπορείτε να γνωρίζετε με βεβαιότητα πότε θα κληθούν. Καλούνται από τον συλλέκτη απορριμμάτων (σε ξεχωριστό νήμα, το οποίο μπορεί να προκαλέσει επιπλοκές) σε αόριστο χρόνο στο μέλλον. Προσπαθώντας να ξεπεράσετε αυτούς τους περιορισμούς αναγκάζοντας τη συλλογή απορριμμάτων με free()
δεν είναι α C # βέλτιστη πρακτική , καθώς αυτό θα μπλοκάρει το νήμα για άγνωστο χρονικό διάστημα ενώ συλλέγει όλα τα αντικείμενα που είναι κατάλληλα για συλλογή.
Αυτό δεν σημαίνει ότι δεν υπάρχουν καλές χρήσεις για τους οριστικοποιητές, αλλά η απελευθέρωση πόρων με ντετερμινιστικό τρόπο δεν είναι μία από αυτές. Αντίθετα, όταν χρησιμοποιείτε σύνδεση σε αρχείο, δίκτυο ή βάση δεδομένων, θέλετε να απελευθερώσετε ρητά τον υποκείμενο πόρο μόλις τελειώσετε με αυτόν.
Οι διαρροές πόρων είναι σχεδόν ανησυχητικές οποιοδήποτε περιβάλλον . Ωστόσο, το C # παρέχει έναν μηχανισμό που είναι στιβαρός και απλός στη χρήση, ο οποίος, εάν χρησιμοποιηθεί, μπορεί να κάνει τις διαρροές πολύ πιο σπάνια εμφάνιση. Το πλαίσιο .NET ορίζει το GC.Collect()
διεπαφή, η οποία αποτελείται αποκλειστικά από το IDisposable
μέθοδος. Κάθε αντικείμενο που υλοποιεί Dispose()
αναμένει να καλέσει αυτή τη μέθοδο κάθε φορά που ο καταναλωτής του αντικειμένου τελειώσει να το χειριστεί. Αυτό οδηγεί σε ρητή, ντετερμινιστική απελευθέρωση πόρων.
Εάν δημιουργείτε και απορρίπτετε ένα αντικείμενο στο πλαίσιο ενός μόνο μπλοκ κώδικα, είναι βασικά αδικαιολόγητο να ξεχάσετε να καλέσετε IDisposable
, επειδή το C # παρέχει ένα Dispose()
δήλωση που θα διασφαλίσει using
καλείται, ανεξάρτητα από το πώς εξέρχεται το μπλοκ κώδικα (είτε πρόκειται για εξαίρεση, δήλωση επιστροφής είτε απλά για το κλείσιμο του μπλοκ). Και ναι, αυτό είναι το ίδιο Dispose()
δήλωση που αναφέρθηκε προηγουμένως που χρησιμοποιείται για να συμπεριλάβει C # namespaces στο επάνω μέρος του αρχείου σας. Έχει έναν δεύτερο, εντελώς άσχετο σκοπό, τον οποίο πολλοί προγραμματιστές C # δεν γνωρίζουν. δηλαδή, για να διασφαλιστεί ότι using
καλείται σε ένα αντικείμενο όταν κλείσει το μπλοκ κώδικα:
Dispose()
Δημιουργώντας ένα μπλοκ using (FileStream myFile = File.OpenRead('foo.txt')) { myFile.Read(buffer, 0, 100); }
στο παραπάνω παράδειγμα, γνωρίζετε σίγουρα ότι using
θα κληθεί μόλις τελειώσετε με το αρχείο, ανεξάρτητα από το αν myFile.Dispose()
ρίχνει μια εξαίρεση.
Το C # συνεχίζει την επιβολή της ασφάλειας τύπου σε χρόνο εκτέλεσης. Αυτό σας επιτρέπει να εντοπίσετε πολλούς τύπους σφαλμάτων στο C # πολύ πιο γρήγορα από ό, τι σε γλώσσες όπως το C ++, όπου οι λανθασμένες μετατροπές τύπου μπορούν να έχουν ως αποτέλεσμα την εκχώρηση αυθαίρετων τιμών στα πεδία ενός αντικειμένου. Ωστόσο, για άλλη μια φορά, οι προγραμματιστές μπορούν να σπαταλήσουν αυτό το εξαιρετικό χαρακτηριστικό, οδηγώντας σε προβλήματα C #. Εμπίπτουν σε αυτήν την παγίδα επειδή το C # παρέχει δύο διαφορετικούς τρόπους για να κάνει πράγματα, έναν που μπορεί να ρίξει μια εξαίρεση και έναν που δεν θα. Μερικοί θα αποφύγουν τη διαδρομή εξαίρεσης, υποθέτοντας ότι δεν χρειάζεται να γράψετε ένα μπλοκ try / catch τους σώζει κάποια κωδικοποίηση.
Για παράδειγμα, εδώ είναι δύο διαφορετικοί τρόποι για να εκτελέσετε ένα ρητό τύπο cast στο C #:
Read()
Το πιο προφανές σφάλμα που θα μπορούσε να προκύψει με τη χρήση της μεθόδου 2 θα ήταν η αποτυχία ελέγχου της τιμής επιστροφής. Αυτό πιθανότατα θα είχε ως αποτέλεσμα μια ενδεχόμενη NullReferenceException, η οποία θα μπορούσε ενδεχομένως να εμφανιστεί σε πολύ αργότερα χρόνο, καθιστώντας πολύ πιο δύσκολο τον εντοπισμό της πηγής του προβλήματος. Αντιθέτως, η Μέθοδος 1 θα είχε ρίξει αμέσως ένα // METHOD 1: // Throws an exception if account can't be cast to SavingsAccount SavingsAccount savingsAccount = (SavingsAccount)account; // METHOD 2: // Does NOT throw an exception if account can't be cast to // SavingsAccount; will just set savingsAccount to null instead SavingsAccount savingsAccount = account as SavingsAccount;
καθιστώντας την πηγή του προβλήματος πολύ πιο προφανής.
Επιπλέον, ακόμα κι αν θυμάστε να ελέγξετε την τιμή επιστροφής στη Μέθοδο 2, τι πρόκειται να κάνετε αν το θεωρείτε μηδενικό; Είναι η μέθοδος που γράφετε ένα κατάλληλο μέρος για να αναφέρετε ένα σφάλμα; Υπάρχει κάτι άλλο που μπορείτε να δοκιμάσετε εάν το cast αποτύχει; Εάν όχι, τότε το να κάνεις μια εξαίρεση είναι το σωστό, ώστε να το αφήσεις να συμβεί όσο το δυνατόν πιο κοντά στην πηγή του προβλήματος.
Ακολουθούν μερικά παραδείγματα άλλων κοινών ζευγών μεθόδων όπου το ένα ρίχνει μια εξαίρεση και το άλλο δεν:
InvalidCastException
Ορισμένοι προγραμματιστές C # είναι τόσο «εξαιρούμενοι αρνητικοί» που θεωρούν αυτόματα ότι η μέθοδος που δεν ρίχνει μια εξαίρεση είναι ανώτερη. Ενώ υπάρχουν ορισμένες επιλεγμένες περιπτώσεις όπου αυτό μπορεί να ισχύει, δεν είναι καθόλου σωστό ως γενίκευση.
Ως συγκεκριμένο παράδειγμα, σε περίπτωση που έχετε μια εναλλακτική νόμιμη (π.χ. προεπιλεγμένη) ενέργεια που πρέπει να αναλάβετε εάν θα είχε δημιουργηθεί μια εξαίρεση, τότε η προσέγγιση μη εξαίρεσης θα μπορούσε να είναι μια νόμιμη επιλογή. Σε μια τέτοια περίπτωση, ίσως είναι καλύτερο να γράψετε κάτι σαν αυτό:
int.Parse(); // throws exception if argument can’t be parsed int.TryParse(); // returns a bool to denote whether parse succeeded IEnumerable.First(); // throws exception if sequence is empty IEnumerable.FirstOrDefault(); // returns null/default value if sequence is empty
αντί:
if (int.TryParse(myString, out myInt)) { // use myInt } else { // use default value }
Ωστόσο, δεν είναι σωστό να υποθέσουμε ότι try { myInt = int.Parse(myString); // use myInt } catch (FormatException) { // use default value }
είναι συνεπώς αναγκαστικά η «καλύτερη» μέθοδος. Μερικές φορές αυτό συμβαίνει, μερικές φορές δεν είναι. Γι 'αυτό υπάρχουν δύο τρόποι να το κάνουμε. Χρησιμοποιήστε το σωστό για το περιβάλλον στο οποίο βρίσκεστε, θυμηθείτε ότι οι εξαιρέσεις μπορούν σίγουρα να είναι ο φίλος σας ως προγραμματιστής.
Αν και αυτό το πρόβλημα δεν είναι σίγουρα C # συγκεκριμένο, είναι ιδιαίτερα κακό στο C # προγραμματισμό, δεδομένου ότι εγκαταλείπει τα οφέλη από τον αυστηρό έλεγχο τύπου που προσφέρει ο C # compiler.
Οι προειδοποιήσεις δημιουργούνται για έναν λόγο. Ενώ όλα τα σφάλματα μεταγλωττιστή C # δηλώνουν ελάττωμα στον κωδικό σας, πολλές προειδοποιήσεις ισχύουν επίσης. Αυτό που διαφοροποιεί τα δύο είναι ότι, σε περίπτωση προειδοποίησης, ο μεταγλωττιστής δεν έχει κανένα πρόβλημα να εκπέμπει τις οδηγίες που αντιπροσωπεύει ο κώδικάς σας. Ακόμα κι έτσι, βρίσκει τον κωδικό σας λίγο ψαρό, και υπάρχει λογική πιθανότητα ο κώδικάς σας να μην αντικατοπτρίζει με ακρίβεια την πρόθεσή σας.
Ένα κοινό απλό παράδειγμα για χάρη αυτού του εκπαιδευτικού προγράμματος C # είναι όταν τροποποιείτε τον αλγόριθμό σας για να εξαλείψετε τη χρήση μιας μεταβλητής που χρησιμοποιούσατε, αλλά ξεχάσετε να καταργήσετε τη δήλωση μεταβλητής. Το πρόγραμμα θα τρέξει τέλεια, αλλά ο μεταγλωττιστής θα επισημάνει την άχρηστη δήλωση μεταβλητής. Το γεγονός ότι το πρόγραμμα λειτουργεί τέλεια προκαλεί στους προγραμματιστές να παραμελήσουν να διορθώσουν την αιτία της προειδοποίησης. Επιπλέον, οι κωδικοποιητές εκμεταλλεύονται τη δυνατότητα Visual Studio που τους διευκολύνει να αποκρύψουν τις προειδοποιήσεις στο παράθυρο 'Λίστα σφαλμάτων', ώστε να μπορούν να εστιάσουν μόνο στα σφάλματα. Δεν χρειάζεται πολύς χρόνος έως ότου υπάρξουν δεκάδες προειδοποιήσεις, όλες αυτές αγνοούνται ευτυχώς (ή ακόμα χειρότερα, κρυμμένες).
Αλλά αν αγνοήσετε αυτόν τον τύπο προειδοποίησης, αργά ή γρήγορα, κάτι τέτοιο μπορεί κάλλιστα να βρεθεί στον κώδικά σας:
TryParse
Και με την ταχύτητα το Intellisense μας επιτρέπει να γράφουμε κώδικα, αυτό το σφάλμα δεν είναι τόσο απίθανο όσο φαίνεται.
Έχετε τώρα ένα σοβαρό σφάλμα στο πρόγραμμά σας (αν και ο μεταγλωττιστής το έχει επισημάνει μόνο ως προειδοποίηση, για τους λόγους που έχουν ήδη εξηγηθεί) και ανάλογα με το πόσο περίπλοκο είναι το πρόγραμμά σας, θα μπορούσατε να χάσετε πολύ χρόνο παρακολουθώντας αυτό. Αν είχατε δώσει προσοχή σε αυτήν την προειδοποίηση, θα αποφύγατε αυτό το πρόβλημα με μια απλή επιδιόρθωση πέντε δευτερολέπτων.
Θυμηθείτε, ο μεταγλωττιστής C Sharp σάς παρέχει πολλές χρήσιμες πληροφορίες σχετικά με την ευρωστία του κωδικού σας… αν ακούτε. Μην αγνοείτε τις προειδοποιήσεις. Συνήθως χρειάζονται μόνο λίγα δευτερόλεπτα για την επίλυση και η διόρθωση νέων όταν συμβεί μπορεί να σας εξοικονομήσει ώρες. Εκπαιδεύστε τον εαυτό σας για να περιμένετε το παράθυρο 'Λίστα σφαλμάτων' του Visual Studio να εμφανίζει '0 Σφάλματα, 0 Προειδοποιήσεις', έτσι ώστε τυχόν προειδοποιήσεις να σας κάνουν να νιώσετε αρκετά άβολα για να τις αντιμετωπίσετε αμέσως.
Φυσικά, υπάρχουν εξαιρέσεις σε κάθε κανόνα. Κατά συνέπεια, μπορεί να υπάρχουν στιγμές που ο κώδικάς σας θα φαίνεται λίγο θαυμάσιος για τον μεταγλωττιστή, παρόλο που είναι ακριβώς όπως σκοπεύατε να είναι. Σε αυτές τις πολύ σπάνιες περιπτώσεις, χρησιμοποιήστε class Account { int myId; int Id; // compiler warned you about this, but you didn’t listen! // Constructor Account(int id) { this.myId = Id; // OOPS! } }
μόνο γύρω από τον κωδικό που ενεργοποιεί την προειδοποίηση και μόνο για το αναγνωριστικό προειδοποίησης που ενεργοποιεί. Αυτό θα καταστείλει αυτήν την προειδοποίηση και μόνο αυτήν την προειδοποίηση, ώστε να μπορείτε να παραμείνετε σε εγρήγορση για νέες.
Το C # είναι μια ισχυρή και ευέλικτη γλώσσα με πολλούς μηχανισμούς και παραδείγματα που μπορούν να βελτιώσουν σημαντικά την παραγωγικότητα. Όπως συμβαίνει με οποιοδήποτε εργαλείο λογισμικού ή γλώσσα, ωστόσο, η περιορισμένη κατανόηση ή εκτίμηση των δυνατοτήτων του μπορεί μερικές φορές να είναι περισσότερο εμπόδιο παρά όφελος, αφήνοντας ένα στην παροιμιώδη κατάσταση «να γνωρίζει αρκετά για να είναι επικίνδυνο».
Χρησιμοποιώντας ένα σεμινάριο C Sharp όπως αυτό εξοικειωθείτε με τις βασικές αποχρώσεις του C #, όπως (αλλά δεν περιορίζονται σε κανένα) τα προβλήματα που τέθηκαν σε αυτό το άρθρο, θα βοηθήσουν στη βελτιστοποίηση C #, αποφεύγοντας παράλληλα μερικές από τις πιο κοινές παγίδες της γλώσσας.
Σχετίζεται με: 6 βασικές ερωτήσεις συνέντευξης C #Το C # είναι μία από τις πολλές γλώσσες προγραμματισμού που στοχεύουν το Microsoft CLR, το οποίο του δίνει αυτόματα τα οφέλη της διαγλωσσικής ενοποίησης και του χειρισμού εξαιρέσεων, της ενισχυμένης ασφάλειας, ενός απλουστευμένου μοντέλου για αλληλεπίδραση στοιχείων και των εντοπισμού σφαλμάτων και των προφίλ.
Τα C ++ και C # είναι δύο εντελώς διαφορετικές γλώσσες, παρά τις ομοιότητες στο όνομα και τη σύνταξη. Το C # σχεδιάστηκε για να είναι κάπως υψηλότερο επίπεδο από το C ++, και οι δύο γλώσσες ακολουθούν επίσης διαφορετικές προσεγγίσεις για λεπτομέρειες, όπως ποιος καθορίζει εάν μια παράμετρος περνάει με αναφορά ή από την τιμή.
Το C # χρησιμοποιείται για πολλούς λόγους, αλλά τα οφέλη του συνόλου εργαλείων Microsoft CLR και μιας μεγάλης κοινότητας προγραμματιστών είναι δύο βασικές ελλείψεις στη γλώσσα.