65.9K
CodeProject 正在变化。 阅读更多。
Home

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

starIconstarIconstarIconstarIcon
emptyStarIcon
starIcon

4.82/5 (14投票s)

2015年12月24日

CPOL

3分钟阅读

viewsIcon

27735

downloadIcon

269

.NET 提供了许多技术来使您的代码更简洁、易于阅读和维护。 扩展和属性是两个例子,它们不仅帮助开发人员比以前更简单地维护和扩展应用程序,而且还使开发人员的重复性任务不那么烦人。

引言

大多数开发人员在日常任务中都会遇到一些类似的情况和子任务。 当然,有大量的解决方案可以处理这些烦人的任务,但它们都有各自的优缺点。 本文的主要目标是展示一种使用 属性 扩展 以及 反射 来处理它们的好方法之一。 本文 **不** 适用复杂的的设计模式,其目标是简单地展示如何在基本场景中利用 .NET。 作者假定本文的读者是中级到高级开发人员,他们了解 **OOP** (**面向对象编程**) 以及 .NET 中应用程序开发的高级技术。

背景

验证和格式化对象是任何应用程序的两个基本组成部分。 通常,在一个项目中,它们必须同时解决。 开发人员通常使用一些框架和工具来处理这些问题,但在某些情况下,开发人员不允许使用第三方工具和框架,那么你想做什么? 幸运的是,您可以使用一些常用技巧来处理大多数简单场景,例如 属性 扩展

Using the Code

**注意**:此解决方案是在 Visual Studio 2015 Community Edition 中开发的,目标 .NET 4.6。 **但是**,您可以使用该项目并将目标框架更改为 .NET 2,只需进行一些小的更改,包括 C# 6 的新功能,这些功能在早期版本中不可用。 或者,您可以将文件和项目带到早期版本的 Visual Studio。

让我们谈谈项目中的简单场景。 这假设您有以下类,并且您想满足以下要求

  • 年龄 属性必须是奇数,并且介于 1 和 100 之间
  • 姓名的第一个字母必须格式化(首字母大写)
  • 全名不能在类外部赋值,它必须通过连接 NameFamilyName 来填充

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();
        }
    }
© . All rights reserved.