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.
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
How can I modify the nested elements inside of an IEnuemerable<ProductLineDto>
So basically the loops look like this product lines > project types > projects > sub projects > which consists of more sub projects and activities.
The problem is I can't modify the activities of a subproject which is supposed to be happening in the recursive function GetActivitiesForActivityPerson
private IEnumerable<ProductLineDto> GetGanttDataByPerson(IEnumerable<ProductLineDto> ganttData, GanttFilterDto ganttFilterDataModel)
{
if(ganttFilterDataModel.PersonId == null)
return ganttData;
var gdata = ganttData.CopyProductLineDtosEnumerable().ToList();
for(var i = 0; i < gdata.Count; i++)
{
var pType = gdata[i].ProjectType.CopyProjectTypeDtoEnumerable().ToList();
for (var j = 0; j < pType.Count; j++)
{
var projects = pType[j].Projects.CopyProjectDtosEnumerable().ToList();
for (var a = 0; a < projects.Count; a++)
{
// projects[a].Children is not being modified
projects[a].Children = GetActivitiesForActivityPerson(projects[a].Children, ganttFilterDataModel.PersonId);
}
pType[j].Projects = projects;
}
gdata[i].ProjectType = pType;
}
ganttData = gdata;
return ganttData; ;
}
here is the recursive function
private IEnumerable<SubProjectDto> GetActivitiesForActivityPerson(IEnumerable<SubProjectDto> children, Guid? personId)
{
var subProjects = children.CopySubProjectDtoEnumerable().ToList();
for (var i = 0; i < subProjects.Count; i++)
{
var acts = subProjects[i].Activities.CopyActivityDtoEnumerable().ToList();
acts.RemoveAll(activity => activity.ActivityPersons.All(person => person.PersonID != personId));
subProjects[i].Activities = acts;
if(subProjects[i].Children.Any())
GetActivitiesForActivityPerson(subProjects[i].Children, personId);
}
children = subProjects;
return children;
}
When I step through the recursive function, the return children statement consists of the modified activities. However, this line of code does not consist of the modified activities it consists of the original data set after the assignment call.
projects[a].Children = GetActivitiesForActivityPerson(projects[a].Children, ganttFilterDataModel.PersonId);
If anyone is wondering what all of the copy methods look like, They just return a new instance of the DTO.
for example
public static IEnumerable<ProjectTypeDto> CopyProjectTypeDtoEnumerable(this IEnumerable<ProjectTypeDto> projectTypes)
{
return projectTypes.Select(x => new ProjectTypeDto
{
ProjectTypeId = x.ProjectTypeId,
Description = x.Description,
RowId = x.RowId,
Projects = x.Projects.CopyProjectDtosEnumerable()
});
}
For future reference, please submit an executable excerpt of code. For best results, you want people to be able to copy-past your problem code straight into their IDEs to play with.
Also, the code was painfully convoluted. It simplifies to this:
using System;
using System.Collections.Generic;
using System.Linq;
namespace StackOverflow_LoopMadness
{
class Program
{
static void Main(string[] args)
{
// ???
}
private static IEnumerable<ProductLineDto> GetGanttDataByPerson(IEnumerable<ProductLineDto> ganttData, GanttFilterDto ganttFilterDataModel)
{
if (ganttFilterDataModel.PersonId == null)
return ganttData;
foreach (var pType in ganttData)
{
foreach (var project in pType.Projects)
{
project.Children = GetActivitiesForActivityPerson(project.Children, ganttFilterDataModel.PersonId);
}
}
return ganttData;
}
private static IEnumerable<SubProjectDto> GetActivitiesForActivityPerson(IEnumerable<SubProjectDto> children, Guid? personId)
{
foreach (var subProject in children)
{
subProject.Activities = subProject.Activities.Where(a => a.ActivityPersons.All(person => person.PersonID != personId));
if (subProject.Children.Any())
{
GetActivitiesForActivityPerson(subProject.Children, personId);
}
}
return children;
}
}
class SubProjectDto
{
public IEnumerable<Activity> Activities { get; internal set; }
public IEnumerable<SubProjectDto> Children { get; internal set; }
}
class Activity
{
public IList<ActivityPerson> ActivityPersons { get; internal set; }
}
class ActivityPerson
{
public Guid? PersonID { get; internal set; }
}
class GanttFilterDto
{
public Guid PersonId { get; internal set; }
}
class ProductLineDto
{
public IList<Project> Projects { get; internal set; }
}
class Project
{
public IEnumerable<SubProjectDto> Children { get; set; }
}
}
As you can see, swapping the for-loops with foreach-loops greatly decreases the amount of code and increases readability. The extraneous copy lists were removed. Moreover, it was necessary for me to create 6 data classes via guesswork in order to compile.
I gave up figuring out a suitable driver for this program. However, I'm confident the problem of modifying nested elements is solved, because the edited variables are only passed by reference.
I think your problem stems from an incomplete understanding of pass by value vs. reference, thus your confusing use of deep copies. Microsoft can explain it better than me, so please check out this link.
First let me show a simple test case of the problem and how to trigger it. Here is the class:
class ProtoRecurseTest
{
private int nextPayload = 1;
public int Payload { get; private set; } = 0;
public ProtoRecurseTest Back { get; private set; } = null;
public List<ProtoRecurseTest> Children { get; set; } = new List<ProtoRecurseTest>();
public ProtoRecurseTest Add()
{
ProtoRecurseTest result = new ProtoRecurseTest(this, nextPayload++);
Children.Add(result);
return result;
}
public ProtoRecurseTest()
{
}
private ProtoRecurseTest(ProtoRecurseTest parent, int payload)
{
Back = parent;
this.Payload = payload;
nextPayload = payload + 1;
}
private static void ToStringHelper(ProtoRecurseTest proto, StringBuilder sb)
{
sb.Append(proto.Payload + " -> ");
// another little hassle of protobuf due to empty list -> null deserialization
if (proto.Children != null)
{
foreach (var child in proto.Children)
ToStringHelper(child, sb);
}
}
public override string ToString()
{
StringBuilder sb = new StringBuilder();
ToStringHelper(this, sb);
return sb.ToString();
}
}
There are no protobuf annotations as that is being taken care of programmatically. I have manually ensured that the class along with Back and Children are all added to the schema with .AsReferenceDefault = true.
The recursion triggering occurs when an instance is populated to a depth of at least 8 bizarrely enough. 7 is fine. Population code is straight forward:
ProtoRecurseTest recurseTest = new ProtoRecurseTest();
ProtoRecurseTest recurseItem = recurseTest;
for (int i = 0; i < 8; i++)
recurseItem = recurseItem.Add();
And then serialize recurseTest. This behavior only occurs when the children are in a list but in a list it occurs even with only 1 child per list as you end up with from the sample populating code. When I replaced the children with a single reference everything serialized fine.
This is using ProtoBuf.NET 2.1.0.0.
Here is a solution, but one I'm not particularly fond of. It was based on #marc-gravell 's answer to this question.
class ProtoRecurseTest : ISerializationManagementCallbacks
{
private int nextPayload = 1;
public int Payload { get; private set; } = 0;
public ProtoRecurseTest Back { get; private set; } = null;
public List<ProtoRecurseTest> Children { get; set; } = new List<ProtoRecurseTest>();
public ProtoRecurseTest Add()
{
ProtoRecurseTest result = new ProtoRecurseTest(this, nextPayload++);
Children.Add(result);
return result;
}
public ProtoRecurseTest()
{
}
private ProtoRecurseTest(ProtoRecurseTest parent, int payload)
{
Back = parent;
this.Payload = payload;
nextPayload = payload + 1;
}
private static void ToStringHelper(ProtoRecurseTest proto, StringBuilder sb)
{
sb.Append(proto.Payload + " -> ");
// another little hassle of protobuf due to empty list -> null deserialization
if (proto.Children != null)
{
foreach (var child in proto.Children)
ToStringHelper(child, sb);
}
}
public override string ToString()
{
StringBuilder sb = new StringBuilder();
ToStringHelper(this, sb);
return sb.ToString();
}
static void PreSerializationHelper(ProtoRecurseTest instance)
{
instance.Back = null;
foreach (var child in instance.Children)
PreSerializationHelper(child);
}
public void BeforeSerialization()
{
PreSerializationHelper(this);
}
static void PostDeserializationHelper(ProtoRecurseTest instance, ProtoRecurseTest parent)
{
if (instance.Children == null)
instance.Children = new List<ProtoRecurseTest>();
instance.Back = parent;
foreach (var child in instance.Children)
PostDeserializationHelper(child, instance);
}
public void AfterDeserialization()
{
PostDeserializationHelper(this, null);
}
}
The calls to serialize/deserialize now simply check if the type can be casted to the ISerializationManagementCallbacks (which was just provide a BeforeSerialization and AfterDeserialization method) before doing their business and then calling the appropriate methods if so. And it works fine.
However, I'm not really fond of mixing ever more serialization issues into my codebase - that's actually why the schema is generated programmatically. Ideally I'd like to separate the serialization entirely but the empty list -> null issue (which not to say it's an "issue" per se but just an undesirale part of the protobuf standard) already makes that impossible, but this would be another even more esoteric issue to work around and I do think this one might indeed be an issue.
I'm building a c# class that works with two different data sources. It will load a data source and take a configuration set from a function. Then I want to do several tasks on all properties within the object.
for example.
public String StreetAddress
{
get { return _streetAddress; }
set
{
if (value.Length <= 64)
_streetAddress = value;
else
_streetAddress = value.Substring(0, 1024).Trim();
}
}
public String City
{
get { return _city; }
set
{
if (value.Length <= 128)
_city = value;
else
_city = value.Substring(0, 128).Trim();
}
}
public String State
{
get { return _state; }
set
{
if (value.Length <= 128)
_state = value;
else
_state = value.Substring(0, 128).Trim();
}
}
So that holds the data from one side. I was hoping to be able to store and set a change flag on each property. So if we take State for example. If the person is moved from Texas to Illinois I want to set a bool within that property to note the change then be able to loop over all changes before saving the object to the DB. But I don't see any way to assign another state variable within that property. Is the best way to write another object on top of this to control it or is there another more creative way to store multiple strings within the one property?
If you'd like an OOP way of doing the thing, you can:
Define an interface and a class for holding your property, such as:
interface IPropertySlot
{
bool IsDirty { get; }
void ResetIsDirty();
object UntypedValue { get; }
}
class PropertySlot<T>:IPropertySlot
{
public T Value { get; private set; }
public bool SetValue(T value)
{
if (!Equals(_value, Value))
{
Value = value;
IsDirty = true;
return true;
}
return false;
}
public bool IsDirty { get; private set; }
public void ResetIsDirty()
{
IsDirty = false;
}
public object UntypedValue
{
get { return Value; }
}
}
Store your properties inside your class in a dictionary from String (for name of property) to IPropertySlot and get/set them through a pair of methods:
void SetProperty<T>(string name, T value)
{
IPropertySlot property;
if (!_properties.TryGetValue(name, out property))
{
property = new PropertySlot<T>();
_properties[name] = property;
}
((PropertySlot<T>)property) .SetValue(value);
}
T GetProperty<T>(string name)
{
IPropertySlot property;
if (!_properties.TryGetValue(name, out property))
{
property = new PropertySlot<T>();
_properties[name] = property;
}
return ((PropertySlot<T>)property).Value;
}
Finding the changed properties later is just a matter of going over the _properties.Values and finding which of them are IsDirty.
This approach also gives you a way to add more functionality to your properties in an OO manner (such as raising PropertyChanged/PropertyChanging events, mapping it to DB fields, etc.).
In such a situation I'd prefer an approach external to the Dto implementation.
Implement some unit that would take two instances of a class, and determine all the differences.
Map each property to compare:
static PropertyManager<Dto> manager = new PropertyManager<Dto>()
.Map(x => x.City)
.Map(x => x.StreetAddress);
Use two instances to compute difference:
var a = new Dto{ StreetAddress = "Foo", City = "Bar" };
var b = new Dto{ StreetAddress = "Foo", City = "Baz" };
var differences = manager.ComputeDifferences(a,b).ToList();
if( differences.Any() )
{
Console.WriteLine("Instances differ");
}
foreach (var diff in differences)
{
Console.WriteLine(diff);
}
This sample code prints out:
Instances differ
x.City
Here is a complete code example:
https://dotnetfiddle.net/4sNeoN
I want correctly return some variables (arrays)
kazkas.Ads[n]; (n = how many ads are)
kazkas.Ads[n].id;
kazkas.Ads[n].Days[m].Stats.Clicks; // every day have his own clicks
kazkas.Ads[n].Days[m].Stats.Impresons; // every day have his own impresions
from this method and use these variables in other class.
public static void GetAdsStats(string Ticket, DateTime start, DateTime end, int CamId)
{
var client = new CampaignStatsServiceClient();
var id = new CampaignIdFilter();
id.CampaignId = CamId;
var statsdata = new GetAdStatsData();
var kazkas = new Campaign();
kazkas = client.GetAdStats(Ticket, new GetAdStatsData
{
IdFilter = id,
StartDate = start,
EndDate = end
});
long AllClicks = 0;
long AllImpresions = 0;
int reklamos = kazkas.Ads.Length;
long[] statistikaClikai = new long[reklamos];
long[] statistikaImpresions = new long[reklamos];
for (int i = 0; i < reklamos; i++)
{
int dienos = kazkas.Ads[i].Days.Length;
for (int lop = 0; lop < dienos; lop++)
{
AllClicks = AllClicks + kazkas.Ads[i].Days[lop].Stats.Clicks;
AllImpresions = AllImpresions + kazkas.Ads[i].Days[lop].Stats.Impressions;
}
statistikaClikai[i] = AllClicks;
statistikaImpresions[i] = AllImpresions;
}
}
I know that void type can't return anything, but this how I know that my method works ( from debugging). Like you see I was trying do that with for loop. Here i have 9 Ads and every ad have one day.
Like I says I want return every Ads id[in array], and every days.stats.impresions and days.stats.click
how can I do that ? Ore how return more variables/arrays from method to other class, I am using webservises, so i cant use database ore something like that.
As can be seen by the downvotes of the question, you need to design the return value and then code against it.
Your query almost does it (now):
kazkas.Ads[n]; (n = how many ads are)
kazkas.Ads[n].id;
kazkas.Ads[n].Days[m].Stats.Clicks; // every day have his own clicks
kazkas.Ads[n].Days[m].Stats.Impressions; // every day have his own impressions
Your existing code show this should be expanded to include:
kazkas.Ads[n].Total.Clicks;
kazkas.Ads[n].Total.Impressions;
So now you're ready to design. First you want a Stat Class that just contains CLicks and Impressions:
public class Stat
{
public long Impressions { get; set; }
public long Clicks { get; set; }
}
An optimisation here may be to use a struct, but I won't go into that.
As you currently have defined it each Day has just a Stats property:
public class DayStat
{
public Stat Stats { get; set; }
}
Now finally we can define the top level AdStat:
public class AdStat
{
public int id { get; set; }
public DayStat Day[];
public Stat Total { get; set; }
}
Etc... There's further issues here, such as ensuring arrays are created and Stat instances are never null (which is why making some of these classes structs is an option). But I'm really a VB programmer so I'll stop here before I get caught typing crap into the SO IDE :-)
Create a class or struct with members you need
public class Stat
{
public int Id { get; set; }
public long Clicks { get; set; }
...
}
Change the signature of your method from void GetAdsStats to IEnumberable<Stat> GetAdsStats and either return a collection of stats or use yield keyword to return the stat object.
Also if you do not want your method to return anything (return type void) do not use a name starting with Get.
Example:
public static IEnumerable<Stat> GetAdsStats(...)
{
...
var statList = new List<Stat>();
for (int i = 0; i < reklamos; i++)
{
var stat = new Stat();
statList.Add(stat);
int dienos = kazkas.Ads[i].Days.Length;
for (int lop = 0; lop < dienos; lop++)
{
AllClicks = AllClicks + kazkas.Ads[i].Days[lop].Stats.Clicks;
AllImpresions = AllImpresions + kazkas.Ads[i].Days[lop].Stats.Impressions;
}
stat.Clicks = AllClicks;
stat.Impression = AllImpresions;
}
return statList;
}
Change your void to the type you want to return, say Campaign, and return the appropriate variable. The variables you define in your method, only live in your method and are not accessible from another method or class.
This might be a stupid one but I'll shoot it out there.
For example let's say I have a model class:
public class PermissionModel
{
public bool AppName_Home_Product_SaveButton_Enabled { get; set; }
public bool AppName_Home_Product_ConfirmButton_Enabled { get; set; }
}
And I have the following list of strings:
"AppName_Home_Product_SaveButton_Enabled_true"
"AppName_Home_Product_SaveButton_Enabled_false"
I want to automatically populate the model properties with true/false without having to use if statements as in the following example:
if (aString.Contains("AppName_Home_Product_SaveButton_Enabled"))
{
PermissionModel.AppName_Home_Product_SaveButton_Enabled = Convert.ToBoolean(AString.Substring(AString.IndexOf("Enabled_") + 8));
}
Any ideas or is this crazy? I just want to avoid a bunch of if statements to populate the model and make it more re-usable.
This can be done via reflection
const string delimiter = "_Enabled";
foreach (string data in aString) {
int index = data.IndexOf(delimiter);
if (index >= 0) {
// Get the name and value out of the data string
string name = data.Substring(0, index + delimiter.Length);
bool value = Convert.ToBoolean(data.Substring(index + delimiter.Length + 1));
// Find the property with the specified name and change the value
PropertyInfo property = GetType().GetProperty(name);
if (property != null) {
property.SetValue(this, value);
}
}
}