У меня есть код ниже, чтобы подготовить объект logEventInfo для регистрации данных. Я использую Нлог. Я обнаружил, что удобно использовать отражение для динамического добавления имени и значения. Но я знаю, что это сильно влияет на производительность.
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 будет вызываться в цикле. Данные будут зацикливаться, может быть миллионы. Есть ли лучший способ реализовать приведенную ниже функцию? Большое спасибо.
«Возможно, влияние на производительность» — безусловно, влияние на производительность. Вопрос в том, заметно ли это в общей схеме вещей и влияет ли это на ваше приложение или его можно игнорировать.
Также обратите внимание, что есть способы смягчить это воздействие. Например, путем «кеширования» отражения с помощью некоторой компиляции во время выполнения с выражением tress, аналогично тому, что делается здесь, но без дженериков, просто сохраняя какой-то ConcurrentDictionary<Type, Func<ILogItem, LogEventInfo>> кеш.
Или, может быть, поиграть с какой-то сериализацией json.





Обратите внимание, что 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/
Большое спасибо ! Я не ограничен только использованием отражения. Я просто понимаю, что Nlog уже сделал отражение, и я удалил свое отражение. Спасибо !
Вы всегда можете погонять на своих лошадях и сравнить это с перегрузкой
ToLogEventInfoдля какого-то конкретного вида.