c# Attribute 使用约束
Attribute是C#中一个广泛使用的特性,通过使用Attribute对程序集中的类型进行标记,并通过Reflection实现一些特殊的需求,同时Attribute也可以在编译时储存一些静态信息。但是在使用Attribute时也有一些限制。
问题的提出
目前工作中主要是编写与维护一套在Unity中使用的Continuous Integration 框架。在这套框架中大量使用了Attribute来对集成的第三方SDK进行标记,来进行一些处理。最初的做法是一些SDK,通过约定静态方法,在CI的其他阶段进行调用
1 | public class SDKA :SDKBase |
通过反射调用该方法的代码如下
1 | public void SDKsModifyConfig() |
对于这种调用方式,不太好的地方是参与开发SDK的人员必须约定用于反射的方法名,以及该方法的参数。每次接入SDK编写一个SDK的类型都需重新编写,容易造成错误(error-prone)。
如果我们使用在基类定义方法,在子类覆盖的形式,这样可以避免发生错误,同时IDE的自动完成可以帮助我们完成代码。但是这样在反射的时候就需要有类的对象,而其实我们所需要的方法是针对与类的静态方法。
1 | public class SDKBase |
使用Attribute
另一中解决方式是将这些方法定义在Attribute中。
1 | public class CustomSDKAttribute :Attribute |
将需要反射的方法放在Attribute的定义中后我们在反射了类获得Attribute之后可以直接调用该方法,不需要再反射方法了。
对于不同的SDK的类型需要不同的实现,我们可以定义一个Attribute的基类,通过Attribute的子类来实现
1 | [ ] |
Attribute的约束
我们知道在定义Attribute的时候可以给Attribute添加约束AttributeUsage
[AttributeUsage(AttributeTargets.Class,AllowMultiple =false)]
但是只能通过AttributeUsage约束Attribute添加到程序集对象的类型,并不能约束Attribute修饰的对象的继承链等其他特性。也就是不能使用泛型和where关键字。这样就会导致我们给SDK定义的Attribute可以被任意使用其他类型的对象上,这是我们不愿意看到的。
由于我们是使用反射来查找所有标记为该Attribute的类型,缺少了类型的继承链约束后,无疑会造成一些隐患。但是由于C#语言本身不提供Attribute的类型约束,在google之后通过一个非常trick的方式解决了Attribute的约束问题。
我们将Attribute的定义声明在需要约束的基类中,并且使用protected
修饰符
1 | public class SDKBase |
这样CustomSDKAttribute就只能在SDKBase的子类进行访问,下面的代码便会产生编译错误。
1 | [//Error:未找到类型 ] |
这样就我们就相当与声明了where T:SDKBASE
,对Attribute的使用进行了约束。
总结
使用Attribute定义的修饰符将Attribute修饰的对象加上约束。
对于将需要反射调用的方法存放在Attribute中还有一个好处是。在Unity开发中,如果我们只在Editor层面进行对这些方法的反射和调用,可以将该Attribute限定在编辑器环境,防止这部分代码污染最后编译的版本。
[System.Diagnostics.Conditional("UNITY_EDITOR")]
但是如果使用最初的反射方法的方式,这需要给所有的反射方法加上宏的限定,同时不一定可以使用System.Diagnostics.Conditional
这个Attribute进行宏的约束。Conditional要求限定的方法必须是void的返回值。讲方法定义在Attribute中直接移除Attribute不受该约束限制