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.
Related
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.
I am having a bit of a frustrating time finding a simple method to compare and prove that the contents of two lists are equal. I have looked at a number of solutions on stackoverflow but I have not been successful. Some of the solutions look like they will require a large amount of work to implement and do something that on the face of it to my mind should be simpler, but perhaps I am too simple to realize that this cannot be done simply :)
I have created a fiddle with some detail that can be viewed here: https://dotnetfiddle.net/cvQr5d
Alternatively please find the full example below, I am having trouble with the object comparison method (variable finalResult) as it's returning false and if the content were being compared I would expect the value to be true:
using System;
using System.Collections.Generic;
using System.Linq;
public class ResponseExample
{
public Guid Id { get; set; } = Guid.Parse("00000000-0000-0000-0000-000000000000");
public int Value { get; set; } = 0;
public string Initials { get; set; } = "J";
public string FirstName { get; set; } = "Joe";
public string Surname { get; set; } = "Blogs";
public string CellPhone { get; set; } = "0923232199";
public bool EmailVerified { get; set; } = false;
public bool CellPhoneVerified { get; set; } = true;
}
public class Program
{
public static void Main()
{
var responseOne = new ResponseExample();
var responseTwo = new ResponseExample();
var responseThree = new ResponseExample();
var responseFour = new ResponseExample();
List<ResponseExample> objectListOne = new List<ResponseExample>();
objectListOne.Add(responseOne);
objectListOne.Add(responseTwo);
List<ResponseExample> objectListTwo = new List<ResponseExample>();
objectListTwo.Add(responseThree);
objectListTwo.Add(responseFour);
bool result = objectListOne.Count == objectListTwo.Count();
Console.WriteLine($"Count: {result}");
bool finalResult = ScrambledEquals<ResponseExample>(objectListOne, objectListTwo);
Console.WriteLine($"Object compare: {finalResult}");
}
//https://stackoverflow.com/a/3670089/3324415
public static bool ScrambledEquals<T>(IEnumerable<T> list1, IEnumerable<T> list2)
{
var cnt = new Dictionary<T,
int>();
foreach (T s in list1)
{
if (cnt.ContainsKey(s))
{
cnt[s]++;
}
else
{
cnt.Add(s, 1);
}
}
foreach (T s in list2)
{
if (cnt.ContainsKey(s))
{
cnt[s]--;
}
else
{
return false;
}
}
return cnt.Values.All(c => c == 0);
}
}
As people in comments have pointed out this will not work as comparing a complex type by default compares whether the reference is the same. Field by field comparison will not work without implementing equality methods (and then you would need to overload GetHashCode and so on). See https://learn.microsoft.com/en-us/dotnet/api/system.object.equals?view=net-5.0
However, if you can use c# 9, which is what you have in the fiddle you can define the type as a record instead of class. Records have built in field by field comparison. See https://learn.microsoft.com/en-us/dotnet/csharp/whats-new/tutorials/records#characteristics-of-records
So public class ResponseExample would become public record ResponseExample and your code works as you expect.
Use Enumerable.All<TSource>(IEnumerable<TSource>, Func<TSource,Boolean>) Method which Determines whether all elements of a sequence satisfy a condition.
Once you have initilized your two List
list1.All(x=>list2.Contains(x))
This works by ensuring that all elements in list2 are containted in list1 otherwise returns false
Your method as is will compare if the 2 lists contain the same objects. So it is returning false as there are 4 different objects. If you create your list like this, using the same objects, it will return true:
List<ResponseExample> objectListOne = new List<ResponseExample>();
objectListOne.Add(responseOne);
objectListOne.Add(responseTwo);
List<ResponseExample> objectListTwo = new List<ResponseExample>();
objectListTwo.Add(responseTwo);
objectListTwo.Add(responseOne);
To get a true value when the contents of the objects are the same you could serialize the objects into a json string like this:
public static bool ScrambledEquals<T>(IEnumerable<T> list1, IEnumerable<T> list2)
{
JavaScriptSerializer json = new JavaScriptSerializer();
var cnt = new Dictionary<string,
int>();
foreach (T _s in list1)
{
string s = json.Serialize(_s);
if (cnt.ContainsKey(s))
{
cnt[s]++;
}
else
{
cnt.Add(s, 1);
}
}
foreach (T _s in list2)
{
string s = json.Serialize(_s);
if (cnt.ContainsKey(s))
{
cnt[s]--;
}
else
{
return false;
}
}
return cnt.Values.All(c => c == 0);
}
If the performance is not a big deal, you can use Newtonsoft.Json. We will be able to compare different types of objects as well as run a deep equals check.
First install the package:
Install-Package Newtonsoft.Json
Here is the code snip:
public static bool DeepEqualsUsingJson<T>(IList<T> l1, IList<T> l2)
{
if (ReferenceEquals(l1, l2))
return true;
if (ReferenceEquals(l2, null))
return false;
if (l1.Count != l2.Count)
return false;
var l1JObject = l1.Select(i => JObject.FromObject(i)).ToList();
var l2JObject = l2.Select(i => JObject.FromObject(i)).ToList();
foreach (var o1 in l1JObject)
{
var index = l2JObject.FindIndex(o2 => JToken.DeepEquals(o1, o2));
if (index == -1)
return false;
l2JObject.RemoveAt(index);
}
return l2JObject.Count == 0;
}
Well, I would like to do my own benchmarking system like spark in Minecraft (https://github.com/lucko/spark):
I'm using Harmony lib (https://github.com/pardeike/Harmony) which allows me to interact/modify methods and allows me to add a Prefix/Postfix on each call that will help me out with this stack.
The basic structure has something similar to (https://github.com/pardeike/Harmony/issues/355):
[HarmonyPatch]
class MyPatches
{
static IEnumerable<MethodBase> TargetMethods()
{
return AccessTools.GetTypesFromAssembly(Assembly.GetExecutingAssembly())
.SelectMany(type => type.GetMethods())
.Where(method => method.ReturnType != typeof(void) && method.Name.StartsWith("Do"));
}
static void Prefix(out Stopwatch __state, MethodBase __originalMethod)
{
__state = Stopwatch.StartNew();
// ...
}
static void Postfix(Stopwatch __state, MethodBase __originalMethod)
{
__state.Stop();
// ....
}
}
The problem here is that the __originalMethod doesn't take care if it was called from A or B.
So for example, we had patched string.Join method. And the we call from A or B, where A or B, is the full callstack of this method.
So first, we need to assign a ID to this call, and we need to create a Tree-based structure (which is hard to serialize later), from here (https://stackoverflow.com/a/36649069/3286975):
public class TreeModel : Tree<TreeModel>
{
public int ID { get; set; }
public TreeModel() { }
public TreeModel(TreeModel parent) : base(parent) { }
}
public class Tree<T> where T : Tree<T>
{
protected Tree() : this(null) { }
protected Tree(T parent)
{
Parent=parent;
Children=new List<T>();
if(parent!=null)
{
parent.Children.Add(this as T);
}
}
public T Parent { get; set; }
public List<T> Children { get; set; }
public bool IsRoot { get { return Parent==null; } }
public T Root { get { return IsRoot?this as T:Parent.Root; } }
public T RecursiveFind(Predicate<T> check)
{
if(check(this as T)) return this as T;
foreach(var item in Children)
{
var result=item.RecursiveFind(check);
if(result!=null)
{
return result;
}
}
return null;
}
}
Now, the thing is that we need to fill the Tree as long as we iterate all the method and instructions got from Harmony. Forget about Harmony for a second, I will explain only two facts about it.
The lib allows you first to get all patched methods through IEnumerable<MethodBase> TargetMethods() so, you have the Assembly X passed through reflection and filtered all methods that are allowed to be patched (some of them broke Unity, so I decided to skip methods from UnityEngine., UnityEditor. and System.* namespaces).
And we have also the ReadMethodBody method (https://harmony.pardeike.net/api/HarmonyLib.PatchProcessor.html#HarmonyLib_PatchProcessor_ReadMethodBody_System_Reflection_MethodBase_) from a given MethodBase it returns all IL stack instructions.
So we can start to iterate over and over in order to get all instructions and fill the entire tree. This is what I wrote last night:
internal static class BenchmarkEnumerator
{
internal static Dictionary<MethodBase, int> Mappings { get; } = new Dictionary<MethodBase, int>();
internal static Dictionary<int, TreeModel> TreeIDs { get; } = new Dictionary<int, TreeModel>();
internal static Dictionary<MethodBase, BenchmarkTreeModel> TreeMappings { get; } = new Dictionary<MethodBase, BenchmarkTreeModel>();
private static HashSet<int> IDUsed { get; } = new HashSet<int>();
public static int GetID(this MethodBase method)
{
return GetID(method, out _);
}
public static int GetID(this MethodBase method, out bool contains)
{
// A > X = X1
// B > X = X2
if (!Mappings.ContainsKey(method))
{
var id = Mappings.Count;
Mappings.Add(method, Mappings.Count);
IDUsed.Add(id);
contains = false;
return id;
}
contains = true;
return Mappings[method];
}
public static int GetFreeID()
{
int id;
Random rnd = new Random();
do
{
id = rnd.Next();
} while (IDUsed.Contains(id));
IDUsed.Add(id);
return id;
}
public static BenchmarkCall GetCall(int id)
{
return TreeIDs[id]?.Call;
}
public static BenchmarkCall GetCall(this MethodBase method)
{
return TreeIDs[Mappings[method]]?.Call;
}
}
The BenchmarkEnumerator class allow us to differentiate between A or B, but it doesn't care about the full hierarchy, only from the parent MethodBase itself, so I need to write something complex to take in care of the full call stack, which I said I have a problem to understand.
Then we have the TargetMethods:
private static IEnumerable<MethodBase> TargetMethods()
{
Model = new BenchmarkTreeModel();
var sw = Stopwatch.StartNew();
//int i = 0;
return Filter.GetTargetMethods(method =>
{
try
{
var instructions = PatchProcessor.ReadMethodBody(method);
var i = method.GetID(out var contains);
var tree = new TreeModel
{
ID = i
};
if (contains)
{
//var lastId = i;
i = GetFreeID();
tree.ID = i;
tree.FillMethodName($"{method.GetMethodSignature()}_{i}"); // TODO: Check this
tree.Parent = null;
tree.Children = TreeMappings[method].Forest.First().Children; // ??
//DictionaryHelper.AddOrAppend(TreeMappings, method, tree);
TreeMappings[method].Forest.Add(tree);
TreeIDs.Add(i, tree);
Model.Forest.Add(tree);
// UNIT TESTING: All contained methods at this point will have a parent.
// string.Join is being added as a method by a instruction, so when we try to patch it, it will have already a reference on the dictionary
// Here, we check if the method was already added by a instruction CALL
// Logic: If the method is already contained by the mapping dictionary
// then, we will exit adding a new that will have the same childs but a new ID
return false;
}
TreeIDs.Add(i, tree);
tree.FillMethodName($"{method.GetMethodSignature()}_{i}"); // TODO: Check this
foreach (var pair in instructions)
{
var opcode = pair.Key;
if (opcode != OpCodes.Call || opcode != OpCodes.Callvirt) continue;
var childMethod = (MethodBase)pair.Value;
var id = childMethod.GetID(out var _contains);
var subTree = new TreeModel(tree)
{
ID = id
};
if (_contains)
{
id = GetFreeID();
subTree.ID = id;
subTree.FillMethodName($"{childMethod.GetMethodSignature()}_{id}"); // TODO: Check this
subTree.Parent = TreeIDs[i];
subTree.Children = TreeMappings[childMethod].Forest.First().Children;
TreeIDs.Add(id, subTree);
continue;
}
TreeIDs.Add(id, subTree);
subTree.FillMethodName($"{childMethod.GetMethodSignature()}_{id}");
tree.Children.Add(subTree);
TreeMappings.Add(childMethod, new BenchmarkTreeModel());
TreeMappings[childMethod].Forest.Add(subTree);
}
TreeMappings.Add(method, new BenchmarkTreeModel());
TreeMappings[method].Forest.Add(tree);
Model.Forest.Add(tree);
return true;
//var treeModel = new TreeModel();
}
catch (Exception ex)
{
//Debug.LogException(new Exception(method.GetMethodSignature(), ex));
return false;
}
}, sw);
//return methods;
}
The GetMethodSignature is something like:
public static string GetMethodSignature(this MethodBase method)
{
if (method == null) return null;
return method.DeclaringType == null ? method.Name : $"{method.DeclaringType.FullName}.{method.Name}";
}
I think I'll replace it with the MethodBase.ToString instead (what do you think?)
Also, we have the BenchmarkCall class which allow us to take in care how many times the call was done and how many time it has spent at all:
[Serializable]
public class BenchmarkCall
{
public string Method { get; set; }
public double SpentMilliseconds { get; set; }
public long SpentTicks { get; set; }
public double MinSpentMs { get; set; } = double.MaxValue;
public double MaxSpentMs { get; set; } = double.MinValue;
public long MinSpentTicks { get; set; } = long.MaxValue;
public long MaxSpentTicks { get; set; } = long.MinValue;
public double AvgMs => SpentMilliseconds / TimesCalled;
public double AvgTicks => SpentTicks / (double)TimesCalled;
public BenchmarkCall()
{
}
public BenchmarkCall(MethodBase method)
{
Method = method.GetMethodSignature();
}
public override string ToString()
{
if (TimesCalled > 0)
return "BenchmarkCall{\n" +
$"Ticks[SpentTicks={SpentTicks},MinTicks={MinSpentTicks},MaxTicks={MaxSpentTicks},AvgTicks={AvgTicks:F2}]\n" +
$"Ms[SpentMs={SpentMilliseconds:F2},MinMs={MinSpentMs:F2},MaxMs={MaxSpentMs:F2},AvgMs={AvgMs:F2}]\n" +
"}";
return "BenchmarkCall{}";
}
}
}
So I think that my next movement will be to differentiate between X method being called from A or B (Xa or Xb) taking care of the full hierarchy (which I'm not sure how to do) instead of the parent method that calls it, maybe the code I wrote has some to do it with it, but I'm not sure (last night I was so tired, so I didn't code it taking care those facts), build up a list of method signatures with different IDs, and then fill up the tree, ID 1 is Xa and ID 2 is Xb (where I have problems also filling up the tree).
Also I'll need to use the Transpiler in order to alter all code instructions, so if a method has:
void method() {
X1();
X2();
}
We will need to add 2 methods (like prefix/postfix) to measure each instruction call:
void method() {
Start(1);
X1();
End(1);
Start(2);
X2();
End(2);
}
This will be a hard task, but I hope somebody could guide me with this out.
lets assume I have the following classes:
public class ServiceStatistics
{
public string LocalId { get; set; }
public string OrganizationId { get; set; }
public List<StatisticElements> Elements { get; } = new List<StatisticElements>();
}
public class StatisticElements
{
public string StatisticId { get; set; }
public string Type { get; set; }
public string ServiceName { get; set; }
}
I retrieve such ServiceStatistics by a soap service and I use serialization/deserialization.
Each ServiceStatistics contains a set of StatisticElements. I also have a static list of StatisticElements-ID's which are relevant for calculation. All other incoming StatisticElements-ID's can be dropped. I need to do this on my side
because the SOAP Service does not support selecting specific StatisticElements-ID's
So I have generated a static Class with a HashSet:
public static class RelevantDutyPlans
{
private static HashSet<int> relevantDutyPlans;
static RelevantDutyPlans()
{
// only a subset of the original ID's
relevantDutyPlans = new HashSet<int>()
{
530,
1150,
1095,
};
}
public static HashSet<int> GetRelevantDutyPlans()
{
return relevantDutyPlans;
}
public static bool Contains(int planId)
{
return relevantDutyPlans.Contains(planId);
}
// Extracts all DutyPlans which are relevant (HashSet) for validation from
// the incoming data
public static List<int> ExtractRelevantDutyPlans(List<int> planIds)
{
var relevantPlans = new HashSet<int>(planIds);
relevantPlans.IntersectWith(relevantDutyPlans);
return relevantDutyPlans.ToList();
}
}
So my thought was, to create an Intersect like this:
List<ServiceStatistics> statistics = SoapService.GetStatistics(Now);
List<int> incomingIds = new List<int>();
foreach(var item in statistics)
{
foreach(var element in item.Statistic)
{
incomingIds.Add(int.Parse(element.StatisticId));
}
}
List<int> extract = RelevantDutyPlans.ExtractRelevantDutyPlans(incomingIds);
So now I have a List of ID's which are relevant for further processing. What I want to achieve is to remove all class elements "StatisticElements" with "StatisticId" not contained in the the extract list generated above.
Any ideas?
Any help is very appreciated
How about a little bit different approach. Simply remove irrelevant plans right away!
List<ServiceStatistics> statistics = SoapService.GetStatistics(Now);
foreach(var item in statistics)
{
item.Elements.RemoveAll(x => !RelevantDutyPlans.Contains(int.Parse(x.StatisticId)));
}
Now you are only left with the relevant once.
Hope you can use selectMany to flatten the collection and proceed the filter.
var filteredItems = statistics.SelectMany(s => s.Elements)
.Where(s => extract.Contains(Convert.ToInt32(s.StatisticId)))
.ToList();
You could also use LINQ to create a new List<> if you need to keep the original statistcs intact - e.g. if you might run multiple plans against it.
var relevantStatistics = statistics.Select(s => new {
LocalId = s.LocalId,
OrganizationId = s.OrganizationId,
Elements = s.Elements.Where(e => !RelevantDutyPlans.Contains(Convert.ToInt32(e.StatisticId))).ToList()
});
Since ServiceStatistics doesn't provide for construction, I return an anonymous object instead, but you could create an appropriate DTO class.
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.