- 1. Assignment and Reading of Property Fields
- 2. Custom Attributes and Attribute Lookup
- 3. Designing a Data Validation Tool
- 3.1 Defining An Abstract Validation Attribute Class
- 3.2 Implementing Multiple Custom Validation Attributes
- 3.3 Checking If An Attribute Belongs to Custom Validation Attributes
- 3.4 Checking If Property Values Meet Custom Validation Requirements
- 3.5 Implementing Parsing Functionality
- 3.6 Writing A Model Class
- 3.7 Executing Validation
- 3.8 Summary
This chapter primarily focuses on the assignment and reading of properties and fields, custom attributes, and applying attributes to practical scenarios.
The content of this article has been uploaded to https://gitee.com/whuanle/reflection_and_properties/blob/master/C%23反射与特性(7)自定义特性以及应用.cs
1. Assignment and Reading of Property Fields
The fifth article introduced method overloading and calling methods. The sixth article summarized previous knowledge and practical exercises. This section will introduce operations on properties and fields.
From the previous discussion, we know that properties can be accessed through reflection using PropertyInfo and fields using FieldInfo. In "C# Reflection and Attributes (III): Members of Reflecting Types" section 1.2, detailed information is provided on obtaining properties and field members. We will not elaborate further here; let's formally dive into the topic.
The GetValue()
and SetValue()
methods in PropertyInfo allow you to get or set the values of instance properties and fields.
Creating a type
public class MyClass
{
public string A { get; set; }
}
Writing test code
// Get Type and PropertyInfo
Type type = typeof(MyClass);
PropertyInfo property = type.GetProperty(nameof(MyClass.A));
// Instantiate MyClass
object example1 = Activator.CreateInstance(type);
object example2 = Activator.CreateInstance(type);
// Assign values to property A in example instance
property.SetValue(example1, "Value Test");
property.SetValue(example2, "Natasha is awesome");
// Read property values from instances
Console.WriteLine(property.GetValue(example1));
Console.WriteLine(property.GetValue(example2));</code></pre>
It's important to emphasize that reflection operations involving type calls (like calling methods or properties) must be done through instances.
Types and PropertyInfo are only for reading metadata and cannot affect the program; only instances can do so.
From the operations above, we created two example instances using reflection and then manipulated the instances to achieve value reading and assignment.
Operations for the value of properties are quite straightforward, and there is nothing more to elaborate on.
2. Custom Attributes and Attribute Lookup
In ASP.NET Core, we can use attributes like [HttpGet]
, [HttpPost]
, [HttpDelete]
for Controllers and Actions to define request types and routing addresses.
In EFCore, attributes like [Key]
, [Required]
can be used, and other frameworks also have various types of attributes.
Attributes can be used to annotate classes, properties, interfaces, structs, enums, delegates, events, methods, constructors, fields, parameters, return values, assemblies, type parameters, and modules.
2.1 Specifications and Custom Attributes
C# pre-defines three types of attributes:
Name
Type
Description
Conditional
Bitmapped Attribute
Can be mapped to specific bits of type metadata; public, abstract, and sealed are compiled as bitmapped attributes.
AttributeUsage
Custom Attribute
Custom-defined attributes.
Obsolete
Pseudo-Custom Attribute
Similar to custom attributes but optimized by the compiler or CLR internally.
Bitmapped attributes mostly occupy only one bit of space in memory, making them very efficient.
An attribute is a class that inherits from Attribute, and the name of an attribute class must end with the suffix Attribute
.
2.1.1 Defining Attributes
First, create a class that inherits from System.Attribute
.
public class MyTestAttribute : Attribute
{
}</code></pre>
2.1.2 Restricting Attribute Usage
Using AttributeUsageAttribute
allows you to specify what types the defined attribute can be applied to.
Usage example
[AttributeUsage(AttributeTargets.Property | AttributeTargets.Field)]
public class MyTestAttribute : Attribute
{
}</code></pre>
The basic format for defining an attribute with AttributeUsageAttribute is as follows:
[AttributeUsage(
validon,
AllowMultiple=allowmultiple,
Inherited=inherited
)]
validon refers to the AttributeTargets enumeration, and the types of AttributeTargets enumeration are as follows:
Enumeration
Value
Description
All
32767
Attributes can be applied to any application elements.
Assembly
1
Attributes can be applied to an assembly.
Class
4
Attributes can be applied to a class.
Constructor
32
Attributes can be applied to a constructor.
Delegate
4096
Attributes can be applied to a delegate.
Enum
16
Attributes can be applied to an enum.
Event
512
Attributes can be applied to an event.
Field
256
Attributes can be applied to a field.
GenericParameter
16384
Attributes can be applied to generic parameters; currently, this attribute can only be applied in C#, Microsoft Intermediate Language (MSIL), and compiled code.
Interface
1024
Attributes can be applied to an interface.
Method
64
Attributes can be applied to a method.
Module
2
Attributes can be applied to a module; Module
refers to portable executable files (.dll or .exe), not to Visual Basic standard modules.
Parameter
2048
Attributes can be applied to a parameter.
Property
128
Attributes can be applied to a property.
ReturnValue
8192
Attributes can be applied to a return value.
Struct
8
Attributes can be applied to a struct (value type).
AllowMultiple indicates whether this attribute can be used multiple times in the same location. By default, it is not allowed. If set to true, the attribute can be applied multiple times to the same field or property, etc.
Inherited indicates whether derived classes can inherit this attribute when inheriting a type that uses this attribute. For example, if A uses this attribute and B inherits from A, then if Inherited = true
, the derived class will also have this attribute.
2.1.3 Attribute Constructors and Properties
Attributes can have constructors and property fields, which are configured when using the attribute.
Defining an attribute
[AttributeUsage(AttributeTargets.Class | AttributeTargets.Property | AttributeTargets.Field)]
public class MyTestAttribute : Attribute
{
private string A;
public string Name { get; set; }
public MyTestAttribute(string message)
{
A = message;
}
}
Using the attribute
public class MyClass
{
[MyTest("test", Name = "666")]
public string A { get; set; }
}
2.2 Retrieving Attributes
After creating custom attributes, we come to the retrieval phase of attributes.
But what are the purposes of these steps? In what scenarios are they applicable? Don't worry about that for now, let's implement the steps first.
There are two methods for retrieving attributes:
- Call the GetCustomAttributes method of Type or MemberInfo;
- Call the Attribute.GetCustomAttribute or Attribute.GetCustomAttributes methods;
2.2.1 Method One
First, define the attributes.
[AttributeUsage(AttributeTargets.Class | AttributeTargets.Property | AttributeTargets.Field)]
public class ATestAttribute : Attribute
{
public string NameA { get; set; }
}
[AttributeUsage(AttributeTargets.Class | AttributeTargets.Property | AttributeTargets.Field)]
public class BTestAttribute : Attribute
{
public string NameB { get; set; }
}</code></pre>
Using the attributes
[ATest(NameA = "Myclass")]
public class MyClass
{
[Required]
[EmailAddress]
[ATest(NameA = "A")]
public string A { get; set; }
[Required]
[EmailAddress]
[ATest(NameA = "B")]
[BTest(NameB = "BB")]
public string B { get; set; }
}</code></pre>
Retrieving at runtime
Type type = typeof(MyClass);
MemberInfo[] member = type.GetMembers();
// GetCustomAttributes method of Type or MemberInfo
// Type.GetCustomAttributes() retrieves attributes of the type
IEnumerable<ATestAttribute> attrs = type.GetCustomAttributes<ATestAttribute>();
Console.WriteLine(type.Name + " has attributes:");
foreach (ATestAttribute item in attrs)
{
Console.WriteLine(item.NameA);
}
Console.WriteLine(**********);
// Loop through each member
foreach (MemberInfo item in member)
{
// Get attributes of each member
var attrList = item.GetCustomAttributes();
foreach (Attribute itemNode in attrList)
{
// If it's an ATestAttribute
if (itemNode.GetType() == typeof(ATestAttribute))
Console.WriteLine(((ATestAttribute)itemNode).NameA);
else if (itemNode.GetType() == typeof(BTestAttribute))
Console.WriteLine(((BTestAttribute)itemNode).NameB);
else
Console.WriteLine("This is not my defined attribute: " + itemNode.GetType());
}
}</code></pre>
2.2.2 Method Two
With the previous custom attributes and MyClass class unchanged, change the code in the Main method to the following:
Type type = typeof(MyClass);
// Attribute[] classAttr = Attribute.GetCustomAttributes(type);
// Retrieve specified attributes of the type
ATestAttribute classAttr = Attribute.GetCustomAttribute(type, typeof(ATestAttribute)) as ATestAttribute;
Console.WriteLine(classAttr.NameA);</code></pre>
3. Designing a Data Validation Tool
To apply what we've learned, we will implement a data validation feature that can check if the properties within a type meet specified requirements.
Requirements include:
- The ability to check whether an object's properties meet format requirements;
- Custom validation failure messages;
- Dynamic implementation;
- Good programming style and extensibility.
After the code is complete, it will look something like this (around 250 lines):

3.1 Defining An Abstract Validation Attribute Class
First, define an abstract attribute class as the base class for our custom validation, making it easier to implement extensions later on.
///
/// Custom validation attribute abstract class
///
[AttributeUsage(AttributeTargets.Property | AttributeTargets.Field, AllowMultiple = false)]
public abstract class MyValidationAttribute : Attribute
{
private string Message;
///
/// Error message when validation fails
///
public string ErrorMessage
{
get
{
return string.IsNullOrEmpty(Message) ? 默认报错 : Message;
}
set
{
Message = value;
}
}
/// <summary>
/// Checks whether the validation is successful
/// </summary>
/// <param name="value"></param>
/// <returns></returns>
public virtual bool IsValid(object value)
{
return value == null ? false : true;
}
}</code></pre>
Design Principle:
ErrorMessage is a custom message for validation failure; if not specified during use, it defaults to 默认报错
.
IsValid indicates the entry point for the custom validation attribute class; this method can check whether the property has passed validation.
3.2 Implementing Multiple Custom Validation Attributes
Based on MyValidationAttribute, we inherit from it to implement different types of data validation.
Four validations are implemented here: non-empty validation, mobile phone number validation, email format validation, and numeric validation.
///
/// Indicates that a property or field cannot be empty
///
[AttributeUsage(AttributeTargets.Property | AttributeTargets.Field, AllowMultiple = false)]
public class MyEmptyAttribute : MyValidationAttribute
{
///
/// Check if it is empty
///
///
///
public override bool IsValid(object value)
{
if (value == null)
return false;
if (string.IsNullOrEmpty(value.ToString()))
return false;
return true;
}
}
/// <summary>
/// Checks if it is a mobile phone number format
/// </summary>
[AttributeUsage(AttributeTargets.Property | AttributeTargets.Field, AllowMultiple = false)]
public class MyPhoneAttribute : MyValidationAttribute
{
public override bool IsValid(object value)
{
if (value == null)
return false;
if (string.IsNullOrEmpty(value.ToString()))
return false;
string pattern = ^((13[0-9])|(14[5,7])|(15[0-3,5-9])|(17[0,3,5-8])|(18[0-9])|166|198|199|(147))\\d{8}%%EDITORCONTENT%%quot;;
Regex regex = new Regex(pattern);
return regex.IsMatch(value.ToString());
}
}
/// <summary>
/// Checks if it is in email format
/// </summary>
[AttributeUsage(AttributeTargets.Property | AttributeTargets.Field, AllowMultiple = false)]
public class MyEmailAttribute : MyValidationAttribute
{
public override bool IsValid(object value)
{
if (value == null)
return false;
if (string.IsNullOrEmpty(value.ToString()))
return false;
string pattern = @^[A-Za-z0-9\u4e00-\u9fa5]+@[a-zA-Z0-9_-]+(\.[a-zA-Z0-9_-]+)+%%EDITORCONTENT%%quot;;
Regex regex = new Regex(pattern);
return regex.IsMatch(value.ToString());
}
}
/// <summary>
/// Checks if it is all numeric
/// </summary>
[AttributeUsage(AttributeTargets.Property | AttributeTargets.Field, AllowMultiple = false)]
public class MyNumberAttribute : MyValidationAttribute
{
public override bool IsValid(object value)
{
if (value == null)
return false;
if (string.IsNullOrEmpty(value.ToString()))
return false;
string pattern = ^[0-9]*%%EDITORCONTENT%%quot;;
Regex regex = new Regex(pattern);
return regex.IsMatch(value.ToString());
}
}</code></pre>
Implementation Principle:
Determine if the property value matches the format through regular expressions (the regular expressions are copied, and the author is not familiar with them).
It should be noted that the above validation code still needs improvement to adapt to various types of validation.
3.3 Checking if the Attribute Belongs to Custom Validation Attributes
Check if an attribute belongs to our custom validation attributes.
If it does not, it should be ignored.
///
/// Checks if the attribute belongs to the MyValidationAttribute type
///
/// The attribute to check
///
private static bool IsMyValidationAttribute(Attribute attribute)
{
Type type = attribute.GetType();
return type.BaseType == typeof(MyValidationAttribute);
}
Implementation Principle:
Our custom validation attribute classes inherit from MyValidationAttribute; if an attribute's parent class is not MyValidationAttribute
, then it is definitely not an attribute we implemented.
3.4 Checking if Property Value Meets Custom Validation Attribute Requirements
This involves property value retrieval, method invocation, etc. We use the instance object, attribute object, and property object to determine whether a property's value meets the attribute's requirements.
///
/// Validates if this property passes validation; can only validate properties that inherit from MyValidationAttribute
///
/// The attribute attached to the property
/// The property to validate
/// The instance object
///
private static (bool, string) StartValid(Attribute attr, PropertyInfo property, object obj)
{
// Specify to retrieve the property value of the instance object
object value = property.GetValue(obj);
// Get the IsValid method of the attribute
MethodInfo attrMethod = attr.GetType().GetMethod(IsValid, new Type[] { typeof(object) });
// Get the ErrorMessage property of the attribute
PropertyInfo attrProperty = attr.GetType().GetProperty(ErrorMessage);
// Begin checking, get the result
bool checkResult = (bool)attrMethod.Invoke(attr, new object[] { value });
// Get the ErrorMessage attribute of the attribute
string errorMessage = (string)attrProperty.GetValue(attr);
// If passed validation, there is no error message
if (checkResult == true)
return (true, null);
// If validation fails, return the predefined message
return (false, errorMessage);
}</code></pre>
Design Principle:
- First, verify the property's value;
- Invoke the
IsValid
method of this attribute to check if the value passes validation;
- Retrieve the custom error message for validation failure;
- Return the validation result;
3.5 Implementing Parsing Functionality
We need to implement a function:
Parse all properties of an object, systematically retrieve each property, and perform checks on properties using our designed custom validation attributes to obtain validation results.
///
/// Parsing functionality
///
///
private static void Analysis(List
文章评论