Dynamic Member Access

Using the Reflection API, it is also possible to access a type's member dynamically. If you have a handle to a Methodlnfo object, you can call the method via MethodInfo::Invoke. A Fieldlnfo object can be used to read or write the field's value via GetValue and SetValue. For properties and events, similar members exist.

Such runtime-bound dynamic access to type members is obviously much slower than a direct method call or direct field access. (For static and non-static methods without arguments and return values, a call is about 300 times slower. Depending on the signature, the overhead can be multiples of that.) However, dynamic access can be helpful for implementing helper algorithms that can operate on objects even if the objects' type is not known at build time. As an example, a data binding implementation can automatically bind control fields of a dialog to data fields of a data object based on field names.

Before the dialog is displayed, a helper method of the data binding implementation could be called. This method can easily get Fieldlnfo objects for all fields of the data source class and the dialog class. Fieldlnfo objects from the data source class and Fieldlnfo objects from the dialog class with matching field names could be used to automatically read the value of the fields in the data source and to set the control's text in the dialog with these fields. When the OK button of the dialog is clicked, a helper function could be called that dynamically updates the fields from the data source with values dynamically read from the controls' fields.

The following code uses the Managed Reflection API to implement a serialization of objects into streams and a deserialization of objects from streams:

// customSerialzation.cpp

// CL /clr:safe customSerialization.cpp using namespace System;

using namespace System::IO;

using namespace System::Reflection;

ref struct Person {

StringA Name; int Age;

void Serialize(StreamA strm, ObjectA o) {

throw gcnew ArgumentNullException("strm");

throw gcnew ArgumentNullException("o");

BinaryWriterA bw = gcnew BinaryWriter(strm);

bw->Write(t->AssemblyOualifiedName);

array<FieldInfoA>A fields = t->GetFields();

for each (FieldlnfoA fi in fields) {

if (fi->FieldType == int::typeid) bw->Write((int)fi->GetValue(o)); else if (fi->FieldType == String::typeid)

bw->Write((StringA)fi->GetValue(o)); else

// for simplicity, other types are not supported here throw gcnew NotSupportedException();

finally {

ObjectA Deserialize(StreamA strm) {

throw gcnew ArgumentNullException("strm"); ObjectA o;

BinaryReaderA br = gcnew BinaryReader(strm);

StringA type = br->ReadString(); TypeA t = Type::GetType(type); o = Activator::CreateInstance(t);

array<FieldInfoA>A fields = t->GetFields();

for each (FieldInfoA fi in fields) {

fi->SetValue(o, br->ReadInt32()); else if (fi->FieldType == String::typeid)

// for simplicity, other types are not supported here throw gcnew NotSupportedException();

finally {

return o;

array<Byte>A bytes = gcnew array<Byte>(l024);

PersonA p = gcnew Person(); p->Name = "Paul"; p->Age = 35;

Serialize(gcnew MemoryStream(bytes), p);

PersonA p2 = (PersonA)Deserialize(gcnew MemoryStream(bytes));

Console::WriteLine(p2->Name);

Console::WriteLine(p2->Age);

In this code, serialization and deserialization of objects is done with the methods Serialize and Deserialize. Serialize expects a handle to the Stream instance into which the object should be serialized, as well as the object that is to be serialized. To serialize fields of primitive types, it uses the helper class System::IO::BinaryWriter. Similar to StreamWriter, this class has helper methods for serializing data into streams. In contrast to StreamWriter, it does not write its data line by line as text into the stream. Instead of WriteLine, it has various overloads of the Write methods to write primitive types in a binary format. The overloads used in this code are Write(StringA) (for writing a string in a length-prefixed format) and Write(int).

First, the object's type is determined, and the assembly-qualified type name is written into the stream. This allows the deserialization function to instantiate the object dynamically. After that, the Fieldlnfo objects for the public fields are determined. After that, a for each loop iterates through all fields. Every iteration determines the object's value of the current field. Depending on the type, different overloads of BinaryWriter::Write are used. For simplicity, only fields of type int and StringA can be serialized.

The method Deserialize expects a stream with the serialized data, instantiates an object, initializes its fields, and returns a handle to that object. To achieve this, Deserialize creates a BinaryReader that operates on the stream. Using this reader, the data can be read in the same order as it was written by the Serialize method. The first piece of information that is read via the reader is the assembly-qualified type name. Using this type name, a handle to the type object for the serialized data can be requested. Once the type object is available, a new instance is created via Activator::CreateInstance. To initialize each field, FieldInfo::SetValue is called for each Fieldlnfo provided for the type.

Was this article helpful?

0 0

Post a comment