关于C#自定义Attribute的应用

2023年4月20日 946点热度 3人点赞 0条评论

Attribute(特性)是C#一个比较有意思的类,通过使用Attribute注释,可以在运行时将特定内容与被注释对象进行绑定,用以约束程序行为。在自定义Attribute上过去没有尝试过,今天尝试写了个小Demo,这里做些记录分享:

举个应用场景的例子,有一个方法,动作是每天收盘后自动操作国债逆回购,国债逆回购的收市时间是3点半,也就是说这个方法执行时要在15:00-15:30这个时间段。按照一般思路,我可能要为这个功能做一个定时任务,在每天的一个固定时间执行一次,这样做当然可以,但如果我不想为这个特定功能再去做一个Job,那这个时候就可以使用自定义Attribute完成这个任务,我可以将这个逆回购的操作方法放在程序整体的大循环中,但是通过特性注释,在调度该方法前做时间段的判断,不在这个时间段内则不予执行。

下面是一个Demo,一个名叫PrinterA的类中,包含一个被自定义特性TimeRange注释,用以约束程序执行时间段的方法Print,当方法执行时间在这个时间段内时,则打印“It's ShowTime From A”。这是一个静态类,还有个PrinterB类,是需要实例化的,后面再讲。以上如下所示:

static class PrinterA
{
    [TimeRange("14:30","15:30")]
    public static void Print()
    {
        Console.WriteLine("It's ShowTime From A");
    }
}

关于自定义Attribute的定义,这里不赘述,网上的资料很多,这里重点讲解一个自定义Attribute的具体实现,TimeRange类的代码如下,作用域为方法,包含两个基本的属性Min和Max,用于储存时间段信息:

[AttributeUsage(AttributeTargets.Method)]
class TimeRangeAttribute : Attribute
{
    public DateTime Min { get; }
    public DateTime Max { get; }
    public TimeRangeAttribute(string min, string max)
    {
        Min = DateTime.Parse(min);
        Max = DateTime.Parse(max);
    }
}

接下来就是通过反射机制使用这个自定义Attribute,步骤是通过typeof运算符获取自定义类的类型,通过类型遍历或者查找这个类型下具有某种CustomAttribute的方法,获取这个自定义Attribute,通过自定义Attribute内定义的一些属性或方法进行逻辑判断后调用被注释的方法,实现约束方法行为的目的。代码如下:

Type printerTypeA = typeof(PrinterA);
var methodsA = printerTypeA.GetMethods();
var printMethodA = methodsA.Where(m => m.Name == "Print").FirstOrDefault();
if (printMethodA != null)
{
    Attribute attribute = Attribute.GetCustomAttribute(printMethodA, typeof(TimeRangeAttribute));
    var timeRange = attribute as TimeRangeAttribute;
    if (timeRange.Min < DateTime.Now  && DateTime.Now < timeRange.Max)
    {
        printMethodA.Invoke(null, null);
    }
}

注意上面这个Invoke方法,两个入参均为Null,第一个入参是方法对应的对象实体,因为用静态方法演示,所以传null,第二个参数是方法参数的[],因为Print方法无参,故也传null。现在,通过TimeRange特性已经可以约束Print方法的运行时间了,如果当前时间在给定时间段内,则打印“It's ShowTime From A”。如下图:

接下来的演示是需要实例化对象及含参的方法调用,定义类为PrinterB,入参是一个字符串words,当方法执行时间在[TimeRange("11:30", "15:30")]这个范围内,则会打印$"It's ShowTime From B ---- {words}",代码如下,可以看到实例和参数被传入了Invoke。

class PrinterB
{
    [TimeRange("11:30", "15:30")]
    public void Print(string words)
    {
        Console.WriteLine($"It's ShowTime From B ---- {words}");
    }
}
PrinterB b = new();

Type printerTypeB = typeof(PrinterB);
var methodsB = printerTypeB.GetMethods();
var printMethodB = methodsB.Where(m => m.Name == "Print").FirstOrDefault();
if (printMethodB != null)
{
    Attribute attribute = Attribute.GetCustomAttribute(printMethodB, typeof(TimeRangeAttribute));
    var timeRange = attribute as TimeRangeAttribute;
    if (timeRange.Min < DateTime.Now && DateTime.Now < timeRange.Max)
    {
        printMethodB.Invoke(b,new object[] { "Let's move!"});
    }
}

效果在上一个截图内也能看到,It's ShowTime From B --- Let's move!就是PrinterB的执行结果。如果当前时间不在时间段内,则控制台没有打印输出。上面的代码块可以封装在一个方法内,作为Print方法的wapper,调用起来会更加方便。

总结,Attribute用起来给人一种和Python装饰器很像的感觉,本质上都是对代码元素的warpper,对于局部的应用需求,其实这么做不太合适,完全可以用常规代码实现同样的功能,但是如果将特性应用在同质但作用域很广的方面,优势就体现出来了,第一是代码的可读性更强,第二是明显降低重复代码的使用,一个最典型的应用就是.net Core WebAPI Controller的[Route("xxxx")] [HttpPost] [Authorize(Roles = "xxx")]。

QThinker

前地产从业者,假装是个程序员,热爱编程与交易 自研QThinker量化交易框架

文章评论