我今晚正在通过NDepend运行我的代码并且违反规则让我感到有些困惑。该规则从MSDN引用以下内容:
.NET Framework类库提供了检索方法 自定义属性。默认情况下,这些方法搜索属性 继承层次;例如Attribute.GetCustomAttribute 搜索指定的属性类型或任何属性类型 扩展指定的属性类型。密封属性消除了 通过继承层次结构进行搜索,并且可以改进 性能
我有一个验证基类,它接受验证规则(属性)并处理它们以确保实例上的属性满足特定条件。当实例化类时,它会运行并缓存稍后将运行的所有PropertyInfo's
和验证attributes
。缓存是一个静态字典,因此每个后续实例只是从字典中提取。然后,我在类上调用ValidateAll()
方法,该方法遍历所有PropertyInfo's
及其关联的属性,并在其上调用Validate()
方法。
我创建了一个Fixture和一个单元测试来迭代超过1,000个实例。测试是在我的验证规则被密封和未密封的情况下进行的。未密封的测试比密封的运行速度更快,这让我有点困惑。至少我预计它们会以相同的速度运行,但速度不会慢。
GetCustomAttribute(typeof(ValidationAttribute), true)
)GetCustomAttribute(typeof(ValidationAttribute), false)
)两次是密封和未密封的平均超过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);
}
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;
}
}
最后,这是处理所有验证的基类。
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 +%?我在基类中的用法是否会影响密封时获取属性的方式?