Home > Mobile >  Store information about class properties
Store information about class properties

Time:10-14

I apologize for my previous post, I'll try to be more detailed here.

I've got a few DTO classes, containing 100 properties which in turn have several attributes on them containing even more data related to the properties. For example:

    [JsonPropertyName("saldo")]
    [DisplayData("Saldo", Unit.Currency)]
    [Section(Section.Taxes)]
    public string saldo { get; set; }

Other properties might have more attributes and others might have fewer. When I fetch the data from the API I deserialize the stream into the specified DTO class. Once that finishes I do the reflection part in order to extract all the data.

    public async Task<IEnumerable<DTOPropertyInfo>> GetPropertiesInfoAsync(BaseDTO dto)
    {
        List<DTOPropertyInfo> info = new();
        List<DTOPropertyInfo> favorites = new();
        QueryType type = dto.ToQueryType();
        PropertyInfo[] properties = dto.GetType().GetProperties();

        for (int i = 0; i < properties.Length; i  )
        {
            PropertyInfo property = properties[i];
            DisplayDataAttribute display = 
                (DisplayDataAttribute)property.GetCustomAttribute(typeof(DisplayDataAttribute));
            if (display is null)
            {
                continue;
            }

            object value = property.GetValue(dto);

            if (value is BaseDTO[] dtos)
            {
                for (int j = 0; j < dtos.Length; j  )
                {
                    IEnumerable<DTOPropertyInfo> nestedInfo =
                        await GetPropertiesInfoAsync(dtos[j]);
                    info.Add(new()
                    {
                        DisplayName = $"{display.DisplayName} {j   1}",
                        Value = nestedInfo,
                        Section = Section.SubSection
                    });
                }
                continue;
            }
            else if (value is string[] strings)
            {
                if (strings.Length == 0)
                {
                    continue;
                }

                List<DTOPropertyInfo> dtoProperties = new();
                for (int j = 0; j < strings.Length; j  )
                {
                    dtoProperties.Add(new()
                    {
                        DisplayName = (j   1).ToString(),
                        Value = strings[j]
                    });
                }

                info.Add(new()
                {
                    DisplayName = display.DisplayName,
                    Value = dtoProperties,
                    Section = Section.SubSection
                });
                continue;
            }
            else if (property.PropertyType.IsArray)
            {
                //Only support arrays of type string[] and BaseDTO[]
                //If more needed, add them to the if-chain
                continue;
            }

            value = (value as string).FormatDisplayValue();
            if (string.IsNullOrEmpty(value.ToString()))
            {
                continue;
            }


            SectionAttribute section =
                (SectionAttribute)property.GetCustomAttribute(typeof(SectionAttribute));
            if (section is null)
            {
                section = (SectionAttribute)dto.GetType().GetCustomAttribute(typeof(SectionAttribute));
                if (section is null)
                {
                    continue;
                }
            }

            if (type is not QueryType.None)
            {
                string favorite = await storage.GetAsync($"{type}/{display.DisplayName}");
                if (!string.IsNullOrEmpty(favorite))
                {
                    favorites.Add(new()
                    {
                        DisplayName = favorite,
                        Value = value,
                        IsFavorite = true,
                        Section = section.Section,
                        Unit = display.Unit
                    });
                    continue;
                }
            }

            info.Add(new()
            {
                DisplayName = display.DisplayName,
                Value = value,
                Section = section.Section,
                Unit = display.Unit
            });
        }

        info.InsertRange(0, favorites);
        return info;
    }

DTOPropertyInfo looks like the following:

public readonly struct DTOPropertyInfo
{
    public readonly string DisplayName { get; init; }
    public readonly Section Section { get; init; }
    public readonly object Value { get; init; }
    public readonly bool IsFavorite { get; init; }
    public readonly Unit Unit { get; init; }

    public DTOPropertyInfo(string name, Section section, string value, bool favorite, Unit unit)
    {
        DisplayName = name;
        Section = section;
        Value = value;
        IsFavorite = favorite;
        Unit = unit;
    }
}

The app is built with .NET MAUI and I haven't really made any 'proper' tests, I've installed the app on several iOS phones (both old & newer ones). Same with Android phone (both old & newer ones) and they all behave the same. iOS has no problem at all when it does the reflection, everything is just instant. However, Android phones are really slow when they do the reflection part. Although newer Android phones do perform a little better than the old ones but the performance hit is still quite noticeable.

I guess I could just write out each and every property by hand but that would be very, very tedious.

CodePudding user response:

There are a few tricks to reflection-heavy code

Don't do things more than once

Most of what you're doing hinges on dto.GetType(); the per-type data won't change per instance / usage, but reflection requires new objects most of the time, so: don't do that! Consider adding a cache, for example:

static readonly ConcurrentDictionary<Type, EverythingYouNeedHere> s_TypeData = new();

Now you can check the cache (TryGetValue) and most of the time: do zero work. Just do the work when it isn't there, and add it.

In advanced scenarios where you really really don't want reflection, you can use "generators" or similar to move even this effort to build-time, but that's much much harder. The static cache approach solves 95% of reflection problems.

  • Related