为什么密封属性的获取速度比使用GetCustomAttribute <t>()启封的速度慢?</t>

时间:2014-08-19 02:05:33

标签: c# performance attributes code-analysis ndepend

我今晚正在通过NDepend运行我的代码并且违反规则让我感到有些困惑。该规则从MSDN引用以下内容:

  

.NET Framework类库提供了检索方法   自定义属性。默认情况下,这些方法搜索属性   继承层次;例如Attribute.GetCustomAttribute   搜索指定的属性类型或任何属性类型   扩展指定的属性类型。密封属性消除了   通过继承层次结构进行搜索,并且可以改进   性能

我有一个验证基类,它接受验证规则(属性)并处理它们以确保实例上的属性满足特定条件。当实例化类时,它会运行并缓存稍后将运行的所有PropertyInfo's和验证attributes。缓存是一个静态字典,因此每个后续实例只是从字典中提取。然后,我在类上调用ValidateAll()方法,该方法遍历所有PropertyInfo's及其关联的属性,并在其上调用Validate()方法。

我创建了一个Fixture和一个单元测试来迭代超过1,000个实例。测试是在我的验证规则被密封和未密封的情况下进行的。未密封的测试比密封的运行速度更快,这让我有点困惑。至少我预计它们会以相同的速度运行,但速度不会慢。

  1. 未密封的属性:41.3199ms
  2. 密封属性:42.6241ms(使用GetCustomAttribute(typeof(ValidationAttribute), true)
  3. 密封属性:43.3881ms(使用GetCustomAttribute(typeof(ValidationAttribute), false)
  4. 两次是密封和未密封的平均超过5种不同的测试。

    以下是单元测试。我在计时器中包含了对象的初始实例化,因为大部分GetCustomAttribute<IValidationRule>()调用来自此处。

    单元测试

    public void ValidatableBase_ValidationPerformanceTest()
    {
        // Arrange
        var fixtures = new List<ValidatableFixture>();
        var watch = new Stopwatch();
        watch.Start();
        for (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 be set.")]
        public string Name { get; set; }
    
    
        [ValidateValueIsNotNullOrEmpty(ValidationMessageType = typeof(MessageFixture), FailureMessage = "Password must be set.")]
        [ValidateStringIsGreaterThan(GreaterThanValue = 4, ValidationMessageType = typeof(MessageFixture), FailureMessage = "Password must be greater than 4 characters.")]
        public string Password { get; set; }
    
    
        [ValidateWithCustomHandler(DelegateName = PasswordConfirmationDelegateName, ValidationMessageType = typeof(MessageFixture), FailureMessage = "Passwords do 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;
        }
    }
    

    最后,这是处理所有验证的基类。

    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 a property name when adding a new validation message to this instance.");
            }
    
            // If the key does not exist, then we 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 for this 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 all validation messages for the property if a 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 the error from the 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 = "") where TMessage : IMessage, new()
        {
            if (string.IsNullOrEmpty(property) || !this.validationMessages.ContainsKey(property))
            {
                return this.validationMessages.Values.Any(collection => collection.Any(item => item is TMessage));
            }
    
            return this.validationMessages.ContainsKey(property) &&
                this.validationMessages[property].Any(message => message is TMessage);
        }
    
        public Dictionary<string, IEnumerable<IMessage>> GetValidationMessages()
        {
            var messages = new Dictionary<string, IEnumerable<IMessage>>();
    
            // We have to iterate over the collection in order to conver the messages collection
            // from a ICollection type to an 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 an 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 for this property.
                this.OnValidationChanged(new ValidationChangedEventArgs(pair.Key.Name, this.validationMessages[pair.Key.Name]));
            }
        }
    
        public void ValidateProperty(string propertyName = "")
        {
            // If no property is provided, we assume we are to 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 a registered property to be specified.");
            }
            else
            {
                propertyInfo = ValidatableBase.PropertyValidationCache[this.GetType()]
                    .FirstOrDefault(kv => kv.Key.Name.Equals(property)).Key;
            }
    
            if (propertyInfo == null)
            {
                throw new ArgumentNullException("PerformValidation requires a registered property to be specified.");
            }
    
            if (validationProxy != null && validationProxy is IValidatable)
            {
                var proxy = validationProxy as 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 is ValidatableBase)
            {
                var proxy = validationProxy as 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()
        {
            // We instance a cache of property info's and validation rules. If any other Type is instanced that matches ours,
            // we won't need to use reflection to obtain it's members again. We will just hit the 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 all property info's and build a collection of validation rules for each property.
                foreach (PropertyInfo property in propertiesToValidate)
                {
                    IEnumerable<ValidationAttribute> rules = property
                        .GetCustomAttributes(typeof(ValidationAttribute), true) as 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 for this instance once we are done caching.
            foreach (PropertyInfo property in cache.Keys)
            {
                this.RegisterProperty(property.Name);
            }
        }
    }
    

    大多数属性规则不使用反射来获取其他验证属性,但ValidateWithCustomHandler除外。

    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 an instance of our validation message and return it if there is not a delegate specified.
            IMessage validationMessage = Activator.CreateInstance(this.ValidationMessageType, this.FailureMessage) as 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 as ValidationCustomHandlerDelegate).DelegateName == this.DelegateName) != null);
    
            // Attempt to invoke our delegate method.
            object result = null;
            try
            {
                 result = validationDelegate.Invoke(sender, new object[] { validationMessage, property });
            }
            catch (Exception)
            {
                throw;
            }
    
            // Return the results of the delegate method.
            if (result != null && result is IMessage)
            {
                return result as IMessage;
            }
            else if (result == null)
            {
                return null;
            }
    
            return validationMessage;
        }
    }
    

    如果密封属性应该更快,为什么我的单元平均测试速度减慢10 +%?我在基类中的用法是否会影响密封时获取属性的方式?

0 个答案:

没有答案