c# - Why are sealed attributes slower to fetch than unsealed with GetCustomAttribute<T>()? -


i running code through ndepend tonight , had rule violation has me bit perplexed. rule quotes following msdn:

the .net framework class library provides methods retrieving custom attributes. default, these methods search attribute inheritance hierarchy; example attribute.getcustomattribute searches specified attribute type, or attribute type extends specified attribute type. sealing attribute eliminates search through inheritance hierarchy, , can improve performance.

i have validation base class takes validation rules (attributes) , processes them ensure property on instance meets criteria. when class instanced, runs through , caches of propertyinfo's , validation attributes ran through later. cache static dictionary each subsequent instance pulls dictionary. call validateall() method on class iterates through of propertyinfo's , associated attributes , invokes validate() method on them.

i created fixture , unit test iterate on 1,000 instances. test performed validation rules being both sealed , unsealed. unsealed tests ran faster sealed, has me bit confused. @ least expected them run @ same speed, not slower.

  1. unsealed attributes: 41.3199ms
  2. sealed attributes: 42.6241ms (using getcustomattribute(typeof(validationattribute), true))
  3. sealed attributes: 43.3881ms (using getcustomattribute(typeof(validationattribute), false))

the 2 times average on 5 different tests both sealed , unsealed.

the following unit test. included initial instancing of objects in timer bulk of getcustomattribute<ivalidationrule>() calls come from.

unit test

public void validatablebase_validationperformancetest() {     // arrange     var fixtures = new list<validatablefixture>();     var watch = new stopwatch();     watch.start();     (int index = 0; index < 1000; index++)     {         var model = new validatablefixture();         model.name = "testname";         model.password = "pass";         model.passwordconfirmation = "pass";         fixtures.add(model);     }       // act     foreach (validatablefixture fixture in fixtures)     {         fixture.validateall();     }      watch.stop();     debug.writeline(watch.elapsed.totalmilliseconds); } 

validatablefixture

public class validatablefixture : validatablebase {     private const string passwordconfirmationdelegatename = "confirmpasswordsmatch";      public validatablefixture()     {         this.name = string.empty;         this.password = string.empty;         this.passwordconfirmation = string.empty;     }       [validatevalueisnotnullorempty(validationmessagetype = typeof(messagefixture), failuremessage = "name must set.")]     public string name { get; set; }       [validatevalueisnotnullorempty(validationmessagetype = typeof(messagefixture), failuremessage = "password must set.")]     [validatestringisgreaterthan(greaterthanvalue = 4, validationmessagetype = typeof(messagefixture), failuremessage = "password must greater 4 characters.")]     public string password { get; set; }       [validatewithcustomhandler(delegatename = passwordconfirmationdelegatename, validationmessagetype = typeof(messagefixture), failuremessage = "passwords not match.")]     public string passwordconfirmation { get; set; }       [validationcustomhandlerdelegate(delegatename = passwordconfirmationdelegatename)]     public imessage passwordconfirmationvalidation(imessage message, propertyinfo property)     {         return this.passwordconfirmation.equals(this.password) ?             null :             message;     } } 

lastly, base class handles of validation.

validatablebase

public class validatablebase : ivalidatable {     private static readonly dictionary<type, dictionary<propertyinfo, ienumerable<ivalidationrule>>> propertyvalidationcache         = new dictionary<type, dictionary<propertyinfo, ienumerable<ivalidationrule>>>();      private dictionary<string, icollection<imessage>> validationmessages;      public validatablebase()     {         this.validationmessages = new dictionary<string, icollection<imessage>>();         this.setupvalidation();     }      public event eventhandler<validationchangedeventargs> validationchanged;      public void addvalidationmessage(imessage message, string property)     {         if (string.isnullorempty(property))         {             throw new argumentoutofrangeexception("property", "you must supply property name when adding new validation message instance.");         }          // if key not exist, create one.         if (!this.validationmessages.containskey(property))         {             this.validationmessages[property] = new list<imessage>();         }          if (this.validationmessages[property].any(msg => msg.message.equals(message.message) || msg == message))         {             return;         }          this.validationmessages[property].add(message);     }      public void removevalidationmessages()     {         foreach (keyvaluepair<string, icollection<imessage>> pair in this.validationmessages)         {             pair.value.clear();              // publish our new validation collection property.             this.onvalidationchanged(new validationchangedeventargs(pair.key, this.validationmessages[pair.key]));         }     }      public void removevalidationmessages(string property)     {         if (!string.isnullorempty(property) && this.validationmessages.containskey(property))         {             // remove validation messages property if message isn't specified.             this.validationmessages[property].clear();             this.onvalidationchanged(new validationchangedeventargs(property, this.validationmessages[property]));         }     }      public void removevalidationmessage(imessage message, string property)     {         if (string.isnullorempty(property))         {             return;         }          if (!this.validationmessages.containskey(property))         {             return;         }          if (this.validationmessages[property].any(msg => msg == message || msg.message.equals(message.message)))         {             // remove error key's collection.             this.validationmessages[property].remove(                 this.validationmessages[property].firstordefault(msg => msg == message || msg.message.equals(message.message)));              this.onvalidationchanged(new validationchangedeventargs(property, this.validationmessages[property]));         }     }      public bool hasvalidationmessages(string property = "")     {         if (string.isnullorempty(property) || !this.validationmessages.containskey(property))         {             return this.validationmessages.values.any(collection => collection.any());         }          return this.validationmessages.containskey(property) &&             this.validationmessages[property].any();     }      public bool hasvalidationmessages(type messagetype, string property = "")     {         if (string.isnullorempty(property) || !this.validationmessages.containskey(property))         {             return this.validationmessages.values.any(collection => collection.any(item => item.gettype() == messagetype));         }          return this.validationmessages.containskey(property) &&             this.validationmessages[property].any(collection => collection.gettype() == messagetype);     }      public bool hasvalidationmessages<tmessage>(string property = "") tmessage : imessage, new()     {         if (string.isnullorempty(property) || !this.validationmessages.containskey(property))         {             return this.validationmessages.values.any(collection => collection.any(item => item tmessage));         }          return this.validationmessages.containskey(property) &&             this.validationmessages[property].any(message => message tmessage);     }      public dictionary<string, ienumerable<imessage>> getvalidationmessages()     {         var messages = new dictionary<string, ienumerable<imessage>>();          // have iterate on collection in order conver messages collection         // icollection type ienumerable type.         foreach (keyvaluepair<string, icollection<imessage>> pair in this.validationmessages)         {             messages.add(pair.key, pair.value);         }          return messages;     }      public ienumerable<imessage> getvalidationmessages(string property)     {         if (this.validationmessages.containskey(property))         {             return this.validationmessages[property].toarray();         }          // if no validation messages exist, return empty collection.         return new collection<imessage>();     }      public virtual void validateall()     {         this.removevalidationmessages();         dictionary<propertyinfo, ienumerable<ivalidationrule>> cache = propertyvalidationcache[this.gettype()];          foreach (keyvaluepair<propertyinfo, ienumerable<ivalidationrule>> pair in cache)         {             foreach (ivalidationrule rule in pair.value)             {                 this.performvalidation(rule, pair.key);             }              // publish our new validation collection property.             this.onvalidationchanged(new validationchangedeventargs(pair.key.name, this.validationmessages[pair.key.name]));         }     }      public void validateproperty(string propertyname = "")     {         // if no property provided, assume validate everything.         if (string.isnullorempty(propertyname))         {             this.validateall();             return;         }          this.removevalidationmessages(propertyname);         var cache = validatablebase.propertyvalidationcache[this.gettype()];         propertyinfo property = cache.keys.firstordefault(p => p.name.equals(propertyname));          foreach (ivalidationrule rule in cache[property])         {             this.performvalidation(rule, property);         }          this.onvalidationchanged(new validationchangedeventargs(propertyname, this.validationmessages[propertyname]));     }      public imessage validateproperty(func<bool> validationdelegate, imessage failuremessage, string propertyname, ivalidatable validationproxy = null)     {         if (validationproxy != null)         {             return validationproxy.validateproperty(validationdelegate, failuremessage, propertyname);         }          bool passedvalidation = validationdelegate();         if (!passedvalidation)         {             this.addvalidationmessage(failuremessage, propertyname);         }         else         {             this.removevalidationmessage(failuremessage, propertyname);         }          return !passedvalidation ? failuremessage : null;     }      public void refreshvalidation(string property)     {         if (!string.isnullorempty(property) && this.hasvalidationmessages(property))         {             this.validateproperty(property);         }     }      public void performvalidation(ivalidationrule rule, string property, ivalidatable validationproxy = null)     {         propertyinfo propertyinfo = null;         if (string.isnullorempty(property))         {             throw new argumentnullexception("performvalidation requires registered property specified.");         }         else         {             propertyinfo = validatablebase.propertyvalidationcache[this.gettype()]                 .firstordefault(kv => kv.key.name.equals(property)).key;         }          if (propertyinfo == null)         {             throw new argumentnullexception("performvalidation requires registered property specified.");         }          if (validationproxy != null && validationproxy ivalidatable)         {             var proxy = validationproxy ivalidatable;             proxy.performvalidation(rule, propertyinfo.name);         }         else         {             imessage result = rule.validate(propertyinfo, this);             if (result != null)             {                 this.addvalidationmessage(result, propertyinfo.name);             }         }     }      protected virtual void onvalidationchanged(validationchangedeventargs args)     {         eventhandler<validationchangedeventargs> handler = this.validationchanged;          if (handler == null)         {             return;         }          handler(this, args);     }      private void registerproperty(params string[] propertyname)     {         foreach (string property in propertyname)         {             if (!this.validationmessages.containskey(property))             {                 this.validationmessages[property] = new list<imessage>();             }         }     }      private void performvalidation(ivalidationrule rule, propertyinfo property, ivalidatable validationproxy = null)     {         if (validationproxy != null && validationproxy validatablebase)         {             var proxy = validationproxy validatablebase;             proxy.performvalidation(rule, property);         }          imessage result = null;         try         {             result = rule.validate(property, this);         }         catch (exception)         {             throw;         }          if (result != null)         {             this.addvalidationmessage(result, property.name);         }     }      private void setupvalidation()     {         // instance cache of property info's , validation rules. if other type instanced matches ours,         // won't need use reflection obtain it's members again. hit cache.         var cache = new dictionary<propertyinfo, ienumerable<ivalidationrule>>();          if (!validatablebase.propertyvalidationcache.containskey(this.gettype()))         {             ienumerable<propertyinfo> propertiestovalidate = this.gettype().getproperties()                 .where(p => p.getcustomattributes(typeof(validationattribute), true).any());              // loop through property info's , build collection of validation rules each property.             foreach (propertyinfo property in propertiestovalidate)             {                 ienumerable<validationattribute> rules = property                     .getcustomattributes(typeof(validationattribute), true) ienumerable<validationattribute>;                 cache.add(property, rules);             }              validatablebase.propertyvalidationcache[this.gettype()] =                 new dictionary<propertyinfo, ienumerable<ivalidationrule>>(cache);         }         else         {             cache = validatablebase.propertyvalidationcache[this.gettype()];         }          // register each property instance once done caching.         foreach (propertyinfo property in cache.keys)         {             this.registerproperty(property.name);         }     } } 

the majority of attribute rules don't use reflection fetch additional validation attributes exception of validatewithcustomhandler does.

public sealed class validatewithcustomhandlerattribute : validationattribute {     public string delegatename { get; set; }      public override imessage validate(system.reflection.propertyinfo property, ivalidatable sender)     {         if (!this.canvalidate(sender))         {             return null;         }          // create instance of our validation message , return if there not delegate specified.         imessage validationmessage = activator.createinstance(this.validationmessagetype, this.failuremessage) imessage;         if (string.isnullorempty(this.delegatename))         {             return validationmessage;         }          // find our delegate method.         ienumerable<methodinfo> validationmethods = sender             .gettype()             .getmethods(bindingflags.public | bindingflags.instance | bindingflags.nonpublic)             .where(m => m.getcustomattributes(typeof(validationcustomhandlerdelegate), true).any());          methodinfo validationdelegate = validationmethods             .firstordefault(m => m.getcustomattributes(typeof(validationcustomhandlerdelegate), true)                 .firstordefault(del => (del validationcustomhandlerdelegate).delegatename == this.delegatename) != null);          // attempt invoke our delegate method.         object result = null;         try         {              result = validationdelegate.invoke(sender, new object[] { validationmessage, property });         }         catch (exception)         {             throw;         }          // return results of delegate method.         if (result != null && result imessage)         {             return result imessage;         }         else if (result == null)         {             return null;         }          return validationmessage;     } } 

if sealed attribute supposed quicker, why unit tests 10+% slower on average? usage in base class affecting way attributes fetched while sealed?


Comments

Popular posts from this blog

java - How to specify maven bin in eclipse maven plugin? -

single sign on - Logging into Plone site with credentials passed through HTTP -

php - Why does AJAX not process login form? -