使用CustomAttributes的优势与GetCustomAttributes()相比是什么?

8
我今天注意到在我的.NET 4.5项目中,System.Type对象的智能感知中出现了一些新属性,其中之一叫做CustomAttributes。我对此很感兴趣,因为我以前曾经了解过GetCustomAttributes是最昂贵的反射调用之一(当然除了DynamicInvoke等)。据我所知,每次调用GetCustomAttributes都会导致调用属性的构造函数(从而进行内存分配)。我经常单独缓存自定义属性,以避免处理大量类型时的性能瓶颈。

因此,我编写了一个测试,以查看CustomAttributes是否比GetCustomAttributes更具有性能优势:

static void Main(string[] args)
{
    var sw = Stopwatch.StartNew();

    Debug.WriteLine(typeof(Attributed).GetType());

    for (int i = 0; i < 10000; i++)
    {
        var attrs = typeof(Attributed)
            .CustomAttributes
            .Select(a => a.AttributeType)
            .ToList();
    }

    sw.Stop();
    Debug.WriteLine("Using .NET 4.5 CustomAttributes property: {0}", sw.Elapsed);

    sw = Stopwatch.StartNew();

    for (int i = 0; i < 10000; i++)
    {
        var attrs = typeof(Attributed)
            .GetCustomAttributes(true)
            .Select(a => a.GetType())
            .ToList();
    }

    sw.Stop();
    Debug.WriteLine("Using GetCustomAttributes method: {0}", sw.Elapsed);
}

有一些测试类:

[Dummy]
[Dummy]
[Dummy]
[Dummy]
[Dummy]
[Dummy]
class Attributed
{
}

[AttributeUsage(AttributeTargets.Class, AllowMultiple=true)]
class DummyAttribute : Attribute
{
    public DummyAttribute()
    {
    }
}

结果令人惊讶:
System.RuntimeType
Using .NET 4.5 CustomAttributes property: 00:00:00.1351259
Using GetCustomAttributes method: 00:00:00.0803161

新的CustomAttributes属性比现有的GetCustomAttributes方法还要慢!进一步调试后,我发现迭代CustomAttributes时并未调用属性构造函数(我原本以为这只是在读取元数据)。但出乎意料的是,它比调用构造函数的GetCustomAttributes方法还要慢。
我的问题是:个人认为使用新属性更易读,但代价是1.5倍左右的性能下降。那么,相较于GetCustomAttributes(),使用CustomAttributes是否有任何优势呢?我假设我们仅检查某种类型的属性是否存在于类上...不使用属性实例的方法或属性。

2
考虑到基准测试的简单性,这只是微小的差异。这可能只是误差范围内可以被丢弃的。您是否比较了这些API的实际实现? - Jeroen Vannevel
是的,这是一个相当小的差异,但它可以扩展并保持1.5倍的比率,当增加迭代次数时,这意味着在迭代具有自定义属性的大量事物时可能会产生影响。我正在尝试找到一个程序集,指向ILSpy来查看实现(除非我可以先在在线参考源上找到它)。 - Los Frijoles
说实话,我更感兴趣的是为什么它会变慢。因为它只是读取类型的元数据,而 GetCustomAttributes 也需要这样做,所以我本来期望它会快得多。也许获取 ConstructorInfo 和其他信息的过程比我想象中的更昂贵? - Los Frijoles
4个回答

19
您正在犯一个传统的基准测试错误,这会让很多.NET程序员认为Reflection很慢。实际上比它真正的速度慢。 Reflection非常懒惰,当您不使用它时不需要付出代价。这使得您的第一个测量包括将元数据页面错误地加载到RAM并设置类型信息反射缓存的所有成本。该成本未包含在第二个测量中,使得GetCustomAttributes()看起来比它实际上要好。
始终在基准代码周围包含一个循环,并运行10次。现在,您会发现CustomAttributes属性实际上并不那么慢,我将其测量为(粗略地)0.083对0.063秒,差异约为30%。
为了支持WinRT的语言投影,必须在.NET 4.5中添加CustomAttributes属性。您不能在Store、Phone或PCL项目中使用GetCustomAttributes()。在WinRT中,Reflection 非常与众不同,这是它成为COM基于api的必然副作用。实现代码足以使任何人眼花缭乱,但总体轮廓是该属性在C#中实现,而该方法在CLR中实现。 C#代码需要执行更多工作以处理语言投影细节,因此不可避免地较慢。
所以,继续使用GetCustomAttributes(),在必要时使用属性。否则,30%的速度差异并不是一个牺牲风格和可读性的重大原因,请运用您的常识。

2

正如sjb-sjb和JL0PD的答案所暗示的那样,这两个东西并没有提供相同的信息,因此它们是没有可比性的。

我建议使用第三种方法,即.NET 4.5及更高版本中的新扩展方法。例如:

typeof(Attributed).GetCustomAttributes()                  // no bool 'inherit' on this overload

或者:

typeof(Attributed).GetCustomAttributes<DummyAttribute>()  // gets only attributes of the specified type, and no need for casting

为了让这些扩展方法在作用域内,你需要使用指令:
using System.Reflection;

请查看文档

1
一个重要的区别在于,CustomAttributes返回IEnumerable,而GetCustomAttributes返回包含从Attribute派生的实例的object[]。

0
从net6开始,GetCustomAttributes 函数会出乎意料地检查属性的可访问性,而 CustomAttributes 函数则不会。当您在运行时生成程序集并将自己的内部属性应用于生成的类型时,可以观察到这一点。请考虑以下程序中的程序集。
program
| -- DefaultAssembly # assembly generated by C# compiler, loaded from dll on disk
|       MyInternalAttribute # type is declared in code and compiled to currently executing program
| -- ReflectionEmitDynamicAssembly # assembly created at runtime with AssemblyBuilder
|       RuntimeGeneratedType # type is generated at runtime and annotated with MyInternalAttribute

给定类型为 System.Type 的变量 type 指向 RuntimeGeneratedType,访问 type.GetCustomAttributes().Count() 将返回 0,但是访问 type.CustomAttributes.Count() 将返回 1。这可以通过使用通常已知的 InternalsVisibleTo 来避免,但您需要标记您的程序集。或者,可以使用 IgnoresAccessChecksTo 来注释生成的程序集。

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接