Reflection, part Three

Αυτό το post είναι μέρος μιας τετράδας από blog posts σχετικά με το reflection. Δείτε τα υπόλοιπα εδώ

Στα προηγούμενα δύο blog posts είδαμε πως να βρίσκουμε το type information μιας assembly και να το παρουσιάζουμε. Πολλές φορές, χρειαζόμαστε να τρέξουμε κώδικα που περιέχεται σε αυτές τις assemblies, και θέλουμε να μην έχουμε απευθείας πρόσβαση στον κώδικα αυτό κατά το compile time (όπως γίνεται όταν κάνουμε κανονικά reference μια assembly). Η δυνατότητα φορτώματος μιας assembly δυναμικά και εκτέλεσης κώδικα από αυτή χωρίς να έχουμε πρόσβαση κατά το compile time είναι ο στόχος του dynamic code.

 

Χρήση του dynamic code

Το σύστημα του reflection μας επιτρέπει να δημιουργήσουμε αντικείμενα δυναμικά, ακόμα και από assemblies που δεν έχουνε γίνει reference στο project μας. Ενώ αυτό είναι πιο δύσκολο από την συγγραφή “type safe compiler checked” κώδικα (όπου έχουμε απευθείας πρόσβαση και “επίβλεψη” για τα πεδία των αντικειμένων που χρησιμοποιούμε) κάποιες φορές ο δυναμικός κώδικας είναι απαραίτητος.

Για το παράδειγμά μας, κατασκευάζουμε ένα νέο project (ονόματι DynamicLibrary) το οποίο και προσθέτουμε στο solution μας. Για ευκολία κατά την εκτέλεση του προγράμματός μας, επιλέγουμε κατά το build του, να τοποθετείται στον ίδιο φάκελο που είναι και η εφαρμογή μας.

image

Στο συγκεκριμένο project, κατασκευάζουμε μία κλάση Product. Μπορείτε να δείτε τον κώδικά της εδώ

  image

Για να πάρουμε δυναμικά ένα instance της κλάσης Product, υπάρχουν δύο τρόποι. Ο πρώτος είναι αυτός

image

Χρησιμοποιούμε την κλάση System.Activator για να πάρουμε δύο instances, το πρώτο καλώντας τον contructor που δεν έχει ορίσματα, και το δεύτερο καλώντας τον constructor που παίρνει όρισμα ένα string. Η κλάση αυτή χρησιμοποιείται και στις Remoting εφαρμογές για να πάρουμε instances από απομακρυσμένα αντικείμενα. Ο δεύτερος τρόπος είναι αυτός

 image

Με την πρώτη μορφή καλούμε τον empty constructor της κλάσης Product (γι’αυτό το λόγο παρέχουμε ένα Type.EmptyTypes κατά την κλήση της GetConstructor). Με τη δεύτερη μορφή, καλούμε τον άλλο constructor ο οποίος παίρνει σαν όρισμα ένα string.

Invoking Members

Εφόσον έχουμε ένα instance ενός object είναι πολύ απλό να καλέσουμε ένα Member. Ας δούμε πώς μπορούμε να κάνουμε set και get το Property Price.

image

Χρησιμοποιούμε τις συναρτήσεις SetValue και GetValue για να πάρουμε τον accessor και τον mutator, αντίστοιχα, του property. Το πρώτο argument στις GetValue και στην SetValue είναι το object στο οποίο θέλουμε να καλέσουμε το αντίστοιχο member (στην περίπτωσή μας, καλούμε τις συναρτήσεις στο αντικείμενο o1). Το τελευταίο argument σε αυτές τις μεθόδους χρησιμοποιείται αν θέλουμε να πάρουμε indexed properties. Εμείς δεν έχουμε κάτι τέτοιο στην περίπτωσή μας, οπότε απλά το κάνουμε set σε null.

Ας δούμε τώρα πως μπορούμε να κάνουμε δυναμικά Register σε Events. Το event μας είναι το PriceChanged, το οποίο είναι τύπου EventHandler. Κατασκευάζουμε λοιπόν μία συνάρτηση που έχει το signature (return value and parameter types) που απαιτεί ο συγκεκριμένος Delegate.

image

Μέσα σε αυτή ενημερώνουμε ότι άλλαξε η τιμή, και παίρνουμε μέσω reflection την καινούρια. Πώς όμως κάνουμε register στο Event; Είναι πολύ απλό. Δείτε εδώ

image

Σαν πρώτο όρισμα στην AddEventHandler βάζουμε το αντικείμενο στο event του οποίου θέλουμε να κάνουμε register.

Εκτελώντας το συγκεκριμένο τμήμα κώδικα θα πάρουμε σαν output

image

το οποίο σημαίνει ότι ο event handler πράγματι έκανε handle το event.

Ας δούμε και πώς μπορούμε να κάνουμε δυναμική κλήση των δύο συναρτήσεων της Product. Ο κώδικας είναι εδώ

image

Για να πάρουμε την WritePrice, χρησιμοποιούμε και το BindingFlags enumeration, καθώς η συγκεκριμένη method είναι declared σαν private, οπότε, μέσω του BindingFlags, δηλώνουμε στην GetMethod ότι η συνάρτηση που ψάχνουμε είναι Instance method και όχι Public. Για να πάρουμε την WriteName, απλά δίνουμε σαν παράμετρο το όνομά της (αφού έχει δηλωθεί ως Public).

Παρόμοια είναι και η κλήση όλων των αντικειμένων που έχουν το Info σαν κατάληξη (FieldInfo, MemberInfo κ.λ.π.), οπότε δεν θα αναφερθούμε παραπάνω.

Invoking Static Members

Για να πάρουμε static μεθόδους από ένα object, δεν κάνουμε κάτι διαφορετικό. Απλά περνάμε null στο object του οποίου η μέθοδος θα εκτελεστεί. Δείτε εδώ πως μπορούμε να γράψουμε στην κονσόλα ένα “Hello World!” με reflection στην static μέθοδο της κλάσης System.Console

image

Στην GetMethod, με την χρήση του new Type[] {typeof(string)} επιλέγουμε την overloaded μέθοδο WriteLine η οποία παίρνει σαν όρισμα ένα string. Κατόπιν, σε αντίθεση με τα προηγούμενα παραδείγματα, όπου στο πρώτο όρισμα της Invoke βάζαμε το αντικείμενο στο οποίο θέλαμε να κληθεί η reflected μέθοδος, εδώ βάζουμε null, αφού η συγκεκριμένη μέθοδος στην οποία έχουμε κάνει reflect είναι static.

Η κλάση Binder

Όταν χρησιμοποιούμε κώδικα από assemblies/classes στις οποίες κάνουμε reflect κατά το runtime, μπορούμε να κατασκευάσουμε μία δικιά μας υλοποίηση της κλάσης Binder, η οποία είναι υπεύθυνη για να αποφασίζει πως να κάνει type conversions (μετατροπές μεταξύ τύπων) και που να βρει τον δυναμικό κώδικα. Ένα instance μιας Binder κλασης είναι ένα προαιρετικό argument στις μεθόδους της κλάσης Type που επιστρέφουν μέλη ενός Type (GetMember, GetEvent κ.λ.π.). Αν δεν προσδιοριστεί μία custom υλοποίηση της κλάσης Binder, το Framework χρησιμοποιεί τον default binder (ο οποίος, στις περισσότερες των περιπτώσεων θα κάνει τη δουλειά που θέλουμε. Μία custom κλάση Binder είναι χρήσιμη αν αναμένουμε να κάνουμε μετατροπές τύπων ή κάποιο άλλο “ταίριασμα” των παραμέτρων προκειμένου να βρούμε τη σωστή μέθοδο/property/event κ.λ.π. σε μία reflected κλάση, όταν δεν γνωρίζουμε την ακριβή υλοποίηση μιας κλάσης. Για περισσότερες πληροφορίες σχετικά με την κλάση Binder, δείτε το παράδειγμα του MSDN εδώ.

Στο επόμενο blog post θα δούμε πως μπορούμε να δημιουργήσουμε κώδικα κατά το runtime.

Επισυνάπτεται ο μέχρι στιγμής κώδικας αυτής της σειράς των posts.

Leave a Reply

Fill in your details below or click an icon to log in:

WordPress.com Logo

You are commenting using your WordPress.com account. Log Out /  Change )

Facebook photo

You are commenting using your Facebook account. Log Out /  Change )

Connecting to %s