Αυτό το post είναι μέρος μιας τετράδας από blog posts σχετικά με το reflection. Δείτε τα υπόλοιπα εδώ
Σε αυτό το blog post θα δούμε πώς να δημιουργήσουμε τα δικά μας Types ή assemblies κατά το runtime (είτε για να τις αποθηκεύσουμε στο σκληρό δίσκο είτε για να τις χρησιμοποιήσουμε ενώ είναι στη μνήμη).
Κατασκευή δικού μας κώδικα
Το σύστημα του Reflection περιλαμβάνει ένα υπο-namespace το οποίο ονομάζεται Emit (System.Reflection.Emit) που περιλαμβάνει κάποιες κλάσεις που χρησιμοποιούνται για κατασκευή Assemblies, Types, Methods κ.λ.π. Για να κατασκευάσουμε κώδικα κατά το runtime, πρέπει να τον κάνουμε encapsulate όπως συνήθως (κατασκευή assembly, κατασκευή module μέσα στην assembly, και μετά κατασκευή Types μέσα στο Module). Κάθε μία από αυτές τις κλάσεις έχει παρόμοιο όνομα με την κλάση που κατασκευάζει, μαζί με την προσθήκη της λέξης Builder (για παράδειγμα για κατασκευή μιας Assembly χρησιμοποιούμε την κλάση AssemblyBuilder). Οι Builder κλάσεις που υποστηρίζονται είναι οι εξής
Όνομα κλάσης |
Κατασκευάζει |
AssemblyBuilder |
assemblies |
ConstructorBuilder |
constructors |
EnumBuilder |
enumerations |
EventBuilder |
events |
FieldBuilder | fields |
LocalBuilder | τοπικές μεταβλητές για συναρτήσεις και constructors |
MethodBuilder | μεθόδους / συναρτήσεις |
ModuleBuilder | modules |
ParameterBuilder | παραμέτρους |
PropertyBuilder | properties |
TypeBuilder | types |
Δημιουργία Assembly και Module
Πριν την δημιουργία ενός Type, πρέπει να δημιουργήσουμε μία Assembly και ένα Module. Για να το κάνουμε αυτό, πρέπει να ζητήσουμε από ένα AppDomain να δημιουργήσει μια δυναμική assembly. Η κλάση AppDomain έχει μία μέθοδο ονόματι DefineDynamicAssembly που παίρνει σαν παραμέτρους ένα AssemblyName αντικείμενο και ένα AssemblyBuilderAccess enumeration. Το πρώτο περιλαμβάνει πληροφορίες για την assembly και το δεύτερο είναι ένα enumeration με τιμές ReflectionOnly, Run, Save, RunAndSave, όπου καθορίζει το τι θέλουμε να κάνουμε με την assembly.
Εφόσον έχουμε τώρα ένα αντικείμενο AssemblyBuilder, μπορούμε να κατασκευάσουμε ένα ModuleBuilder.
Δημιουργία Type
Για να δημιουργήσουμε ένα νέο Type, θα καλέσουμε μια συνάρτηση του ModuleBuilder ονόματι DefineType. Αυτή η συνάρτηση παίρνει σαν παραμέτρους ένα όνομα για το Type, και μία τιμή από το enumeration TypeAttributes. Το TypeAttributes περιλαμβάνει επιλογές για το καινούριο Type.
Υπάρχουν διάφορες overloaded συναρτήσεις της DefineType, που επιτρέπουν (μεταξύ άλλων) το inheritance μιας άλλης κλάσης ή το implementation κάποιου interface.
Δημιουργία Members
Η κλάση TypeBuilder μας επιτρέπει να δημιουργήσουμε μέλη για το Type μας. Οι συναρτήσεις που υποστηρίζει είναι
Όνομα συνάρτησης |
Δημιουργεί |
DefineContructor | constructor για το type |
DefineDefaultConstructor | constructor χωρίς παραμέτρους |
DefineEvent | events |
DefineField | fields |
DefineGenericParameters | generic παραμέτρους για generic types |
DefineMethod | συναρτήσεις |
DefineMethodOverride | συναρτήσεις οι οποίες κάνουν override συναρτήσεις μιας γονικής κλάσης |
DefineNestedType | types τα οποία είναι εμφωλευμένα (nested) σε ένα type |
DefinePInvokeMethod | συναρτήσεις οι οποίες καλούν εξωτερικό κώδικα μέσω της λειτουργίας Platform Invoke |
DefineProperty | properties |
Το πρώτο πράγμα που χρειάζεται να κάνουμε για το νέο μας Type είναι να δημιουργήσουμε έναν constructor. Θα κατασκευάσουμε έναν default (χωρίς παραμέτρους) constructor, οπότε θα γράψουμε το εξής τμήμα κώδικα
Η συνάρτηση DefineDefaultConstructor παίρνει σαν όρισμα μία τιμή από το MethodAttributes enumeration, η οποία και καθορίζει την “ορατότητα” του νέο μας constructor. Σε αυτή την περίπτωση, επιλέγουμε να είναι public.
Κατασκευάζουμε και έναν άλλον constructor
Εν συνεχεία, θα χρησιμοποιήσουμε την κλάση ILGenerator προκειμένου να γράψουμε μέσα στον constructor μας κάποιο κώδικα. Αυτή η κλάση χρησιμοποιείται για να δημιουργήσουμε τοπικές μεταβλητές και IL (Intermediate Language) κώδικα. Στην προκειμένη περίπτωση, ο constructor μας απλά θα κάνει ένα return, οπότε θα γράψουμε το παρακάτω
Για περισσότερες πληροφορίες σχετικά με τα OpCodes αλλά και με την Intermediate Language του .NET Framework, δείτε εδώ
Βέβαια, εκτός από constructor μπορούμε να δημιουργήσουμε και άλλες μεθόδους. Δείτε
Για να δημιουργήσουμε μία static μέθοδο, βάζουμε το MethodAttributes.Static στα attributes, με αυτόν τον τρόπο
Για να δημιουργήσουμε ένα private field, κάνουμε το εξής
Για να δημιουργήσουμε ένα property το οποίο θα μας κάνει expose το παραπάνω field, γράφουμε τον εξής κώδικα
Θα πρέπει βέβαια να φτιάξουμε και τα get και set operations για το property μας. Δείτε:
Τέλος, για να σώσουμε στο δίσκο την καινούρια μας assembly, κάνουμε το εξής
Στο παρόν blog post μπορείτε να βρείτε συννημένο τον τελικό κώδικα αυτής της τετράδας των blog posts.