使用 .NET 中的扩展、属性和反射自动化日常任务






4.82/5 (14投票s)
.NET 提供了许多技术来使您的代码更简洁、易于阅读和维护。 扩展和属性是两个例子,它们不仅帮助开发人员比以前更简单地维护和扩展应用程序,而且还使开发人员的重复性任务不那么烦人。
引言
大多数开发人员在日常任务中都会遇到一些类似的情况和子任务。 当然,有大量的解决方案可以处理这些烦人的任务,但它们都有各自的优缺点。 本文的主要目标是展示一种使用 属性 和 扩展 以及 反射 来处理它们的好方法之一。 本文 **不** 适用复杂的的设计模式,其目标是简单地展示如何在基本场景中利用 .NET。 作者假定本文的读者是中级到高级开发人员,他们了解 **OOP** (**面向对象编程**) 以及 .NET 中应用程序开发的高级技术。
背景
验证和格式化对象是任何应用程序的两个基本组成部分。 通常,在一个项目中,它们必须同时解决。 开发人员通常使用一些框架和工具来处理这些问题,但在某些情况下,开发人员不允许使用第三方工具和框架,那么你想做什么? 幸运的是,您可以使用一些常用技巧来处理大多数简单场景,例如 属性 和 扩展。
Using the Code
**注意**:此解决方案是在 Visual Studio 2015 Community Edition 中开发的,目标 .NET 4.6。 **但是**,您可以使用该项目并将目标框架更改为 .NET 2,只需进行一些小的更改,包括 C# 6 的新功能,这些功能在早期版本中不可用。 或者,您可以将文件和项目带到早期版本的 Visual Studio。
让我们谈谈项目中的简单场景。 这假设您有以下类,并且您想满足以下要求
年龄
属性必须是奇数,并且介于 1 和 100 之间- 姓名的第一个字母必须格式化(首字母大写)
- 全名不能在类外部赋值,它必须通过连接
Name
和FamilyName
来填充
Person.cs
public class Person
{
public int Age { get; set; }
public string FamilyName { get; set; }
public string Name { get; set; }
public string FullName { get; private set; }
}
满足要求的其中一种方法是使用自定义属性修饰属性。 但怎么做? 让我们看一下下面的代码。 难道它不具有可读性吗?
Person.cs
public class Person
{
[RangeValidator(1, 100)]
[IsOddNumber]
public int Age { get; set; }
[ToUpper(ToUpperTypes.FirstAndAfterSpace)]
public string FamilyName { get; set; }
[ToUpper(ToUpperTypes.FirstLetter)]
public string Name { get; set; }
[AutoPupulate("Name", "FamilyName")]
public string FullName { get; private set; }
}
虽然上面的代码非常易于阅读和理解,但它什么也没做,因为我们需要实现修饰属性的属性。 让我们看看这些属性的实现。 以下代码是这些属性的实际实现。 这些属性基本上是从 属性 类派生的类。 它们可能具有构造函数和属性。
RangeValidatorAttribute.cs
[AttributeUsage(AttributeTargets.Property,AllowMultiple =true)]
public class RangeValidatorAttribute : Attribute
{
public int MaxValue { get; set; } // Maximum acceptable value
public int MinValue { get; set; } // Minimum acceptable value
public RangeValidatorAttribute(int MinValue, int MaxValue)
{
this.MaxValue = MaxValue;
this.MinValue = MinValue;
}
}
IsOddNumberAttribute.cs
[AttributeUsage(AttributeTargets.Property,AllowMultiple =true)]
public class IsOddNumberAttribute : Attribute
{
}
ToUpperAttribute.cs
public enum ToUpperTypes
{
FirstLetter,
FirstAndAfterSpace,
AllLetters
}
[AttributeUsage(AttributeTargets.Property, AllowMultiple = true)]
public class ToUpperAttribute : Attribute
{
public ToUpperTypes Type { get; set; }
public ToUpperAttribute(ToUpperTypes Type = ToUpperTypes.AllLetters)
{
this.Type = Type;
}
}
AutoPupulateAttribute.cs
[AttributeUsage(AttributeTargets.Property)]
public class AutoPupulateAttribute : Attribute
{
public string[] PropertyNames { get; set; }
public AutoPupulateAttribute(params string[] names)
{
if (names != null)
{
PropertyNames = names;
}
}
}
ValidationException.cs
public class ValidationException : Exception
{
public enum Types
{
RangeException,
OddException
}
public ValidationException(Types ExceptionType, string Message) : base(Message) { }
public ValidationException
(Types ExceptionType, string Message, Exception InnerException) :
base(Message, InnerException) { }
}
正如您所看到的,这些属性都没有做任何特别的事情。 它们只是标记您的属性的修饰符。 但问题是“我们如何自动化任务?”。 这个问题要求我们利用反射。 当我们运行应用程序时,我们可以访问包括属性在内的对象。 例如,我们可以有一系列扩展来解析对象(使用反射)并执行预定义的操作以满足之前的要求。
以下代码基本上包含一系列类的扩展,但请注意,它们是通用扩展,当它们在您的代码中被引用时,它们将全局影响您项目中的所有类。
请注意,如果属性(在这种情况下)没有被属性标记,则不会引发错误。 属性是可选的。
using System;
using System.Linq;
using System.Text;
namespace FormatingAttributes
{
public static class ClassExtnetions
{
public static T AutoPopulate(this T input) where T : class
{
var propertyInfos = input.GetType().GetProperties();
foreach (var propertyInfo in propertyInfos)
{
var customAttributes = propertyInfo.GetCustomAttributes(true);
foreach (var customAttribute in customAttributes)
{
if (!(customAttribute is AutoPupulateAttribute)) continue;
var propNamesInfo = customAttribute.GetType().GetProperty("PropertyNames");
var propNames = (string[])propNamesInfo.GetValue(customAttribute);
var result = new StringBuilder();
foreach (var propValue in propNames.Select
(propName => input.GetType().GetProperty(propName)).Select
(propinfo => propinfo.GetValue(input)))
{
result.Append(propValue);
result.Append(" ");
}
propertyInfo.SetValue(input, result.ToString());
}
}
return input;
}
public static string ConvertToString(this T input) where T : class
{
var output = new StringBuilder();
var className = input.GetType().ToString();
var properties = input.GetType().GetProperties();
output.Append($"Class Name : {className} {Environment.NewLine}");
foreach (var property in properties)
{
output.Append($"{property.Name} :
{property.GetValue(input)} {Environment.NewLine}");
}
return output.ToString();
}
public static T Format(this T input) where T : class
{
var propertyInfos = input.GetType().GetProperties();
foreach (var propertyInfo in propertyInfos)
{
var customAttributes = propertyInfo.GetCustomAttributes(true);
foreach (var customAttribute in customAttributes)
{
if (!(customAttribute is ToUpperAttribute)) continue;
var value = propertyInfo.GetValue(input).ToString();
var customAttributeType = customAttribute.GetType().GetProperty("Type");
var type = (ToUpperTypes)customAttributeType.GetValue(customAttribute);
ToUpperAttribute(ref value, type);
propertyInfo.SetValue(input, value);
}
}
return input;
}
private static void ToUpperAttribute(ref string value, ToUpperTypes type)
{
switch (type)
{
case ToUpperTypes.FirstLetter:
if (string.IsNullOrEmpty(value))
{
return;
}
value = string.Concat(char.ToUpper(value.ToCharArray()[0]).ToString(),
value.Substring(1));
break;
case ToUpperTypes.FirstAndAfterSpace:
if (string.IsNullOrEmpty(value))
{
return;
}
var result = new StringBuilder();
var splittedValues = value.Split(' ');
foreach (var splittedValue in splittedValues)
{
result.Append(string.Concat(char.ToUpper
(splittedValue.ToCharArray()[0]).ToString(),
splittedValue.Substring(1)));
result.Append(' ');
}
value = result.ToString();
break;
case ToUpperTypes.AllLetters:
value = value.ToUpper();
break;
default:
throw new ArgumentOutOfRangeException(nameof(type), type, null);
}
}
public static bool Validate(this T input)
{
var propertyInfos = input.GetType().GetProperties();
return propertyInfos.Select
(propertyInfo => Validate(input, propertyInfo.Name)).FirstOrDefault();
}
public static bool Validate(this T input, string PropertyName)
{
var propertyInfo = input.GetType().GetProperty(PropertyName);
var customAttributes = propertyInfo.GetCustomAttributes(true);
var isValid = true;
foreach (var customAttribute in customAttributes)
{
if (customAttribute is RangeValidatorAttribute)
{
var value = (int) propertyInfo.GetValue(input);
var minValueProperty = customAttribute.GetType().GetProperty("MinValue");
var maxValueProperty = customAttribute.GetType().GetProperty("MaxValue");
var minValue = (int) minValueProperty.GetValue(customAttribute);
var maxValue = (int) maxValueProperty.GetValue(customAttribute);
isValid &= RangeValidator(value, minValue, maxValue, PropertyName);
}
else if (customAttribute is IsOddNumberAttribute)
{
var value = (int) propertyInfo.GetValue(input);
if (value%2 == 0)
{
throw new ValidationException(ValidationException.Types.OddException,
$"The value of Property {PropertyName} is {value} which is not Odd!!!");
}
}
}
return isValid;
}
private static bool RangeValidator(int value, int min, int max, string PropertyName)
{
var isValid = value <= max & value >= min;
if (!isValid)
{
throw new ValidationException(ValidationException.Types.RangeException,
$"The value of property {PropertyName} is not between {min} and {max}");
}
return true;
}
}
}
正如您所看到的,我们在扩展类中具有格式化、验证和自动填充功能。 因此,在您的代码中,您可以轻松地创建一个新的 person
类的实例,并调用这些函数,因为此类在您的代码中被引用时针对所有类。
以下是一个简单的控制台应用程序,它创建一个 person
类的实例并运行这些函数。
internal class Program
{
private static void Main(string[] args)
{
var persons = new List();
persons.Add(
new Person()
{
Name = "john",
FamilyName = "smith dC",
Age = 22
}
);
persons.Add(
new Person()
{
Name = "Judy",
FamilyName = "Abbout Story",
Age = 65
}
);
foreach (var person in persons)
{
try
{
var isValid = person.Format().AutoPopulate().Validate();
Console.WriteLine(person.ToString());
}
catch (Exception ex)
{
Console.WriteLine(person.ToString());
Console.WriteLine();
if (ex is ValidationException)
{
Console.WriteLine(ex.Message);
Console.WriteLine();
}
}
}
Console.ReadLine();
}
}