栏目分类:
子分类:
返回
名师互学网用户登录
快速导航关闭
当前搜索
当前分类
子分类
实用工具
热门搜索
名师互学网 > IT > 软件开发 > 后端开发 > .Net

详谈C#特性及应用

.Net 更新时间: 发布时间: IT归档 最新发布 模块sitemap 名妆网 法律咨询 聚返吧 英语巴士网 伯小乐 网商动力

详谈C#特性及应用

特性与反射息息相关,学习特性前,建议学习了解下这篇博客:C# 反射

一、特性直观感受

每一个特性都会带来对应得功能(其实这是个错觉)。

如下图,两个特性使我们经常在开发中见到的,Obsolete 可以影响到编译器发出警告,Serializable 可以序列化和反序列化影响程序运行

二、声明,定义

特性使用中括号声明,特性是一个类且必须直接或者间接继承 Attribute。

看了 Obsolete 与 Serializable 特性使用后,F12 查看一下源码,可以看到这两个特性都是一个类并且继承 Attribute


三、IL 语言特性

特性添加后,编译会在元素内部产生 IL,但我们没有办法直接使用的,而且在 metadata 里面会有记录。

这里定义了一个 CustomAttribute 特性,且声明到了 Product 类与其成员上,然后进行编译一下
使用反编译工具,在 bin 目录查看程序集(dll or exe),可以看到与我们写得 C# 源代码一样

点开 Product 类,切换到 IL 语言,可一看到其成员,且在 C# 源代码标记过的地方,都编译成了特性的构造函数


四、特性本身无意义

特性本身是没有用没有意义的。这句话,我们从程序运行也可以验证,下面是个一个简单的例子

这里定义了 CustomAttribute ,且在构造函数里面打印了一句话,在实例化对象是会进行提示

public class CustomAttribute: Attribute
{
    public CustomAttribute()
    {
        Console.WriteLine("CustomAttribute 构造函数");
    }
}

再定义一个 Product 类,其被 CustomAttribute 声明

[Custom]
public class Product
{
    [Custom]
    public int Id { get; set; }

    [Custom]
    public int GetId()
    {
        return Id;
    }
}

这里在 Main 函数,对类进行实例化,对属性赋值,且调用方法

static void Main(string[] args)
{
    Product product= new Product() { Id=1};
    Console.WriteLine(product.GetId());

    Console.ReadKey();
}

启动,可以看到,程序正常运行,且没有打印 Custom 特性相关的提示信息,所以特性本身是没有意义的。


五、特性 API

特性本身是没有用的,程序运行过程中,我们能找到特性,而且也能应用一下。特性可以声明在类、方法、字段、属性、参数、返回值上,程序运行时可以获取到特性声明的信息。

首先定义 CustomAttribute 特性,且包含 Desc 属性和两个构造函数

public class CustomAttribute: Attribute 
{
    public string Desc { get; set; }

    public CustomAttribute()
    {
        Console.WriteLine("CustomAttribute 构造函数");
    }

    public CustomAttribute(string desc)
    {
        Console.WriteLine("CustomAttribute 有参构造函数");
        Desc = desc;
    }
}

定义 Product 类,在其本身与成员及方法返回值全部声明 CustomAttribute

[Custom("我在类上")]
public class Product
{
    [Custom("我在字段上")]
    public string name;

    [Custom("我在属性上")]
    public string Name { get; set; }

    [Custom("我在方法上")]
    [return:Custom("我在返回值上")]
    public string GetName([Custom("我在参数上")] int id)
    {
        return Name;
    }
}

在 Main 函数,写了一些特性实例化与获取信息的一些 API

static void Main(string[] args)
{
    Product product= new Product() { };

    Type type = typeof(Product);
    if (type.IsDefined(typeof(CustomAttribute), true))
    {
        CustomAttribute customAttribute = (CustomAttribute)type.GetCustomAttribute(typeof(CustomAttribute),true);
        Console.WriteLine(customAttribute.Desc);
    }

    MethodInfo method = type.GetMethod("GetName");
    if (method.IsDefined(typeof(CustomAttribute), true))
    {
        CustomAttribute customAttribute = (CustomAttribute)method.GetCustomAttribute(typeof(CustomAttribute), true);
        Console.WriteLine(customAttribute.Desc);
    }

    ParameterInfo parameter = method.GetParameters()[0];
    if (parameter.IsDefined(typeof(CustomAttribute), true))
    {
        CustomAttribute customAttribute = (CustomAttribute)parameter.GetCustomAttributes(typeof(CustomAttribute), true)[0];
        Console.WriteLine(customAttribute.Desc);
    }

    ParameterInfo returnParameter = method.ReturnParameter;
    if (returnParameter.IsDefined(typeof(CustomAttribute), true))
    {
        CustomAttribute customAttribute = (CustomAttribute)returnParameter.GetCustomAttribute(typeof(CustomAttribute), true);
        Console.WriteLine(customAttribute.Desc);
    }

    PropertyInfo property = type.GetProperty("Name");
    if (property.IsDefined(typeof(CustomAttribute), true))
    {
        CustomAttribute customAttribute = (CustomAttribute)property.GetCustomAttribute(typeof(CustomAttribute), true);
        Console.WriteLine(customAttribute.Desc);
    }

    FieldInfo field = type.GetField("name");
    if (field.IsDefined(typeof(CustomAttribute), true))
    {
        CustomAttribute customAttribute = (CustomAttribute)field.GetCustomAttribute(typeof(CustomAttribute), true);
        Console.WriteLine(customAttribute.Desc);
    }

    Console.ReadKey();
}

启动,可以看到在成员运行时,拿取到声明特性的各种信息

CustomAttribute customAttribute = (CustomAttribute)field.GetCustomAttribute(typeof(CustomAttribute), true); // 会实例化特性


六、特性实际应用

没有破坏封装的前提下,可以额外的加点消息与行为。

说到这可能有的同学就问,上面说了那么多,在我们平时开发时有什么实际用处吗?答案是:有的,接下来使用实例进行讲解

6.1、添加额外消息

场景:分销系统,订单状态开发时通常喜欢使用枚举进行定义,但在 UI 展示其对于描述时有哪些做法呢?

首先,订单状态枚举定义,这里进行简化

public enum OrderStatus
{
    /// 
    /// 暂存
    /// 
    TemporaryStorage = 0,
    /// 
    /// 未审批
    /// 
    NotApproved = 10,
    /// 
    /// 已审批
    /// 
    Approved = 20

    // 还有其他许多状态,这里不进行举例
}

第一种:手动维护,拼凑(还会有其他类似的写法,这里不进行一一举例)

列表, 这里有的开发时写了一个类似 K-V 的集合,分表对值与描述进行了,平凑返回给 UI ,进行渲染 select 或者 list

public static Dictionary GetOrderStatusList()
{
    Dictionary orderStatus = new Dictionary();
    orderStatus.Add(OrderStatus.Approved.GetHashCode(), "暂存");
    orderStatus.Add(OrderStatus.Approved.GetHashCode(), "未审批");
    orderStatus.Add(OrderStatus.Approved.GetHashCode(), "已审批");
    // 未来扩展状态流,都得手动进行添加

    return orderStatus;
}

表单,面对数据报表,单个状态对于的描述,通常使用一串的 if 进行获取

public static string GetOrderStatusRemark(OrderStatus orderStatus)
{
    if (orderStatus == OrderStatus.Approved)
    {
        return "暂存";
    }
    else if(orderStatus == OrderStatus.Approved)
    {
        return "已审批";
    }
    else if (orderStatus == OrderStatus.NotApproved)
    {
        return "未审批";
    }
    // 未来扩展状态流,都得手动进行添加

    return orderStatus.ToString();
}

特性方式:提高开发效率,且公用,易于维护,易于编码

首先定义一个 RemarkAttribute 特性,使用 _Remark 承载我们的描述信息

public class RemarkAttribute : Attribute
{
    private string _Remark { get; set; }

    public RemarkAttribute(string remark)
    {
        _Remark = remark;
    }

    public string GetRemark()
    {
        return _Remark;
    }
}

在状态枚举成员打上特性,且输入每个状态的描述信息

public enum OrderStatus
{
    /// 
    /// 暂存
    /// 
    [Remark("暂存")]
    TemporaryStorage = 0,
    /// 
    /// 未审批
    /// 
    [Remark("未审批")]
    NotApproved = 10,
    /// 
    /// 已审批
    /// 
    [Remark("已审批")]
    Approved = 20

    // 还有其他许多状态,这里不进行举例
}

创建一个状态枚举的扩张类,写一个 GetRemark 扩张方法,用于取当前状态成员标记特性的描述信息

public static class OrderStatusExtension
{
    public static string GetRemark(this OrderStatus val)
    {
        Type type = val.GetType();
        FieldInfo field = type.GetField(val.ToString());
        if (field.IsDefined(typeof(RemarkAttribute),true))
        {
            RemarkAttribute remarkAttribute = (RemarkAttribute)field.GetCustomAttribute(typeof(RemarkAttribute), true);
            return remarkAttribute.GetRemark();
        }
        return val.ToString();
    }
}

启动,可以看到,成功从 Remark 特性成员 _Remark 字段获取到 NotApproved 状态的描述信息

static void Main(string[] args)
{

    OrderStatus orderStatus = OrderStatus.NotApproved;
    Console.WriteLine(orderStatus.GetRemark());

    Console.ReadKey();
}


延展:平时开发程序,model 对于的表格或者表单的描述也可用此方法

6.2、添加额外行为

场景:表单提交,数据效验

public class Personal
{
    public string Name { get; set; }

    public string Address { get; set; }

    public long  QQ { get; set; }

    // 还有许多其他字段
}

第一种:这种写法最简单的,通常在 mvc 的 action 方法里面进行效验,在多个 action 中这种会造成代码重复,且维护困难

Personal personal= new Personal() { QQ = 1};
if (personal.QQ > 10001 && personal.QQ < 999999999999)
{

}
 // 还有更多 else if () 对其他进行效验
 

第二种:由于第一种产生的问题,有的小伙伴会将验证逻辑放到 get set 访问器中,这种造成了职责不分明,实体本身是承载信息的,不需要存在业务逻辑

public class Personal
{
    public string Name { get; set; }

    public string Address { get; set; }

    public long QQ
    {
        get
        {

            return this.QQ;
        }
        set
        {
            if (value > 10001 && value < 999999999999)
            {
                this.QQ = value;
            }
            else
            {
                throw new Exception("QQ号不合法");
            }
        }
    }

    // 还有许多其他字段
}

特性方式:由于以上两种产生的问题,我们可以使用特性进行处理,易于维护、易于编码、易于公用

定义一个 LongAttribute 特性,其中有个最小、最大字段,且有一个 Validate 方法用于处理效验逻辑

public class LongAttribute : Attribute
{
    private long _Min { get; set; }
    private long _Max { get; set; }

    public LongAttribute(long min,long max)
    {
        _Min = min;
        _Max = max;
    }

    public bool Validate(object val)
    {
        if (val != null && !string.IsNullOrEmpty(val.ToString()))
        {
            if (long.TryParse(val.ToString(),out long lResult))
            {
                if (lResult > _Min && lResult < _Max)
                {
                    return true;
                }
            }
        }
        return false;
    }
}

定义一个实体类,有 Name、Address 、QQ 三个字段(实际比这多),在 QQ 字段标记特性 Long 并定义 min max

public class Personal
{
    public string Name { get; set; }

    public string Address { get; set; }

    [Long(1001, 99999999999)]
    public long QQ { get; set; }

    // 还有许多其他字段
}

定义一个 Validate 的扩张类与扩张方法,其作用是取得对象 Type 获取 LongAttribute 并调用其 Validate 进行业务逻辑效验

public static class ValidateExtension
{
    public static bool Validate(this object val)
    {
        Type type= val.GetType();
        foreach (var prop in type.GetProperties())
        {
            if (prop.IsDefined(typeof(LongAttribute),true))
            {
                LongAttribute longAttribute = (LongAttribute)prop.GetCustomAttribute(typeof(LongAttribute),true);
                if(!longAttribute.Validate(prop.GetValue(val)))
                {
                	return  false;
                }            
             }
        }
        return true;
    }
}

这里,分别实例化两个 Personal ,并对 QQ 字段赋予不合法与合法的值

static void Main(string[] args)
{
    Personal personal= new Personal() { QQ = 1};
    Console.WriteLine(personal.Validate());
    
    Personal personal1= new Personal() { QQ = 100000};
    Console.WriteLine(personal1.Validate());

    Console.ReadKey();
}

启动,可以看到对效验的结果,分别返回了 false true 成功的进行了效验

有人要问只验证个 QQ 有啥意义?且慢,我们还可以多名字等其他字段进行验证,接着我们对 name 字段进行效验

定义一个 LengthAttribute 特性,定义 min max 两个成员,Validate 方法进行业务逻辑效验

public class LengthAttribute : Attribute
{
    private long _Min { get; set; }
    private long _Max { get; set; }

    public LengthAttribute(long min, long max)
    {
        _Min = min;
        _Max = max;
    }

    public bool Validate(object val)
    {
        if (val != null && !string.IsNullOrEmpty(val.ToString()))
        {
            if (val.ToString().Length > _Min && val.ToString().Length < _Max)
            {
                return true;
            }
        }
        return false;
    }
}

在 name 字段打上 Length 特性,且定义 min = 1,max =10

public class Personal
{
    [Length(1, 10)]
    public string Name { get; set; }

    public string Address { get; set; }

    [Long(1001, 99999999999)]
    public long QQ { get; set; }

    // 还有许多其他字段
}

在 ValidateExtension 类 Validate 方法添加一个 LengthAttribute 特性的检查

public static class ValidateExtension
{
    public static bool Validate(this object val)
    {
        Type type= val.GetType();
        foreach (var prop in type.GetProperties())
        {
            if (prop.IsDefined(typeof(LongAttribute),true))
            {
                LongAttribute longAttribute = (LongAttribute)prop.GetCustomAttribute(typeof(LongAttribute),true);
                if(!longAttribute.Validate(prop.GetValue(val)))
                {
                	return false;
                }
            }
            if (prop.IsDefined(typeof(LengthAttribute), true))
            {
                LengthAttribute longAttribute = (LengthAttribute)prop.GetCustomAttribute(typeof(LengthAttribute), true);
                if(!longAttribute.Validate(prop.GetValue(val)))
                {
                	return false;
                }
            }
        }
        return true;
    }
}

实例化 Personal ,对 name 字段进行赋值

static void Main(string[] args)
{

    Personal personal= new Personal() { Name = "张三"};
    Console.WriteLine(personal.Validate());

    Personal personal1= new Personal() { Name = "张"};
    Console.WriteLine(personal1.Validate());

    Console.ReadKey();
}

启动,可以看到,根据标记的特性正确的进行了检测

抽象

public class AbstractValidateAttribute : Attribute
{
    public virtual bool Validate(object val) => false;
}

public class LongAttribute : AbstractValidateAttribute
{
    private long _Min { get; set; }
    private long _Max { get; set; }

    public LongAttribute(long min, long max)
        {
            _Min = min;
            _Max = max;
        }

    public override bool Validate(object val)
        {
            if (val != null && !string.IsNullOrEmpty(val.ToString()))
            {
                if (long.TryParse(val.ToString(), out long lResult))
                {
                    if (lResult > _Min && lResult < _Max)
                    {
                        return true;
                    }
                }
            }
            return false;
        }
}



public class LengthAttribute : AbstractValidateAttribute
{
    private long _Min { get; set; }
    private long _Max { get; set; }

    public LengthAttribute(long min, long max)
        {
            _Min = min;
            _Max = max;
        }

    public override bool Validate(object val)
    {
        if (val != null && !string.IsNullOrEmpty(val.ToString()))
        {
            if (val.ToString().Length > _Min && val.ToString().Length < _Max)
            {
                return true;
            }
        }
        return false;
    }
}

static void Main(string[] args)
{
    Personal personal= new Personal() { Name = "张",QQ = 9999999};
    Console.WriteLine(personal.Validate());

    Personal personal1 = new Personal() { Name = "张三", QQ = 1 };
    Console.WriteLine(personal1.Validate());

    Personal personal2 = new Personal() { Name = "张三", QQ = 9999999 };
    Console.WriteLine(personal2.Validate());

    Console.ReadKey();
}

转载请注明:文章转载自 www.mshxw.com
本文地址:https://www.mshxw.com/it/984573.html
我们一直用心在做
关于我们 文章归档 网站地图 联系我们

版权所有 (c)2021-2022 MSHXW.COM

ICP备案号:晋ICP备2021003244-6号