我有下面的代码来准备 logEventInfo 对象来记录数据。我正在使用 Nlog。我发现使用反射动态添加名称和值很方便。但我知道这会对性能产生很大影响。
public static LogEventInfo ToLogEventInfo(this ILogItem data, string message, Exception ex = null)
{
var eventInfo = new LogEventInfo();
if (ex != null)
eventInfo.Exception = ex;
if (data != null)
{
data.EventMessage = message;
data.LogTime = TimeZoneInfo.ConvertTime(DateTime.Now, TimeZoneInfo.Utc);
data.LogId = Guid.NewGuid();
var properties = data.GetType().GetProperties();
foreach (PropertyInfo property in properties) //Possibly a performance impact.
{
eventInfo.Properties[property.Name] = property.GetValue(data, null);
}
}
else
{
if (!string.IsNullOrEmpty(message))
eventInfo.Message = message;
}
return eventInfo;
}
这个 ToLogEvenInfo 函数将在循环中被调用。将循环的数据可能是数百万。有没有更好的方法来实现以下功能?非常感谢。
注意 NLog Jsonlayout 执行反射没有问题,因此您只需将
ILogItem
添加到 LogEventInfo.Properties,然后将 NLog JsonLayout 与 includeAllProperties="true"
和 maxRecursionLimit="1"
一起使用。您还可以使用 ${event-properties:item=LogItem:format=@}:
<layout type="JsonLayout" includeAllProperties="true" maxRecursionLimit="1" excludeProperties="LogItem" >
<attribute name="Time" layout="${date:format=O}" />
<attribute name="Level" layout="${level:upperCase=true}"/>
<attribute name="LogItem" encode="false" layout="${event-properties:item=LogItem:format=@}" />
</layout>
Microsoft System.Text.Json 序列化程序在处理基本 DTO 类时应该很快。但是当遇到特殊的对象值时可能会在你面前爆炸,因为它期望默认情况下对所有对象进行序列化和反序列化。
但是如果你只是想让反射快一点,那么这应该很管用。
static ConcurrentDictionary<Type, Func<object, IEnumerable<KeyValuePair<string, object>>> _typeProperties = new();
static IEnumerable<KeyValuePair<string, object> ResolveProperties(object value)
{
if (!_typeProperties.TryGetValue(value.GetType(), out var propertyResolver))
{
var properties = value.GetType().GetProperties();
propertyResolver = (v) => {
foreach (PropertyInfo property in properties)
{
var propertyName = property.Name;
var propertyValue = property.GetValue(v, null);
yield new KeyValuePair<string, object>(propertyName, propertyValue);
}
_typeProperties.TryAdd(value.GetType(), propertyResolver);
}
return propertyResolver.Invoke(this);
}
您可以通过编译
property.GetValue(..)
进一步优化它
表达式树。也许是这样的:
private static Func<object, object> GenerateGetterLambda(PropertyInfo property)
{
// Define our instance parameter, which will be the input of the Func
var objParameterExpr = Expression.Parameter(typeof(object), "instance");
// 1. Cast the instance to the correct type
var instanceExpr = Expression.TypeAs(objParameterExpr, property.DeclaringType);
// 2. Call the getter and retrieve the value of the property
var propertyExpr = Expression.Property(instanceExpr, property);
// 3. Convert the property's value to object
var propertyObjExpr = Expression.Convert(propertyExpr, typeof(object));
// Create a lambda expression of the latest call & compile it
return Expression.Lambda<Func<object, object>>(propertyObjExpr, objParameterExpr).Compile();
}
static IEnumerable<KeyValuePair<string, object> ResolveProperties(object value)
{
if (!_typeProperties.TryGetValue(value.GetType(), out var propertyResolver))
{
var slowProperties = value.GetType().GetProperties();
var fastProperties = new Func<object, object>[slowProperties.Length];
for (int i = 0; i < slowProperties.Length; ++i)
fastProperties = GenerateGetterLambda(slowProperties[i]);
propertyResolver = (v) => {
for (int i = 0; i < slowProperties.Length; ++i)
{
var propertyName = slowProperties[i].Name;
var propertyValue = fastProperties[i].Invoke(v);
yield new KeyValuePair<string, object>(propertyName, propertyValue);
}
_typeProperties.TryAdd(value.GetType(), propertyResolver);
}
return propertyResolver.Invoke(this);
}
另见:https://blog.zhaytam.com/2020/11/17/expression-trees-property-getter/