Home

Awesome

DirectRetrieveAttribute

GitHub last commit GitHub package.json version openupm

为什么有用

使用额外的全局元数据而不是反射遍历来实现Attribute获取。(并且自动preserve标记的类型)

可以满足以下三大常见需求:

参考 基准测试结果

快速开始

安装

via Git URL

Package Manager通过git url安装: https://github.com/labbbirder/DirectRetrieveAttribute.git

via OpenUPM

openupm add com.bbbirder.directattribute

检索Attribute

using com.bbbirder;

//自定义Attribute继承DirectRetrieveAttribute
class FooAttribute:DirectRetrieveAttribute 
{
    public string title { get; private set; }
    
    public FooAttribute(string title)
    {
        this.title = title;
        // dont access targetType here, it will always be null
    }
    
    public override void OnReceiveTarget()
    {
        // targetType is available here ^_^
    }
}

//作出标记
[Foo("whoami")]
class Player
{
    [Foo("Hello")]
    void Salute()
    {

    }
}

//检索当前Domain下所有FooAttribute
FooAttribute[] attributes = Retriever.GetAllAttributes<FooAttribute>(); 
foreach(var attr in attributes)
{
    print($"{attr.targetType} {attr.targetMember?.Name} {attr.title}"); 
}
// output: 
//    Player null whoami
//    Player Salute Hello

自定义Attribute需要继承DirectRetrieveAttributeRetriever.GetAllAttributes会在检索过程中赋值目标类型targetType和成员targetMember并调用OnReceiveTarget()通知赋值完成。

检索子类型

using com.bbbirder;

// 定义几个类型
public class Battler:IDirectRetrieve
{

}

public class Hero:Battler
{

}

public class Titant:Hero
{

}

// 获取Domain下所有Battler子类
Type[] types = Retriever.GetAllSubtypes(typeof(Battler));
foreach(var type in types){
    print($"{type.Name}");
}
// output:
//     Hero
//     Titant

接口的实现检索与上例类似。

传统方式对比

传统方式获取Attributes列表

在传统方式下,我们可能会这样获取所有自定义属性:

AppDomain.CurrentDomain.GetAssemblies()
    .SelectMany(a=>a.GetTypes())
    .SelectMany(a=>a.GetMembers()
        .SelectMany(m=>m.GetCustomAttributes(attrType)))
    .ToArray();

众所周知,反射方法效率低,并且会产生大量GC。如果你是一个Package Developer,在你开发的众多Package中可能有不少需要检索Attribute列表的情况,这无疑是灾难性的。(参考基准测试结果

基准测试结果

benchmark

可以得出结论如下:

运行时间内存消耗每用户代码体积增长
传统方式100%100%开销线性增加
Direct<5%<1%开销几乎不变

基准测试源码

值得一提的是,以上的结果还只是基于理论的测试,在实际应用中,DirectRetrieveAttribute因考虑到检索通常集中地发生在开始运行阶段,因此在内部做了一点小小的优化:使用WeakReference缓冲了一下中间计算。结果是,Direct方式开销小到无法察觉(见下图)! benchmark

实际差异如下:

运行时间内存消耗每用户代码体积增长
传统方式100%100%开销线性增加
Direct<0.1%<0.1%开销几乎不变

实现原理

开发者编辑代码时使用源生成方式写入assembly Attribute列表(名为GeneratedDirectRetrieveAttribute),并提供RoslynAnalyzer保证代码准确性。

使用反编译工具打开Unity自动生成的Dll,可以看到类似下面的额外元数据:

[assembly: GeneratedDirectRetrieve(typeof(global::Program))]
[assembly: GeneratedDirectRetrieve(typeof(global::Program),"Main")]
[assembly: GeneratedDirectRetrieve(typeof(com.Another.ns.Player<,>))]
[assembly: GeneratedDirectRetrieve(typeof(com.Another.ns.Player<,>),"age")]
[assembly: GeneratedDirectRetrieve(typeof(com.Another.ns.Player<,>),"Inner")]
[assembly: GeneratedDirectRetrieve(typeof(com.Another.ns.Player<,>.Inner))]
[assembly: GeneratedDirectRetrieve(typeof(com.Another.ns.IPlayer))]
[assembly: GeneratedDirectRetrieve(typeof(com.Another.ns.bbbirder))]
[assembly: GeneratedDirectRetrieve(typeof(com.Another.ns.labbbider))]

对于Inherit的Attribute,会额外记录他们的子类。

在运行时直接从全局元数据中获得GeneratedDirectRetrieveAttribute列表。

目录结构

大致目录如下: