C#的Attribute和Typescript的装饰器之比较

2022-07-27,,

概要

C#的Attribute和TypeScript装饰器都可以实现AOP,以达到优化代码结构的作用。两者都可以在不修改原有代码的基础让,添加新的功能到已有类中,但是却采用了不同的实现方式。本文以一个字符串最大长度的限定的例子,来比较二者的实现方式。

概念比较

在C#中,通过定义一个类,该类继承自Attribute类。通过Attribute的子类实现对类,类中的属性,方法,方法参数等的装饰。C#的编译器会先实例化该子类,再实例化该类装饰的内容。这就决定了在Attribute子类内部,是无法访问到被装饰的内容,所以Attribute子类往往是和反射结合在一起使用,作为反射的路标。通过反射,定位被装饰的内容,以实现Attribute子类的功能。

在Typescript中,装饰器是一个函数,编译器将被装饰的内容作为参数,注入到该函数的参数中,这就决定了在TypeScript的装饰器中,可以访问到被装饰的内容。也就是说可以通过装饰器,扩展原有类的内容,以实现装饰器定义的功能。

实例比较

通过在C#和Typescript中定义一个限定类中属性长度的修饰器,来比较两者的实现方式。

C#Attribute子类的实例

[AttributeUsage(AttributeTargets.Property, AllowMultiple = true, Inherited = true)]
public class MaxLengthAttribute: Attribute
{

	private int MaxLength {get;set;} = 10;
	public MaxLengthAttribute(int maxLength = 10){
		Console.WriteLine("MaxLengthAttribute");
		this.MaxLength = maxLength;
	}
	public bool checkMaxLength(string prop, string str){
		if (str == null){
			Console.WriteLine("Property {prop} is null");
			return false;
		}
		if (str.Length > MaxLength){
			Console.WriteLine($"Failed to update property {prop}. Its maximum length cannot exceed {this.MaxLength}.");
			return false;
		}
		return true;
	}
}
  • 限定该类只能修饰类中属性
  • 类中属性默认最大长度为10个字符,可以通过参数默认值实现定制。
  • checkMaxLength方法定义了检查方式,如果类中的属性长度大于指定值,报错。

Attribute子类的应用

using System;
using System.Reflection;	
using System.Linq;
public class Program
{
	public static void Main()
	{
		Engine engine = new Engine();
		engine.Vendor = "AVAVAAAA";
		engine.Type = "AVAVAAAAAVAVAAAAAVAVAAAAAVAVAAAAAVAVAAAA";
		var engineType = typeof(Engine);
		var properties = engineType.GetProperties();
		foreach (var property in properties){
			if (property.IsDefined(typeof(MaxLengthAttribute))){
				var maxlength = property.GetCustomAttributes<MaxLengthAttribute>().First();	
				maxlength.checkMaxLength(property.Name, (string)property.GetValue(engine));
			}
 
		}
	}
}
public class Engine {
	public Engine(){
		Console.WriteLine("Engine");
	}
	[MaxLength(5)]
	public string Vendor {get;set;}
	[MaxLength]
	public string Type {get;set;}
}
  • 自定义发动机类,该类有两个属性,供应商和型号。其中供应商不能超过5个字符,型号不能超过10个。
  • 通过反射找到被MaxLength标识的Engine对象的供应商和型号属性,MaxLength起到了路标的作用。
  • 通过反射调用Attribute子类对象的checkMaxLength,检查属性字符串的长度。
  • 运行结果如下:

    从结果上可以看出如下:

  1. MaxLengthAttribute子类的实例化先于被装饰的内容Engine类的实例化。
  2. MaxLengthAttribute子类每次使用,编译器会实例化一个新的对象。

Typescript装饰器的实例

const maxLength = (max: number = 10) => (target: any, name: any) => {
  const key = "_temp" + new Date().getTime();
  Object.defineProperty(target, name, {
    get: function () {
      return this[key];
    },
    set: function (val) {
      if (val == null) {
        console.log(`Property ${name} is null.`);
        return;
      }
      if (val.length > max) {
        console.log(
          `Failed to update property ${name}. Its maximum length cannot exceed ${max}.`
        );
        return;
      }
      this[key] = val;
    },
  });
};
  • 因为要传递参数来限制最大允许的字符数,所以采用工厂方式定义该装饰器函数maxLength。
  • 采用defineProperty实现数据劫持,一旦赋值超过规定长度,则赋值失败。
  • 因为要支持多个属性的长度检查,所以通过new Date().getTime()来区分每个属性,以配合defineProperty的使用。

Typescript装饰器的使用


class Engine {
  @maxLength(5)
  public Vendor: string | undefined;
  @maxLength()
  public Type: string | undefined;
}

let p: Engine = new Engine();
p.Vendor = "1sssssssssssssssssssssssss";
p.Type = "2ssssssssssssssssssssssssssss";

通过tsc命令编译后,生成js代码并引入到html中,执行结果如下:

结论

Attribute子类在.net的各种框架中已经大量使用,TypeScript的装饰器已经成为ES7的必备内容。虽然收到本身设计的限制,二者的实现方式并不同,但是却可以实现相同的功能,达到基于AOP编程的目标。

本文地址:https://blog.csdn.net/weixin_43263355/article/details/110137016

《C#的Attribute和Typescript的装饰器之比较.doc》

下载本文的Word格式文档,以方便收藏与打印。