Thanks to Bruno Baia and Mark Pollack for pointing me to the Mono.Cecil introspection library. The focus of the previous post on this subject were limitations of System.Reflection library in relation to copying custom attributes from the original type to the target type.
High level inspection of Mono.Cecil showed that library looks very promising for resolving the custom attributes issue and far beyond this.
I only targeted some proof of concept for this time, and code to follow can be pretty naive at some places and only counts now with copying of type attributes from one type to another (not including methods, properties, parameters’ attributes). Though it should be pretty straight forward to evolve to the fully fledged solution.
Proof of concept code:
Helper method signature is
First, I tried to follow the current ReflectionUtils signature for this which would look like:
Type type, object[] ctorArgs, Attribute sourceAttribute)
Though I ran quite soon into the necessity to find the attribute passed in this method call via Cecil (which leads to the question of attributes equality). Realizing that that would take quite an effort to resolve, may still be error prone and there is no any real value in being this granular, I decided to go with the high-level signature presented above. I can imagine some interceptor to be passed in as a parameter to influence the attributes copy process.
Find the TypeDefinition of the source type from which we want to copy attributes.
GetTypeMatchFromAssembly method simply iterates through the assembly and finds a type based onto the FullName match which for the given assembly should be unique.
Iterate through the collection of the type’s custom attributes.
Create constructor parameter types and values arrays.
Type[] parameterTypes = new Type[attribute.Constructor.Parameters.Count];
for (int i = 0; i < attribute.Constructor.Parameters.Count; i++)
{
parameterTypes[i] =
Type.GetType(attribute.Constructor.Parameters[i].ParameterType.FullName);
parameterValues[i] = attribute.ConstructorParameters[i];
}
The abilities given by Cecil in this area are of great use for our task. If we compare with current workarounds present in Spring:
{
….
paramsType[i] = (arg != null) ? args[i].GetType() : typeof(object);
This above line of code from ReflectionUtils is definitely a big workaround which may pick the wrong ctor actually. Though, Spring.NET is not utilizing this code path I believe, as array of objects would be always empty, due to other workarounds.
Create ConstructorInfo and set the custom attribute on a typeBuilder.
attribute.Constructor.DeclaringType.FullName + “,” +
attribute.Constructor.DeclaringType.Scope, true).GetConstructor(
parameterTypes); target.SetCustomAttribute(new CustomAttributeBuilder(
constructor, parameterValues));
The above code is absolutely precise about the constructor it is creating based onto parameter types and values. Which again is not achievable by means of pure Reflection in the current Spring.NET implementation.
Unit test output
In the unit test a type of TargetType is created, its typeBuilder is passed to the proof of concept method along with a source type.
Type sourceType = typeof(TypeWithTestAttribute);
Type targetType = typeBuilder.CreateType();
Console.WriteLine(DumpMemberAttributes(sourceType));
Console.WriteLine(DumpMemberAttributes(targetType));
targetType = typeBuilder.CreateType();
Console.WriteLine(DumpMemberAttributes(targetType));
Which produces the following output:
*Dump of the custom attributes for member: Dsi.Mono.Cecil.TestTypes.B.TypeWithTestAttribute(TypeInfo)
**Attribute of type Dsi.Mono.Cecil.TestTypes.A.CustomTestAttribute
Properties:
FirstGetterOnlyPropertyValue:99
SecondGetterOnlyPropertyValue:-99
ReadWriteProperty:ReadWriteProperty 2 set via class attribute ctor
TypeId:Dsi.Mono.Cecil.TestTypes.A.CustomTestAttribute
Fields:
**Attribute of type System.Runtime.InteropServices.GuidAttribute
Properties:
Value:263E3E90-9D06-4660-AD67-28D1BA5E0107
TypeId:System.Runtime.InteropServices.GuidAttribute
Fields:
**Attribute of type Dsi.Mono.Cecil.TestTypes.A.CustomTestAttribute
Properties:
ReadWriteProperty:ReadWriteProperty 2 set via class attribute ctor
FirstGetterOnlyPropertyValue:99
SecondGetterOnlyPropertyValue:-99
TypeId:Dsi.Mono.Cecil.TestTypes.A.CustomTestAttribute
Fields:
**Attribute of type Dsi.Mono.Cecil.TestTypes.A.CustomTestAttribute
Properties:
ReadWriteProperty:ReadWriteProperty 1 set via class attribute ctor
FirstGetterOnlyPropertyValue:100
SecondGetterOnlyPropertyValue:-100
TypeId:Dsi.Mono.Cecil.TestTypes.A.CustomTestAttribute
Fields:
**Attribute of type System.SerializableAttribute
Properties:
TypeId:System.SerializableAttribute
Fields:
*Dump of the custom attributes for member: TargetType(TypeInfo)
No Attributes present!
*Target Type after copying attributes:
*Dump of the custom attributes for member: TargetType(TypeInfo)
**Attribute of type Dsi.Mono.Cecil.TestTypes.A.CustomTestAttribute
Properties:
ReadWriteProperty:null
FirstGetterOnlyPropertyValue:99
SecondGetterOnlyPropertyValue:-99
TypeId:Dsi.Mono.Cecil.TestTypes.A.CustomTestAttribute
Fields:
**Attribute of type System.Runtime.InteropServices.GuidAttribute
Properties:
Value:263E3E90-9D06-4660-AD67-28D1BA5E0107
TypeId:System.Runtime.InteropServices.GuidAttribute
Fields:
**Attribute of type Dsi.Mono.Cecil.TestTypes.A.CustomTestAttribute
Properties:
ReadWriteProperty:null
FirstGetterOnlyPropertyValue:99
SecondGetterOnlyPropertyValue:-99
TypeId:Dsi.Mono.Cecil.TestTypes.A.CustomTestAttribute
Fields:
**Attribute of type Dsi.Mono.Cecil.TestTypes.A.CustomTestAttribute
Properties:
ReadWriteProperty:null
FirstGetterOnlyPropertyValue:100
SecondGetterOnlyPropertyValue:-100
TypeId:Dsi.Mono.Cecil.TestTypes.A.CustomTestAttribute
Fields:
Conclusion.
It seems that introspection and Mono.Cecil library would provide quite a benefit to Spring.NET if used. Areas of use exceed broadly the issue resolved by this proof of concept. As well as I believe one can expect much better performance in many areas from applying introspection as opposed to a Reflection.
For completeness, providing the test types used.
Test classes - custom attribute and class it is applied to.
using System;using System.Collections.Generic;
using System.Text;namespace Dsi.Mono.Cecil.TestTypes.A
{
[AttributeUsage(AttributeTargets.All, AllowMultiple = true)]
public class CustomTestAttribute : Attribute
#region Fields
private int firstGetterOnlyPropertyValue;
private int secondGetterOnlyPropertyValue;
private string readWriteProperty;
#endregion
#region Properties
public int FirstGetterOnlyPropertyValue
{
get { return firstGetterOnlyPropertyValue; }
}
public int SecondGetterOnlyPropertyValue
{
get { return secondGetterOnlyPropertyValue; }
}
public string ReadWriteProperty
{
get { return readWriteProperty; }
set
{
if (value == null)
{
throw new ArgumentNullException(”ReadWriteProperty”,
“Value can not be null!”);
}
readWriteProperty = value;
}
}
#endregion
#region Constructors
public CustomTestAttribute(int firstGetterOnlyProperty) : base()
{
this.firstGetterOnlyPropertyValue = firstGetterOnlyProperty;
}
public CustomTestAttribute(int firstGetterOnlyProperty, int secondGetterOnlyPropertyValue)
: this(firstGetterOnlyProperty)
{
this.secondGetterOnlyPropertyValue = secondGetterOnlyPropertyValue;
}
public CustomTestAttribute(int firstGetterOnlyProperty, int secondGetterOnlyPropertyValue,
string readWriteProperty)
: this(firstGetterOnlyProperty, secondGetterOnlyPropertyValue)
{
this.readWriteProperty = readWriteProperty;
}
#endregion
}
}
namespace Dsi.Mono.Cecil.TestTypes.B{ [CustomTestAttribute(100, -100, ReadWriteProperty = “ReadWriteProperty 1 set via class attribute ctor”)][CustomTestAttribute(99, -99, ReadWriteProperty = “ReadWriteProperty 2 set via class attribute ctor”)][CustomTestAttribute(99, -99, ReadWriteProperty = “ReadWriteProperty 2 set via class attribute ctor”)][SerializableAttribute()][GuidAttribute(”263E3E90-9D06-4660-AD67-28D1BA5E0107″)]public class TypeWithTestAttribute
{
}}
0 Responses to “Spring.NET - Introspection vs. Reflection. Use of Mono.Cecil library.”