获取方法参数自定义属性:寻求替代反射的更优实现方案
Great question—reflection can be a real performance bottleneck when you're fetching parameter attributes frequently. Let’s walk through practical, lower-overhead alternatives that fit different use cases:
1. Cache Reflection Results (Quick Win)
If you can’t eliminate reflection entirely, caching the results of your initial reflection call is the simplest way to cut overhead. The bulk of reflection’s cost comes from resolving metadata on the first access; subsequent lookups from a cache are nearly free.
Here’s a thread-safe static cache implementation:
using System.Collections.Concurrent; using System.Reflection; public static class ParamAttributeCache { private static readonly ConcurrentDictionary<(MethodInfo Method, int ParamIndex), Attribute[]> _cache = new(); public static T[] GetAttributes<T>(MethodInfo method, int paramIndex) where T : Attribute { if (paramIndex < 0 || paramIndex >= method.GetParameters().Length) return Array.Empty<T>(); return _cache.GetOrAdd((method, paramIndex), key => key.Method.GetParameters()[key.ParamIndex] .GetCustomAttributes(true) .Cast<Attribute>() .ToArray()) .OfType<T>() .ToArray(); } }
This way, you only pay the reflection cost once per method-parameter pair, and all future calls pull directly from the cache.
2. Compile-Time Source Generators (Zero Runtime Overhead)
For high-performance scenarios where you need frequent access to parameter attributes, source generators are the gold standard. They let you generate static access code at compile time, completely avoiding runtime reflection.
Using Roslyn’s source generator API, you can scan your codebase for methods with parameter attributes during compilation, then auto-generate helper classes that return attribute values directly. For example, if you have a [MyParam] attribute, the generator might output code like:
// Auto-generated code public static class ParamAttributeHelpers { public static MyParamAttribute GetMyParamFor_MyService_MyMethod_Param0() { return new MyParamAttribute("TargetValue"); // Compile-time known value } }
At runtime, you just call this static method—no reflection, no overhead. The tradeoff is a small learning curve for writing source generators, but it’s well worth it for performance-critical code.
3. Expression Trees (Middle Ground)
If source generators feel too heavy but you still want better performance than raw reflection, expression trees are a solid middle option. You can build an expression that retrieves the parameter attributes, compile it into a delegate, and cache that delegate for repeated use.
Here’s an example:
using System.Linq.Expressions; using System.Reflection; public static class ParamAttributeFetcher { private static readonly ConcurrentDictionary<(MethodInfo Method, int ParamIndex), Func<Attribute[]>> _fetchers = new(); public static T[] GetAttributes<T>(MethodInfo method, int paramIndex) where T : Attribute { if (paramIndex < 0 || paramIndex >= method.GetParameters().Length) return Array.Empty<T>(); var fetcher = _fetchers.GetOrAdd((method, paramIndex), key => { // Build expression to get parameters and their attributes var methodParam = Expression.Parameter(typeof(MethodInfo)); var getParams = Expression.Call(methodParam, nameof(MethodInfo.GetParameters), Type.EmptyTypes); var paramAccess = Expression.ArrayIndex(getParams, Expression.Constant(key.ParamIndex)); var getAttrs = Expression.Call(paramAccess, nameof(ParameterInfo.GetCustomAttributes), Type.EmptyTypes, Expression.Constant(true)); return Expression.Lambda<Func<Attribute[]>>(getAttrs, methodParam).Compile(); }); return fetcher().OfType<T>().ToArray(); } }
Compiled delegates run at near-native speed, so this is much faster than repeated reflection calls, without the complexity of source generators.
Final Recommendations
- Occasional access: Stick with cached reflection—it’s simple and effective.
- High-frequency access: Use source generators for zero runtime overhead.
- Middle ground: Expression trees offer a good balance between performance and complexity.
内容的提问来源于stack exchange,提问作者dks

