在开发库以及框架的时候,持续维护会遇到兼容性的问题,如发现了旧版本有一些接口设计不合理,或者方法命名不符合逻辑等。此时如果直接更改原有的属性名或方法名甚至类名等,将会导致上层业务的开发者们在升级库之后构建不通过,因为缺少对应的方法。此时就需要上层业务的开发者们查阅文档才能了解如何应对升级之后带来的变动
在 dotnet 里面,可以使用 Obsolete 这个编译器分析辅助特性,给某个成员,如类和属性和方法事件等标记过时。这个 Obsolete 特性可以用来辅助库和框架开发者,在发生 API 变动时,可以保持兼容,或者提醒上层业务的开发者们如何应对
进行不兼容代码层 API 的变动,包括类名、属性名、方法名等所有公开的命名变更,以及命名空间的变更。还有删除成员带来的不兼容更改
而 Obsolete 特性标记,可以用来告知上层业务的开发者们当前成员已过时,同时在 Obsolete 特性标记上允许传入字符串,用于告诉上层业务的开发者们应当如何应对此变更。利用好这个特性,就可以让库和框架在变动 API 时,更好的保持兼容性,以及对上层业务的开发者们更加友好
用一个简单的例子说明 Obsolete 特性标记对 API 兼容性的用法
如一开始我创建了一个类 Foo 类,在这里面添加了 F1 属性,代码如下
class Foo
{
public int F1 { set; get; }
}
而随后,如需要做一个 API 命名变动的更改,如发现 F1 这个命名不清真,需要修改为 F2 这个命名。如果不做任何的兼容性处理,意味着更改的代码如下
class Foo
{
public int F2 { set; get; }
}
此时上层的开发者将会发现在升级了库或框架之后,将会因为找不到 Foo.F1
属性而构建不通过。有趣的是,上层业务的开发者们也不知道可以如何解决此构建不通过的问题
而如果依然保留 F1 这个属性,同时在属性上面标记 Obsolete 特性,告诉上层业务的开发者们应该如何更改,如以下代码
class Foo
{
[EditorBrowsable(EditorBrowsableState.Never)]
[Obsolete("请使用 F2 代替")]
public int F1
{
set => F2 = value;
get => F2;
}
public int F2 { set; get; }
}
那么此时的上层业务的开发者们在升级完成了库或框架之后,依然可以构建成功,只是存在一个警告。同时警告里面也告诉了上层业务的开发者们 请使用 F2 代替
这个解决方法
上面代码中,使用 F1
属性将不会占用任何内存量,因为整个 F1 属性都是对 F2 进行封装,在运行时优化里面又会进行内联,整体对性能影响将会特别小
通过在属性上面标记 Obsolete 特性的方式,可以做到在更改命名以及挪动方法和属性的时候,依然可以让上层业务能构建通过,同时也能告诉上层业务开发者们的更改方式
在上面代码中,依然添加了 EditorBrowsable
特性,通过这个特性可以告诉 VS 等 IDE 当前这个成员不应该出现在代码建议中。换句话说标记了 EditorBrowsable
特性将可以让开发者在使用到 Foo 类的时候,智能提示不会显示 F1
属性。通过标记 EditorBrowsable
特性可以保持 API 的整洁,对上层业务的开发者来说,不会因为整个类或命名空间里面充满了 Obsolete 的成员,而觉得很乱
而有一些方法因为之前版本设计的不合理,导致了用法上可能会出现坑,但是新版本没有一个能做到完全兼容的方案。此时也可以通过 Obsolete 特性,在特性的字符串里面告诉上层业务开发者们这个问题
而另一部分是在新版本上完全无法兼容的逻辑,例如更改了机制等,此时整个属性或方法等,都是无法实现的。可以保存一个空属性或方法等,同时标记 Obsolete 特性,只不过此时需要再添加一个参数,设置构建不通过,如以下代码
[EditorBrowsable(EditorBrowsableState.Never)]
[Obsolete("请使用 F2 代替", error: true)]
public int F1
{
// 无法实现
set;
// 无法实现
get;
}
使用 Obsolete 特性的做法对比直接删除属性或方法等的做法,优势在于可以让上层业务开发者们看到明确的放在 Obsolete 特性里面的字符串提示,可以解决很大部分的迁移成本。如上面的例子,在升级完成了库或框架之后上层业务开发者们看到构建不通过的提示内容,就可以自助完成迁移,而不需要再去寻找文档等
另一个话题是,通过本文的例子也可以让大家感受到公开的是属性而不是字段的一个优势。加入上面代码公开的是字段而不是属性,那么一些兼容性做法就非常难实现了,无论如何使用字段就一定占用了内存空间,此时的字段更新等行为都非常坑。而作为属性,是可以在内存上不存在的,只是公开出设置或获取方法,可以做到兼容
需要小心的一点是标记了 Obsolete 特性仅仅是编译器或 IDE 层面上的作用,而不是二进制 dll 或 exe 的作用,意味着如果在引用了旧版本的库或框架实现的应用,直接替换到新版本标记了 Obsolete 特性同时设置构建不通过的新库或框架的时候,此时的应用依然可以运行,只是运行过程中也许会出现不兼容的问题
也就是说 Obsolete 特性是给开发者用的,用于在写代码的时候的提示而已。即使在 Obsolete 特性同时设置构建不通过,也可以通过禁用错误提示的方法强行构建通过
本文代码放在 github 欢迎小伙伴访问
本文会经常更新,请阅读原文: https://dotnet-campus.github.io//post/dotnet-%E4%BD%BF%E7%94%A8-Obsolete-%E7%89%B9%E6%80%A7%E6%A0%87%E8%AE%B0%E6%88%90%E5%91%98%E8%BF%87%E6%97%B6%E4%BF%9D%E6%8C%81%E5%BA%93%E5%92%8C%E6%A1%86%E6%9E%B6%E7%9A%84%E5%85%BC%E5%AE%B9%E6%80%A7.html ,以避免陈旧错误知识的误导,同时有更好的阅读体验。
本作品采用 知识共享署名-非商业性使用-相同方式共享 4.0 国际许可协议 进行许可。欢迎转载、使用、重新发布,但务必保留文章署名 lindexi (包含链接: https://dotnet-campus.github.io/ ),不得用于商业目的,基于本文修改后的作品务必以相同的许可发布。如有任何疑问,请 与我联系 。