You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

.NET MAUI Android反射性能问题:DTO属性提取优化求助

优化.NET MAUI Android端反射性能方案

问题背景

拥有多个包含100+属性的DTO类,属性带有DisplayData、Section等自定义特性;从API反序列化为DTO后,通过反射提取属性数据生成DTOPropertyInfo集合。该操作在iOS设备上流畅,但在新旧Android设备上均存在明显卡顿,且不想手动编写每个属性的处理逻辑。

核心优化方案

1. 缓存反射元数据,避免重复反射调用

反射中GetType().GetProperties()、GetCustomAttribute是高开销操作,可通过静态字典缓存每个DTO类型的属性与对应特性信息,避免每次处理都重复反射:

private static readonly Dictionary<Type, List<(PropertyInfo Prop, DisplayDataAttribute Display, SectionAttribute Section)>> _propertyCache = new();

private List<(PropertyInfo Prop, DisplayDataAttribute Display, SectionAttribute Section)> GetCachedProperties(Type dtoType)
{
    if (_propertyCache.TryGetValue(dtoType, out var cached))
        return cached;

    var properties = dtoType.GetProperties()
        .Select(p => 
        {
            var display = p.GetCustomAttribute<DisplayDataAttribute>();
            if (display == null) return null;
            var section = p.GetCustomAttribute<SectionAttribute>() ?? dtoType.GetCustomAttribute<SectionAttribute>();
            return (Prop: p, Display: display, Section: section);
        })
        .Where(item => item != null && item.Section != null)
        .ToList();

    _propertyCache[dtoType] = properties;
    return properties;
}

后续处理同一类型DTO时,直接从缓存读取属性与特性,无需重复反射。

2. 用表达式树替代PropertyInfo.GetValue

PropertyInfo.GetValue的反射调用性能较差,可通过表达式树编译成委托,将属性读取速度提升至接近直接调用属性的水平:

private static readonly Dictionary<Type, List<(Func<object, object> GetValue, DisplayDataAttribute Display, SectionAttribute Section)>> _propertyAccessorCache = new();

private List<(Func<object, object> GetValue, DisplayDataAttribute Display, SectionAttribute Section)> GetCachedAccessors(Type dtoType)
{
    if (_propertyAccessorCache.TryGetValue(dtoType, out var cached))
        return cached;

    var accessors = new List<(Func<object, object>, DisplayDataAttribute, SectionAttribute)>();
    var properties = dtoType.GetProperties();

    foreach (var prop in properties)
    {
        var display = prop.GetCustomAttribute<DisplayDataAttribute>();
        if (display == null) continue;
        var section = prop.GetCustomAttribute<SectionAttribute>() ?? dtoType.GetCustomAttribute<SectionAttribute>();
        if (section == null) continue;

        // 编译属性取值委托
        var param = Expression.Parameter(typeof(object));
        var castParam = Expression.Convert(param, dtoType);
        var propertyAccess = Expression.Property(castParam, prop);
        var castResult = Expression.Convert(propertyAccess, typeof(object));
        var getter = Expression.Lambda<Func<object, object>>(castResult, param).Compile();

        accessors.Add((getter, display, section));
    }

    _propertyAccessorCache[dtoType] = accessors;
    return accessors;
}

使用时直接调用委托getter(dto)获取属性值,替代prop.GetValue(dto)。

3. 批量处理异步操作,减少上下文切换

原代码在循环内频繁调用await storage.GetAsync,会导致多次异步等待叠加延迟。可先收集所有需要查询的key,通过Task.WhenAll批量执行异步请求:

if (type is not QueryType.None)
{
    var accessors = GetCachedAccessors(dto.GetType());
    // 收集所有需要查询的键
    var favoriteKeys = accessors.Select(a => $"{type}/{a.Display.DisplayName}").ToList();
    // 批量执行异步查询
    var favoriteTasks = favoriteKeys.Select(k => storage.GetAsync(k)).ToList();
    var favoriteValues = await Task.WhenAll(favoriteTasks);

    for (int i = 0; i < accessors.Count; i++)
    {
        var accessor = accessors[i];
        var favorite = favoriteValues[i];
        if (!string.IsNullOrEmpty(favorite))
        {
            favorites.Add(new()
            {
                DisplayName = favorite,
                Value = accessor.GetValue(dto).FormatDisplayValue(),
                IsFavorite = true,
                Section = accessor.Section.Section,
                Unit = accessor.Display.Unit
            });
            continue;
        }
        // 处理非收藏属性逻辑
    }
}

4. 减少不必要的类型判断与转换

  • 在缓存阶段提前标记属性类型(如是否为BaseDTO[]、string[]),避免每次运行时重复判断;
  • 若属性类型确定为string,直接调用FormatDisplayValue,无需value as string的类型转换判断。

Android平台专属优化

  • 开启AOT编译:在Android项目的csproj中添加配置,提前编译反射相关代码,大幅提升Android设备运行性能:
    <PropertyGroup>
      <PublishAot>true</PublishAot>
    </PropertyGroup>
    
  • 启用部分剪裁:配合AOT开启剪裁模式,移除未使用代码,减少内存占用与运行时开销:
    <PropertyGroup>
      <TrimMode>partial</TrimMode>
    </PropertyGroup>
    
  • 后台线程执行反射:将反射处理逻辑放在后台线程,避免阻塞UI线程导致卡顿:
    var propertyInfo = await Task.Run(() => GetPropertiesInfoAsync(dto));
    

内容的提问来源于stack exchange,提问作者Zita

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.08.16 17:10:32