attribute is a meta-programming capability provided on the. NET platform that can be tagged to decorate various members. Whether it is component design, interoperability between languages, or the most common frame use, now is inseparable from the attribute. Due to the importance of attribute's function (Kent Beck argues that NUnit is better than the early junit design, a major aspect is the use of attribute), The Java language also introduced the annotation concept similar to attribute in version 5.0. But attribute is also a reflection operation, usually normal use will not cause problems, but the dense call still has some impact on performance. This time we'll summarize how we can avoid some performance issues with attribute operations.
Suppose we have a attribute, which is defined on a type:
[AttributeUsage (AttributeTargets.Class, AllowMultiple = True, inherited = True)]public Class Testattribute: attribute{Public Testattribute (string prop) {this . Prop = Prop; } Public Testattribute () {} public string Prop {get; set;}} [Test ("Hello World")] [Test (Prop = "Hello World")]public class SomeClass {}
So, if we need to get the testattribute marked on the SomeClass type, we will generally use the GetCustomAttributes method of the Type object. So what happened in it?
by using. NET Reflector to track the implementation, you will find that the logic is ultimately done by the CustomAttribute GetCustomAttributes method, and interested friends can find the most complex overload. Because of the complexity of the implementation, I did not understand the complete logic, but from the key code it can be seen that it actually uses the Activator.CreateInstance method to create the object, and uses reflection to set the properties of the attribute object. So I'm going to look at how much of this reflection operation accounts for the whole GetCustomAttributes method:
Codetimer.time ("GetCustomAttributes", [+], () =>{ var attributes = typeof (SomeClass). GetCustomAttributes (typeof (Testattribute), True);}); Codetimer.time ("Reflection", [+], () =>{ var a1 = (Testattribute) activator.createinstance (typeof ( Testattribute), "Hello World"); var a2 = (testattribute) activator.createinstance (typeof (Testattribute)); typeof (Testattribute). GetProperty ("Prop"). SetValue (A2, "Hello World", NULL);});
The results are as follows:
GetCustomAttributes Time Elapsed: 2,091ms CPU Cycles: 5,032,765,488 gen 0: Gen 1: 0 Gen 2: 0 Reflection Time Elapsed: 527ms CPU Cycles: 1,269,399,624 gen 0: gen 1: 0 Gen 2: 0
As you can see, although reflection is used in the GetCustomAttributes method for object creation and property setting, most of its overhead is used to get some metadata, which takes up 3/4 of the time, and the cost of reflection is actually only about 1/4. This is somewhat surprising, since it is a static metadata, why does the. NET framework not cache this data, but do it again every time? Even though we should not cache the last attribute object, the "information" used to construct the object is completely cacheable.
The fact that the. NET framework has actually given enough information is CustomAttributeData's GetCustomAttributes method, which returns Ilist<customattributedata The > object, which contains all the information needed to construct the attribute. In other words, I can "quickly build" attribute objects based on a customattributedata:
public class attributefactory{Public attributefactory (CustomAttributeData data) {this. data = data; var ctorinvoker = new Constructorinvoker (data. Constructor); var Ctorargs = data. Constructorarguments.select (A = a.value). ToArray (); This.m_attributecreator = () = Ctorinvoker.invoke (Ctorargs); This.m_propertysetters = new list<action<object>> (); foreach (var arg in data). namedarguments) {var property = (PropertyInfo) arg. MemberInfo; var propertyaccessor = new PropertyAccessor (property); var value = arg. Typedvalue.value; THIS.M_PROPERTYSETTERS.ADD (o = Propertyaccessor.setvalue (o, value)); }} public CustomAttributeData Data {get; private set;} Private func<object> M_attributecreator; Private list<action<object>> m_propertysetters; Public Attribute Create () {var Attribute = This.m_attributecreator (); foreach (var setter in this.m_propertysetters) {setter (attribute); } return (Attribute) Attribute; }}
Attributefactory used the Fastreflectionlib, The ConstructorInfo and PropertyInfo are packaged into high performance Constructorinvoker and PropertyAccessor objects, which can be used with an order of magnitude performance improvement. Let's take another test:
var factories = Customattributedata.getcustomattributes (typeof (SomeClass)) . Where (d = d.constructor.declaringtype = = typeof (Testattribute)) . Select (d = new Attributefactory (d)). ToList (); Codetimer.time ("GetCustomAttributes", [+], () =>{ var attributes = typeof (SomeClass). GetCustomAttributes (typeof (Testattribute), True);}); Codetimer.time ("attributefactory", + +, () = factories. ForEach (f = f.create ()));
The results are as follows:
GetCustomAttributes Time Elapsed: 2,131ms CPU Cycles: 5,136,848,904 gen 0: gen 1: Gen 2: 0 Attribute Factory Time Elapsed: 18ms CPU Cycles: 44,235,564 gen 0: 4 gen 1: 4 Gen 2: 0
Here, we first get all the defined CustomAttributeData objects in SomeClass, and then, based on their constructor type, determine which ones are used to construct the Testattribute object. They are then used to construct the attributefactory. Attributefactory instances can be cached and reused during actual use. In this way, we can get new attribute objects each time and avoid the inexplicable overhead of the GetCustomAttributes method.
In fact, we can completely use this method to achieve a higher performance Getcustomattributesex method, which behaves well. NET comes in exactly the same getcustomattributes, but the performance can be fast on countless--maybe 100 times times. However, although this method is not difficult to write, but more cumbersome. Because CustomAttributeData can only be used to obtain data that is "directly defined" on a member, the reality is that We also often have to decide whether or not to traverse the entire inheritance chain based on the AllowMultiple and inherited attributes of the AttributeUsage that are marked on a attribute. Only in this way can we reproduce the behavior of the GetCustomAttribute method completely.
But we have an advantage here, that is, "static." Once "static", we can use the "naked eye" to determine the specific processing mode for a particular scene, so that we do not need a very general Getcustomattributeex method. For example, in the actual use of the process, we can find that a attribute inherited property is False, then we can eliminate the hassle of traversing the inheritance chain.
Finally, there are two points that may be worth mentioning:
In addition to the GetCustomAttributes methods that are available to members such as type,assembly, attribute classes also have static getcustomattributes methods that can be used to get attribute objects. However, with. NET Reflector, we can see that static methods in the attribute class are ultimately delegated to their own instance methods, so there is no performance improvement. The only difference is parameterinfo--but I don't understand why it's so complicated and interested friends can explore it by themselves.
If you are merely judging whether a member defines a particular type of attribute object, you can use the attribute.isdefined static method. Its performance is many times more efficient than the length of the array after GetCustomAttributes. But personally, this is not very important, because this is a static information, even if we use a slower GetCustomAttributes method to judge, but also the final true or false results can be cached, which naturally does not have a performance problem.
The reason we call the GetCustomAttributes method over and over again is because each attribute object we get is new, so it might not be possible to cache them in some scenarios. Now, however, there is now a quicker way of doing so, and there will be no big problem in this area.
Performance optimization methods for attribute operations