Using LINQ to loop through inner class properties in outer class collection - c#

Leveraging off the Q&As dealing with looping through an object's properties (https://stackoverflow.com/questions/15586123/loop-through-object-and-get-properties, Loop Through An Objects Properties In C#), where you have:
a collection of Class1 objects (i.e. listObj1)
each Class1 contains a collection of Class2 objects (i.e. dictObj2)
How would you:
efficiently determine the properties of the inner class (Class2)
loop through the the properties of the inner class (Class2)
loop through the collection of Class1 objects (listObj1) selecting all instances of the the Class2 property
output the collection of Class2 property (e.g. the first iteration would return a collection of MeasurementA, one from each Class1 object).
and group the collection by Class1.PropertyA and Class1.PropertyB
Please find below a rough map of the classes involved.
I have been trying to use a LINQ query without success. Any ideas or guidance would be greatly appreciated.
class MainClass {
List<Class1> listObj1
}
class Class1 {
// a number of fields including...
int PropertyA { get; set; }
int PropertyB { get; set; }
Dictionary<int, Class2> dictObj2 { get; set; }
}
class Class2 {
// a number of fields all of type double...
double MeasurementA { get; set; }
double MeasurementB { get; set; }
double MeasurementC { get; set; }
}

Given data:
MainClass mainClass = new MainClass();
mainClass.listObj1 = new List<Class1>()
{
new Class1() {
dictObj2 = new Dictionary<int,Class2>() {
{ 1, new Class2() { MeasurementA = 2.0, MeasurementB = 3.0, MeasurementC = 4.0 }},
{ 2, new Class2() { MeasurementA = 5.0, MeasurementB = 6.0, MeasurementC = 7.0 }}
}
}
};
you can write with LINQ:
var fields = typeof(Class2)
// Get Properties of the ClassB
.GetProperties(BindingFlags.Public | BindingFlags.Instance)
// Map each PropertyInfo into collection of its values from c1.dictObj2
.SelectMany(pi => mainClass.listObj1
.SelectMany(c1 => c1.dictObj2)
.Select(p => new
{
Property = pi.Name,
Value = pi.GetValue(p.Value)
}))
// Group data with respect to the PropertyName
.GroupBy(x => x.Property, x => x.Value)
// And create proper dictionary
.ToDictionary(x => x.Key, x => x.ToList());
and now you have a Dictionary with keys of ClassB property names and values as List of those properties values.

Efficiently determine the properties of the inner class (Class 2)
Regardless of efficiency, there really is only one way you can do it (assuming you mean at runtime) and that's using Reflection.
Loop through the properties of the inner class (Class 2)
foreach (var prop in instance.GetType().GetProperties(BindingFlags.Public | BindingFlags.Instance))
{
Console.WriteLine(prop.Name);
}
loop through the collection of Class1 objects (listObj1) selecting all instances of the the Class2 property
foreach (var obj in mainClass.listObj1)
{
var innerClasses = obj.dictObj2.Values;
// do something with inner classes
}
output the collection of Class2 property (e.g. the first iteration would return a collection of MeasurementA, one from each Class1 object).
foreach (var prop in typeof(Class2).GetProperties(BindingFlags.Public | BindingFlags.Instance))
{
foreach (var obj in mainClass.listObj1)
{
foreach (var innerClass in obj.dictObj2.Values)
{
Console.WriteLine(prop.GetValue(innerClass, null));
}
}
}
Not sure why you are so keen on using LINQ here, I think a couple of simple for loops are all you need here.

Related

C# - LINQ - Sorting collection by double-nested properties

Let's assume that we have a few classes:
public class SecondNestingLevel
{
public string SortPropety {get; set;}
}
public class FirstNestingLevel
{
public ICollection<SecondNestingLevel> SecondNestingLevelCollection {get; set;}
}
public class Wrapper
{
public ICollection<FirstNestingLevel> FirstNestingLevelCollection {get; set;}
}
I have to sort collection of Wrapper objects by SortPropety (located inside double-nested SecondNestingLevel objects).
Guidelines:
Firstly, aggregate together Wrapper objects, which every value of SortPropety is the same (in every FirstNestingLevel and SecondNestingLevel located inside specific Wrapper object).
Secondly, sort aggregated in this way Wrapper objects in a alphabetic way.
General schema of result collection:
Part 1: Wrappers with the same value of SortProperty sorted in an alphabetic way.
Part 2: Other wrappers sorted in an alphabetic way of SortProperty located in first SecondNestingLevel object which is placed in first FirstNestingLevel object in nested collections.
I would be very grateful for your help.
To order SortProperty I made this example:
var wrapper = new Wrapper()
{
FirstNestingLevelCollection = new List<FirstNestingLevel>()
{
new FirstNestingLevel() { SecondNestingLevelCollection = new List<SecondNestingLevel>()
{
new SecondNestingLevel() { SortPropety = "11" },
new SecondNestingLevel() { SortPropety = "02" },
new SecondNestingLevel() { SortPropety = "05" }
}
}
}
};
Now to order SortPropety you need to go through each element in firstNestingLevel to be able to order SecondNestingLevel.
foreach (var firstNestingLevel in wrapper.FirstNestingLevelCollection)
{
var orderedEnumerable = firstNestingLevel
.SecondNestingLevelCollection.OrderBy(e => e.SortPropety);
foreach (var secondNestingLevel in orderedEnumerable)
{
Console.WriteLine(secondNestingLevel.SortPropety);
}
}
If you need it in Linq, one way to go is using Select and you will get a nested loop of ordered elements:
var orderedEnumerable = wrapper.FirstNestingLevelCollection
.Select(e => e.SecondNestingLevelCollection.OrderBy(e1 => e1.SortPropety));
To print that you can do something like:
foreach (var secondNestingLevels in orderedEnumerable)
{
foreach (var secondNestingLevel in secondNestingLevels)
{
Console.WriteLine(secondNestingLevel.SortPropety);
}
}
Both codes will give the same output:
02
05
11

C# - Generic custom List.Add method to add checks before adding?

I am wondering if something like this is possible. I am looking to create a method that can be called instead of List.Add. The method would check any/all string properties and make sure they don't exceed their specific given max lengths, and if so truncate to proper size. I would ideally like it to be generic so that it will not only work for ObjectA, but also ObjectB, ObjectC, etc.
I am open to any and all suggestions. I know it seems like a weird thing to do, but I have a lot of different objects I am working with and potentially millions of those object instances in totality across all my lists. I mainly just need a way to ensure that any objects with properties exceeding their max string limit are truncated and logged via the Worker class in a timely way. Thanks!
public class ObjectA {
public Guid aID {get; set;}
[MaxLength(128)]
public string aName {get; set;}
[MaxLegnth(30)]
public string aType {get; set;}
}
--
public class Worker {
private void Work() {
List<ObjectA> listOfA = new List<ObjectA>();
listOfA.CustomAddMethod(new ObjectA(new Guid, "Something", "Unknown"));
}
// ??????
private CustomAddMethod(T object) {
foreach property {
if (isStringProperty && isGreaterThanMaxLength) {
// truncate to proper size
// log for truncation message
}
// Then add to list
}
}
}
You can create an extension method.
Here is a code snip. You can improve the performance by implementing a cache, for example, using a dictionary to store the properties and the MaxLengthAttribute based on object's type.
public static class ListExtensions
{
public static void CustomAdd<T>(this List<T> list, T item, Action<string> logger = null)
{
var propertyInfos = typeof(T)
.GetProperties(BindingFlags.Instance | BindingFlags.Public)
.Where(propertyInfo => propertyInfo.PropertyType == typeof(string))
.ToList();
foreach (var propInfo in propertyInfos)
{
var maxLengthAttr = propInfo
.GetCustomAttributes(typeof(MaxLengthAttribute))
.Cast<MaxLengthAttribute>()
.FirstOrDefault();
if (maxLengthAttr is null)
continue;
var currentString = (string)propInfo.GetValue(item);
if (!maxLengthAttr.IsValid(currentString))
{
var newValue = currentString.Substring(0, maxLengthAttr.Length);
logger?.Invoke(
$"Resolving error: {maxLengthAttr.FormatErrorMessage(propInfo.Name)}\n" +
$"Old Value: {currentString}\n" +
$"New Value: {newValue}"
);
propInfo.SetValue(item, newValue);
}
}
list.Add(item);
}
}
Example of usage (code removed for brevity):
public class Person
{
[MaxLength(4)]
public string Name { get; set; }
}
...
var personList = new List<Person>();
personList.CustomAdd(
new Person {Name = "John Doe"},
message => Debug.WriteLine(message)
);
...
As result the Jhon Doe string will be trimmed to Jhon

How do I identify List fields using reflection and add new objects to that list?

I have two objects that have mostly properties with the same type and name, and I have a method that assigns values from object A to the matching properties in object B. I'm writing a unit test which should call that method, and then assert that for the each property in object A that has a matching property in object B, the value was copied.
My idea to accomplish this is to use reflection to enumerate all the properties in object A and then assign a random value to each property. Then I call my "copy values" method to copy the values to object B, and then use reflection to enumerate the fields on each object and make sure that fields with matching names have the same value.
Here is my code to assign random values to object A
var objectA = new ObjectA();
var random = new Random();
var fields = typeof(ObjectA).GetProperties(BindingFlags.Instance | BindingFlags.Public);
foreach (var field in fields)
{
if (field.CanWrite)
{
if (field.PropertyType == typeof(bool) || field.PropertyType == typeof(bool?))
{
field.SetValue(objectA, Convert.ToBoolean(random.Next(2)));
}
if (field.PropertyType == typeof(decimal) || field.PropertyType == typeof(decimal?))
{
field.SetValue(objectA, Convert.ToDecimal(random.Next()));
}
if (field.PropertyType == typeof(string))
{
field.SetValue(objectA, random.Next().ToString());
}
// similar code for other data types
}
}
Then I call my method to copy the values:
ObjectB objectB = ObjectB.FromObjectA(objectA);
Then I call this method to compare the values of the two objects:
public static void AssertMatchingFieldAreEqual<T1, T2>(T1 a, T2 b)
{
foreach (var fieldA in typeof(T1).GetProperties(BindingFlags.Instance | BindingFlags.Public))
{
var fieldInfoB = typeof(T2).GetProperty(fieldA.Name);
if (fieldInfoB != null)
{
var propertyA = typeof(T1).GetProperty(fieldA.Name);
var propertyB = typeof(T2).GetProperty(fieldA.Name);
// Adding field names to make error messages for failed Assert calls list the field name
var valueA = $"{propertyA.Name}: {propertyA.GetValue(a)}";
var valueB = $"{propertyB.Name}: {propertyB.GetValue(b)}";
Assert.AreEqual(valueA, valueB);
}
}
}
This works for basic data types. My problem is that I have some fields that are Lists, and I'd like to populate them with a random number of objects of their type, and then assert that when the fields are copied, each list has the same number of objects.
My two questions:
When I'm assigning values, how do I check if a property is a List without knowing the type of item in the list? I've tried if (field.PropertyType == typeof(List<object>), but that doesn't work.
How do I create a new object of type T add and it to my list when my property type is a list?
Or alternatively if there's a better way to check that my "copy values" method does copy all identically named fields, what's the better way?
The direct answer to your question is that you'd need to incorporate generic testing into your reflection.
if(field.PropertyType.GetGenericTypeDefinition() == typeof(List<>)){
var typeOfThingInside = field.PropertyType.GetGenericArguments()[0];
// ...
}
The better way is to use AutoFixture to create your objects, and use Fluent Assertions to check that everything got copied.
void Main()
{
// Arrange
var fixture = new Fixture();
var a = fixture.Create<ObjectA>();
// Act
var b = ObjectB.FromObjectA(a);
// Assert
b.Should().BeEquivalentTo(a, options => options.ExcludingMissingMembers());
}
public class ObjectA {
public int A {get;set;}
public string B {get;set;}
public List<string> C {get;set;}
public decimal Z {get;set;}
}
public class ObjectB
{
public int A { get; set; }
public string B { get; set; }
public List<string> C { get; set; }
public decimal Y { get; set; }
public static ObjectB FromObjectA(ObjectA a) => JsonConvert.DeserializeObject<ObjectB>(JsonConvert.SerializeObject(a));
}

How do I ignore all hidden properties of class being inherited using reflection?

Im having an issue where I need to get all properties from an object, then sort through the properties and send the values of certain properties to another service. Here is an example of the code:
public class Class1
{
public string A { get; set; }
public bool B { get; set; }
}
public class Class2 : Class1
{
public new bool? B { get; set; }
public bool C { get; set; }
}
I need to get all the properties of Class2, however when using Class2.GetType().GetProperties(), the result contains B from Class2 AND Class1. This causes my issue as when looping through each property I am sending B twice, one with the default value of false since it was never set, then the other with the proper value that was set by my service. I need the result to contain B from Class2, A from Class1, and C from Class2 but ignore B from Class1 since it has been hidden with the new keyword.
I have tried looking through the binding flags I can use, but it has not helped. The closest flag I can find is the BindingFlags.DeclaredOnly flag, but that excludes A from Class1, so it will not work for me.
How would I go about ignoring the original property if it has been hidden?
You could use a LINQ query to filter out the hidden properties.
var allProps = typeof(Class2).GetProperties(
BindingFlags.Instance | BindingFlags.Public
);
var thePropsYouWant =
from p in allProps
group p by p.Name into g
select g.OrderByDescending(t => t.DeclaringType == typeof(Class2)).First();
See it running here: https://dotnetfiddle.net/V5sGIs
If I understand you right you need all properties from Class2 and all properties from Class1 that not redefined in Class2
You can achive this with two calls to GetProperties: first select all defined in Class2 then visit type of Class1 and add any that missing
var type = typeof(Class2);
var list = new List<PropertyInfo>();
list.AddRange(type.GetProperties(BindingFlags.Public | BindingFlags.Instance | BindingFlags.DeclaredOnly));
var baseType = type.BaseType;
if (baseType != null)
{
foreach (var propertyInfo in baseType.GetProperties())
{
if (list.All(p => p.Name != propertyInfo.Name))
list.Add(propertyInfo);
}
}
If you print that list
foreach (var propertyInfo in list)
Console.WriteLine($"From {propertyInfo.DeclaringType} > '{propertyInfo.Name}':{propertyInfo.PropertyType}");
You will see something like:
From Class2 > 'B':System.Nullable`1[System.Boolean]
From Class1 > 'A':System.String
A simple LINQ based solution which works in a generic way for all class hierarchies:
var propertiesWithoutHiddenOnes = GetProperties(objectToUpdate.GetType())
.GroupBy(prop => prop.Name)
.Select(group => group.Aggregate(
(mostSpecificProp, other) => mostSpecificProp.DeclaringType.IsSubclassOf(other.DeclaringType) ? mostSpecificProp : other))

looping through nested list of object to find properties in c#

I have an object model like this:
public class MyClass
{
int TheProp {get;set;}
List<Object1> TheListOfObject1 {get;set;}
List<Object2> TheListOfObject2 {get;set;}
List<Object3> TheListOfObject3 {get;set;}
List<Object4> TheListOfObject4 {get;set;}
}
Now some the the nested objects contain a property called MyField and some of the lists might have objects in them and some don't.
I'm looking to loop through every list and if the objects in the list contain the property MyField then set that property to 1.
What's the best way to do this? I could hard-code the loop and only loop through the lists for which I know there's the property I'm looking for and then test if the list has a count > 0 but if I then add another list of nested object I'd have to rewrite that part too. What's a more general way to do it?
Thanks.
Either use reflection or write an interface containing that property
public interface IProperty {
int Prop { get; set; }
}
and implement it in all your classes which have that property. Then you can use
foreach (var obj in myList.OfType<IProperty>()) {
obj.Prop = 1;
}
The question here is: Do you really need to have different lists? Let us assume that your ObjectX classes all derive from a common base class BaseObject.
class BaseClass {
int Prop { get; set; }
// possibly other members here
}
class Object1 : BaseClass {
}
class Object2 : BaseClass {
}
// And so on
Then you could either add all your objects to the same list:
List<BaseClass> list = new List<BaseClass>();
list.Add(new Object1());
list.Add(new Object2());
Or you could have a list of lists:
List<List<BaseClass>> lists = new List<List<BaseClass>>();
lists.Add(new List<BaseClass>());
lists.Add(new List<BaseClass>());
lists[0].Add(new Object1());
lists[1].Add(new Object2());
Using nested loops you can access all your objects at once:
foreach (List<BaseClass> lst in lists) {
foreach (BaseClass obj in lst) {
if (obj != null) {
obj.Prop = 1;
}
}
}

Categories