Working with XML serialization, I need to create object with several public properties taken from complex “parent” object, and its class is created on the fly via reflection. Full code is accessible on GitHub Blog repository.
The following MSDN’s topic contains an example of the creating dynamic class with public property. Let me list sample code that creates object with two properties: Name
and Count
.
public class ObjectFactory { public static string[] PropertyNames = new[] { "Name", "Count" }; public static Type[] PropertyTypes = new[] { typeof(string), typeof(int) }; private readonly ModuleBuilder _moduleBuilder = null; public ObjectFactory() { var assemblyName = new AssemblyName() { Name = "LiteObjectTypes" }; _moduleBuilder = Thread.GetDomain() .DefineDynamicAssembly(assemblyName, AssemblyBuilderAccess.Run) .DefineDynamicModule(assemblyName.Name); } public object GetObject() { const string className = "LiteObject"; var typeBuilder = _moduleBuilder.DefineType(className, TypeAttributes.Public | TypeAttributes.Class | TypeAttributes.Serializable); for (var pos = 0; pos < PropertyNames.Length; pos++) { // create private field and init it by default value from DefaultValue attribute var fieldName = $"_{PropertyNames[pos].ToLower()[0]}{PropertyNames[pos].Substring(1)}"; var fieldBuilder = typeBuilder.DefineField(fieldName, PropertyTypes[pos], FieldAttributes.Private); var propertyBuilder = typeBuilder.DefineProperty(PropertyNames[pos], PropertyAttributes.HasDefault, PropertyTypes[pos], null); // the property set and property get methods require a special set of attributes. const MethodAttributes accessorAttributes = MethodAttributes.Public | MethodAttributes.SpecialName | MethodAttributes.HideBySig; // define the "get" accessor method for CustomerName. var getterMethod = typeBuilder.DefineMethod($"get_{PropertyNames[pos]}", accessorAttributes, PropertyTypes[pos], Type.EmptyTypes); var getterMethodIL = getterMethod.GetILGenerator(); getterMethodIL.Emit(OpCodes.Ldarg_0); getterMethodIL.Emit(OpCodes.Ldfld, fieldBuilder); getterMethodIL.Emit(OpCodes.Ret); // Define the "set" accessor method for CustomerName. var setterMethod = typeBuilder.DefineMethod($"set_{PropertyNames[pos]}", accessorAttributes, null, new Type[] { PropertyTypes[pos] }); var setterMethodIL = setterMethod.GetILGenerator(); setterMethodIL.Emit(OpCodes.Ldarg_0); setterMethodIL.Emit(OpCodes.Ldarg_1); setterMethodIL.Emit(OpCodes.Stfld, fieldBuilder); setterMethodIL.Emit(OpCodes.Ret); // Last, we must map the two methods created above to our PropertyBuilder to // their corresponding behaviors, "get" and "set" respectively. propertyBuilder.SetGetMethod(getterMethod); propertyBuilder.SetSetMethod(setterMethod); } var liteObjectType = typeBuilder.CreateType(); if (liteObjectType == null) return null; return Activator.CreateInstance(liteObjectType); } }
This code should be covered by unit tests. We need check the following cases: method returns not-null object with exactly two properties. The main issue that the type of constructed object is not defined during compile time. We use Xunit library, and let’s show two different approaches: the using dynamic
type and TypeDescriptor
class.
The first test takes the collection of properties via TypeDescritor
type and checks whether properties with expected name and type exists.
[Fact] public void GetObject_Should_ReturnObjectWithTwoProperties() { object liteObject = null; var exception = Record.Exception(() => liteObject = (new ObjectFactory()).GetObject()); // check correct properties Assert.Null(exception); Assert.NotNull(liteObject); var propertyInfos = TypeDescriptor.GetProperties(liteObject.GetType()); Assert.Equal(propertyInfos.Count, ObjectFactory.PropertyNames.Length); for (var pos = 0; pos < ObjectFactory.PropertyNames.Length; pos++) { var property = propertyInfos[ObjectFactory.PropertyNames[pos]]; Assert.NotNull(property); Assert.Equal(property.PropertyType, ObjectFactory.PropertyTypes[pos]); } }
The second test casts returned object to dynamic type and checks two expected properties and one wrong property. It tries to read the value of expected and wrong properties and checks these operations against exception. Let me note, that one of the disadvantages of the using dynamic
that it is slow, but I guess for the testing it is ok.
[Fact] public void GetObject_Should_ReturnDynamicWithTwoProperties() { dynamic liteObject = null; var exception = Record.Exception(() => liteObject = (new ObjectFactory()).GetObject()); Assert.Null(exception); Assert.NotNull(liteObject); // check correct properties exception = Record.Exception(() => { var name = liteObject.Name; Assert.Equal(name, null); }); Assert.Null(exception); exception = Record.Exception(() => { var count = liteObject.Count; Assert.Equal(count, new int()); }); Assert.Null(exception); // check wrong property exception = Record.Exception(() => { var wrong = liteObject.Wrong; Assert.NotNull(wrong); }); Assert.NotNull(exception); Assert.Equal(exception.Message, "'LiteObject' does not contain a definition for 'Wrong'"); }
Now let us to add methods that assign values to properties of created object:
public object GetObjectViaReflection(string name, int count) { var liteObject = GetObject(); if (liteObject == null) return null; var values = new object[] { name, count }; var propertyInfos = TypeDescriptor.GetProperties(liteObject.GetType()); for (var pos = 0; pos < PropertyNames.Length; pos++) { var property = propertyInfos[ObjectFactory.PropertyNames[pos]]; property?.SetValue(liteObject, values[pos]); } return liteObject; } public object GetObjectViaDynamic(string name, int count) { dynamic liteObject = GetObject(); if (liteObject == null) return null; liteObject.Name = name; liteObject.Count = count; return liteObject; }
The following tests check that property values of constructed object are assigned correctly. As above, they are written in different approaches.
[Fact] public void GetObject_Should_AssignValuesViaReflection() { const string name = "TestName1"; const int count = 10; object liteObject = null; var exception = Record.Exception(() => liteObject = (new ObjectFactory()).GetObjectViaReflection(name, count)); // check correct properties Assert.Null(exception); Assert.NotNull(liteObject); var propertyInfos = TypeDescriptor.GetProperties(liteObject.GetType()); var values = new object[] { name, count }; for (var pos = 0; pos < ObjectFactory.PropertyNames.Length; pos++) { var property = propertyInfos[ObjectFactory.PropertyNames[pos]]; Assert.NotNull(property); Assert.Equal(property.GetValue(liteObject), values[pos]); } } [Fact] public void GetObject_Should_AssignValuesViaDynamic() { const string name = "TestName2"; const int count = 20; dynamic liteObject = null; var exception = Record.Exception(() => liteObject = (new ObjectFactory()).GetObjectViaDynamic(name, count)); // check correct properties Assert.Null(exception); Assert.NotNull(liteObject); Assert.Equal(liteObject.Name, name); Assert.Equal(liteObject.Count, count); }
All tests are working well and cover necessary test cases. As for my opinion, both approaches could be used together that allows choose shorter and more elegant code for each test case.
1. All used IP-addresses, names of servers, workstations, domains, are fictional and are used exclusively as a demonstration only.
2. Information is provided «AS IS».
[…] is located in Nuget storage, and symbol’s package was pushed to SymbolSource.org. The post Xunit and dynamic describes tests that cover simplified version of LiteObjectService […]