.NET Core 特性(Attribute)底层原理浅谈
简介
烂大街的资料不再赘述,简单来说就是给代码看的注释
Attribute的使用场景
Attribute不仅仅局限于C#中,在整个.NET框架中都提供了非常大的拓展点,任何地方都有Attribute的影子
- 编译器层
比如 Obsolete,Conditional - C#层
GET,POST,Max,Range,Require - CLR VM层
StructLayout,DllImport - JIT 层
MethodImpl
Attribute在C#中的调用
举个常用的例子,读取枚举上的自定义特性。
public enum Test
{
[EnumDescription("hhhhhh")]
None = 0,
[EnumDescription("xxxxxx")]
Done =1
}
private static IEnumerable<string> GetEnumDescriptions(this Enum e)
{
IEnumerable<string> result = null;
var type = e.GetType();
var fieldInfo = type.GetField(e.ToString());
var attr = fieldInfo?.GetCustomAttributes(typeof(EnumDescriptionAttribute), false);
if (attr?.Length > 0)
{
result = attr.Cast<EnumDescriptionAttribute>().Select(x => x.Description);
}
return result ?? Enumerable.Empty<string>();
}
可以看到,Attribute底层在C#中实现依旧是依赖反射,所以为什么说Attribute是写给代码看的注释,因此对反射的优化思路也可以用在Attribute中。
比如在代码中,使用Dictionary缓存结果集。避免过多调用反射造成的性能问题。
private static IEnumerable<string> GetEnumDescriptionsCache(this Enum e)
{
var key = $"{e.GetType().Name}_{e.ToString()}";
if (_enumMap.ContainsKey(key))
{
return _enumMap[key];
}
else
{
var result = GetEnumDescriptions(e);
_enumMap.TryAdd(key, result);
return result;
}
}
循环100000次造成的性能差距还是很明显的
Newtonsoft.Json对Attrubute的使用
以JsonConverter为蓝本举例说明。
public class Person
{
[JsonConverter(typeof(DateTimeConverter))]
public DateTime CreateTime { get; set; }
}
public class DateTimeConverter : JsonConverter<DateTime>
{
public override DateTime ReadJson(JsonReader reader, Type objectType, DateTime existingValue, bool hasExistingValue, JsonSerializer serializer)
{
if (reader.Value == null)
return DateTime.MinValue;
if (DateTime.TryParse(reader.Value.ToString(), out DateTime result))
return result;
return DateTime.MinValue;
}
public override void WriteJson(JsonWriter writer, DateTime value, JsonSerializer serializer)
{
writer.WriteValue(value.ToString("yyyy-MM-dd HH:mm:ss"));
}
}
定义了一个Attribute:JsonConverter.其底层调用如下:
[RequiresUnreferencedCode(MiscellaneousUtils.TrimWarning)]
[RequiresDynamicCode(MiscellaneousUtils.AotWarning)]
public static JsonConverter? GetJsonConverter(object attributeProvider)
{
// 底层还是调用Reflection,为了性能,也缓存了对象元数据。
JsonConverterAttribute? converterAttribute = GetCachedAttribute<JsonConverterAttribute>(attributeProvider);
if (converterAttribute != null)
{
Func<object[]?, object> creator = CreatorCache.Instance.Get(converterAttribute.ConverterType);
if (creator != null)
{
return (JsonConverter)creator(converterAttribute.ConverterParameters);
}
}
return null;
}
Attribute在CLR上的调用
public class NativeMethods
{
[DllImport("xxxxx", EntryPoint = "add", CallingConvention = CallingConvention.Cdecl)]
public extern static int ManagedAdd(int a, int b);
}
在CLR中,同样用来调用 C/C++ 的导出函数。有兴趣的朋友可以使用windbg查看线程调用栈。以及在MetaData中有一张ImplMap表,存储着C#方法与C++函数的mapping关系
Attribute在JIT上的调用
public class Person
{
public int id { get; set; } = 0;
[MethodImpl(MethodImplOptions.Synchronized)]
public void SyncMethod()
{
id++;
}
}
JIT会自动为该Attribute注入同步代码
其本质就是注入lock同步块代码,只是颗粒度在整个方法上。相对比较大
结论
Attrubute在C#层面,底层使用反射。因此使用自定义Attribute时,酌情使用缓存来提高性能
Attrubute在CLR层面,大多数情况下依旧是依赖反射,从Metadata就可以看出(元数据就是为反射而准备)。
Attrubute在JIT层面,大多数情况下是直接转成IL或者汇编,以达到性能更优。