C# How to access a brother class from within a class function? - c#

I have a code structured like the one below. I simplified it with just the strictly needed to pose the question.
I have a Parent class, which includes an Item1 class, Item2 class, Item1_to_Item2_relationship class. I must keep this structure for reasons not relevant to this problem.
How can I access a value in Item1 from Item2?
The code explains better what needs to be done.
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
namespace ConsoleApp3
{
class Program
{
static void Main(string[] args)
{
Parent parent = new Parent();
// Adding item1 values
Item1 item1_01 = new Item1();
item1_01.id = "item1_01";
item1_01.item1_code = "CODE_01";
parent.items1.Add(item1_01);
Item1 item1_02 = new Item1();
item1_02.id = "item1_02";
item1_02.item1_code = "CODE_02";
parent.items1.Add(item1_02);
// Adding item2 values
Item2 item2 = new Item2();
item2.id = "item2_01";
parent.items2.Add(item2);
// Adding relationships
Item1_to_Item2_Relationship item1_to_Item2_Relationship = new Item1_to_Item2_Relationship();
item1_to_Item2_Relationship.item1.id_alt = item1_01.id;
item1_to_Item2_Relationship.item2.id_alt = item2.id;
parent.Item1_to_Item2_Relationships.Add(item1_to_Item2_Relationship);
item1_to_Item2_Relationship = new Item1_to_Item2_Relationship();
item1_to_Item2_Relationship.item1.id_alt = item1_02.id;
item1_to_Item2_Relationship.item2.id_alt = item2.id;
parent.Item1_to_Item2_Relationships.Add(item1_to_Item2_Relationship);
// How to make the code below return a List<string> with the values "CODE_01" and "CODE_02"?
foreach (Item2 my_item2 in parent.items2)
{
my_item2.item1_codes;
}
}
}
class Parent
{
public List<Item1> items1;
public List<Item2> items2;
public List<Item1_to_Item2_Relationship> Item1_to_Item2_Relationships;
public Parent()
{
items1 = new List<Item1>();
items2 = new List<Item2>();
Item1_to_Item2_Relationships = new List<Item1_to_Item2_Relationship>();
}
}
class Item1
{
public string id;
public string id_alt;
public string item1_code;
public Item1()
{
id = "";
item1_code = "";
}
}
class Item2
{
public string id;
public string id_alt;
public Item2()
{
id = "";
}
}
class Item1_to_Item2_Relationship
{
public Item1 item1;
public Item2 item2;
public Item1_to_Item2_Relationship()
{
item1 = new Item1();
item2 = new Item2();
}
}
}
At the moment, I would have to right a static function which receives a Parent parameter and do the logic there. But I believe there should be a better more intuitive way. How can I make the above code work?

As mentioned in other answers you can access it directly. Since you are maintaining a relationship, I assume you want to do it via the relationship
var test = new List<String>();
foreach (Item2 my_item2 in parent.items2)
{
foreach (var item in parent.Item1_to_Item2_Relationships)
{
//implement your own equality comparision if it should be differernt
if (item.item2.id_alt == my_item2.id)
{
test.Add(item.item1.item1_code);
}
}
}
Few important points:
You are dealing with list and nested loops, the performance of the
approach is not best.
To improve the performance the relationship data structure should be
chosen wisely, is it possible to convert it to Dictionary, thus
giving quicker lookup times.
The code currently will add blank string since you do not add the Code in the relationship, but you can always search for object using the id, that means another search in list, this is the reason I am saying that you might want to change the underlying data structure.
It might be better to store the individual objects also in a dictionary if their item code is the primary key.

The fastest and easiest way to do this would be to pass in parent into the function in which itemX would need to access a property from itemY.
For instance:
class Item1
{
public string id;
public string id_alt;
public string item1_code;
public Item1()
{
id = "";
item1_code = "";
}
}
class Item2
{
public string id;
public string id_alt;
public Item2()
{
id = "";
}
public void AccessItem1Prop(Parent parent) {
string item1ID = parent.items1[0].id;
}
}
You could also pass in parent to the individual items constructor and attach it to the item as an object property, but I'd avoid that approach if you can simply to keep things cleaner.

I'm not a fan of how the code is structured... but here's the simple answer.
You're adding two different item types to two different collections.
You're making a relationship item, that relates the two based on id.
Although your code screams help the idea is logical and a good way to relate items such as working with a database.
We can do this simply with Linq but I would rather stick closer to how you're writing the question.
First iterate your items like you're doing, then iterate your relationships. Based on Id comparison you then iterate the other items and based on that final Id comparison you get your answer.
foreach (Item2 my_item2 in parent.items2)
{
foreach (Item1_to_Item2_Relationship relatedItem in parent.Item1_to_Item2_Relationships)
{
if (my_item2.id == relatedItem.item2.id_alt)
{
foreach (Item1 item1 in parent.items1)
{
if (relatedItem.item1.id_alt == item1.id)
{
Console.WriteLine(item1.item1_code);
}
}
}
}
}
//Outputs
//CODE_01
//CODE_02

'just do it'
var i1 = new Item1();
var i2 = new Item2();
i1.id = i2.id;
Now others will argue that you should use properties rather than public fields. But what you have at the moment allows you to do exactly what you want.

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

Trying to use reflection to concatenate lists of objects

I have below class
public class HydronicEquipment
{
public List<LibraryHydronicEquipment> Source { get; set; }
public List<LibraryHydronicEquipment> Distribution { get; set; }
public List<LibraryHydronicEquipment> Terminals { get; set; }
}
and then i have the below class for "libraryHydronicEquipment"
public class LibraryHydronicEquipment : IEquipmentRedundancy
{
public string Name { get; set; }
public RedundancyStatus RedundancyStatus { get; set; }
public EquipmentRedundancy EquipmentRedundancy { get; set; }
}
I am trying to concatenate the list of "LibraryHydronicEquipment" objects available from all three properties (i.e) from source, distribution and terminal and General concatenate method will looks like as this below
var source = hydronicEquipment.Source;
var distribution = hydronicEquipment.Distribution;
var teriminals = hydronicEquipment.Terminals;
Source.Concat(Distribution).Concat(Terminals)
I am trying to achieve the same using reflection and the code looks like as below
foreach (var (systemName, hydronicEquipment) in hydronicSystemEquipment)
{
bool isFirstSystem = true;
var equipmentList = new List<string> { "Source", "Distribution", "Terminals" };
var redundancyequipmentList = GetRedundancyEquipment(hydronicEquipment, equipmentList);
}
and the method GetRedundancyEquipment is looks like below
private static IEnumerable<IEquipmentRedundancy> GetRedundancyEquipment(HydronicEquipment hydronicEquipment, List<string> equipmentList)
{
IEnumerable<IEquipmentRedundancy> equipmentRedundancies = new List<IEquipmentRedundancy>();
dynamic equipmentResults = null;
foreach(var equipment in equipmentList)
{
var componentList = hydronicEquipment.GetType().GetProperty(equipment).GetValue(hydronicEquipment, null) as IEnumerable<IEquipmentRedundancy>;
equipmentResults = equipmentRedundancies.Concat(componentList);
}
return equipmentResults;
}
The problem here is even though i have Source is having list of objects and Distribution is having list of objects, the equipmentResults is giving only one object instead of list of concatenated objects.
I am trying to return the IEnumerable<IEquipmentRedundancy> at the end using reflection method but it seems not working with the above code.
Could any one please let me know how can i achieve this, Many thanks in advance.
GetRedundancyEquipment should preserve your values instead of reassign the reference with each iteration. Here's the fixed version:
private static IEnumerable<IEquipmentRedundancy> GetRedundancyEquipment(HydronicEquipment hydronicEquipment, List<string> equipmentList)
{
IEnumerable<IEquipmentRedundancy> equipmentRedundancies = new List<IEquipmentRedundancy>();
var equipmentResults = new List<IEquipmentRedundancy>();
foreach (var equipment in equipmentList)
{
var componentList = hydronicEquipment.GetType().GetProperty(equipment).GetValue(hydronicEquipment, null) as IEnumerable<IEquipmentRedundancy>;
equipmentResults.AddRange(equipmentRedundancies.Concat(componentList));
}
return equipmentResults;
}
If we look at what you're doing in GetRedundancyEquipment() it becomes clear.
First you create equipmentRedundancies = new List<IEquipmentRedundancy>();
Then you never modify equipmentRedundancies - e.g. via Add(). It remains an empty list until it goes out of scope and is garbage collected.
In a loop you then repeatedly make this assignment equipmentResults = equipmentRedundancies.Concat(componentList);
That is to say: Assign to equipmentResults the concatenation of componentList to equipmentRedundancies.
Note that Concat() is a lazily evaluated linq method. When you actually enumerate it results are produced. It doesn't modify anything, it's more like a description of how to produce a sequence.
So each time through the loop you're assigning a new IEnumerable that describes a concatentaion of an empty list followed by the property that you retrieved with reflection to equipmentResults. Then at the end you return the final one of these concatenations of an empty list and retrieved property.
If you want all of them together, you should concatenate each of them to the result of the previous concatenation, not to an empty list.

Setting properties for each sub element of each sub element

I have a set of classes I populate from a 3rd party XML (so I can't really control the structure that much):
public class Category
{
// some properties...
public Tournament[] Tournaments {get;set;}
}
public class Tournament
{
// some properties...
public Match[] Matches {get;set;}
}
public class Match
{
// some properties...
public Competitors Competitors {get;set;}
}
public class Competitors
{
// some properties...
public Teams[] Teams {get;set;}
}
and so on.
Now, I need on the team level to set some properties from different classes -
for instance, I need the id of the category, the gender of the tournament etc.
So for now, I have a foreach loop that sets the category id for each tournament, and later on in the code I have 3 nested foreach loops to copy values from properties of the tournaments to properties the teams.
foreach (var tournament in tournaments)
{
foreach (var match in tournament.Matches)
{
match.SomeProperty = tournament.SomeProperty;
foreach (var team in match.Competitors.Teams)
{
team.CategoryId = tournament.CategoryId;
team.Gender = tournament.Gender;
// and a few more here...
}
}
}
Needless to say this is quite an ugly code.
I was wondering if there is a better way to do it, perhaps using LINQ or something like that.
Update:
For now I have changed my code so that each class has a reference to it's parent. This way I don't need to set any more properties down the chain.
Instead of having team.CategoryId I'm now using team.Match.Tournament.Category.Id
However, I still have have the same problem, only now it's concentrated in only one place - After the deserialization is completed, I have 4 nested foreach loops - so it looks like this:
foreach (var category in MainClass.Categories)
{
category.FileName = MainClass.FileName;
foreach (var tournament in category.Tournaments)
{
tournament.Category = category;
foreach (var match in tournament.Matches)
{
match.Tournament = tournament;
foreach (var team in match.Fixture.Competitors.Teams)
{
team.Match = match;
}
}
}
}
It would be nice to find a way to avoid these nested loops...
You can merge the both inner foreach with the SelectMany-LINQ operation.
foreach (var tournament in tournaments)
{
//// We take every team in every competitors to on Enumeration
foreach (var team in tournament.Matches.SelectMany(match => match.Competitors.Teams))
{
team.CategoryId = tournament.CategoryId;
team.Gender = tournament.Gender;
}
}
Edit:
When you set an property at the first foreach, the SelectMany will not work (because the projection will only hold all teams from the matchs Competitors).
One way to make it a little bit nicer, would be to extract a method
foreach (var tournament in tournaments)
{
SetMatchPropertiesFromTournament(tournament);
}
...
private void SetMatchPropertiesFromTournament(Tournament tournament)
{
foreach (var match in tournament.Matches)
{
match.SomeProperty = tournament.SomeProperty;
foreach (var team in match.Competitors.Teams)
{
team.CategoryId = tournament.CategoryId;
team.Gender = tournament.Gender;
// and a few more here...
}
}
}
A nice, small function, which just does only one thing... thats a great thing!

How do I add to an ICollection property when iterating object type?

I have need to add to an ICollection<string> property of a class of which I have an IEnumerable of. Here is a complete program which illustrates the problem:
using System;
using System.Collections.Generic;
using System.Linq;
namespace CollectionAddingTest
{
public class OppDocumentServiceResult
{
public OppDocumentServiceResult()
{
this.Reasons = new List<string>();
}
public Document Document { get; set; }
public bool CanBeCompleted
{
get
{
return !Reasons.Any();
}
}
public ICollection<string> Reasons { get; private set; }
}
public class Document
{
public virtual string Name { get; set; }
}
public class Program
{
private static void Main(string[] args)
{
var docnames = new List<string>(new[] {"test", "test2"});
var oppDocResult = docnames
.Select(docName
=> new OppDocumentServiceResult
{
Document = new Document { Name = docName }
});
foreach (var result in oppDocResult)
{
result.Document.Name = "works?";
result.Reasons.Add("does not stick");
result.Reasons.Add("still does not stick");
}
foreach (var result in oppDocResult)
{
// doesn't write "works?"
Console.WriteLine(result.Document.Name);
foreach (var reason in result.Reasons)
{
// doesn't even get here
Console.WriteLine("\t{0}", reason);
}
}
}
}
}
I would expect that each OppDocumentServiceResult would have its referenced Document.Name
property set to works? and each OppDocumentServiceResult should have two reasons added to it. However, neither is happening.
What is special about the Reasons property that I cannot add things to it?
The problem is that oppDocResult is the result of a LINQ query, using deferred execution.
In other words, every time you iterate over it, the query executes and new OppDocumentServiceResult objects are created. If you put diagnostics in the OppDocumentServiceResult constructor, you'll see that.
So the OppDocumentServiceResult objects you're iterating at the end are different ones to the ones you've added the reasons to.
Now if you add a ToList() call, then that materializes the query into a "plain" collection (a List<OppDocumentServiceResult>). Each time you iterate over that list, it will return references to the same objects - so if you add reasons the first time you iterate over them, then you print out the reasons when you iterate over them again, you'll get the results you're looking for.
See this blog post (among many search results for "LINQ deferred execution") for more details.
The issue is your initial Select you are instantiating new OppDocumentServiceResult objects. Add a ToList and you should be good to go:
var oppDocResult = docnames
.Select(docName
=> new OppDocumentServiceResult
{
Document = new Document { Name = docName }
}).ToList();
As Servy pointed out I should have added a bit more detail to my answer, but thankfully the comment he left on Tallmaris' answer takes care of that. In his answer Jon Skeet further expands on the reason, but what it boils down to "is that oppDocResult is the result of a LINQ query, using deferred execution."
Fixed like this, converting to List instead of keeping the IEnumerable:
var oppDocResult = docnames
.Where(docName => !String.IsNullOrEmpty(docName))
.Select(docName
=> new OppDocumentServiceResult
{
Document = docName
}).ToList();
I can only guess (this is a shot in the dark really!) that the reason behind this is that in an IEnumerable the elements are like "proxies" of the real elements? basically the Enumerable defined by the Linq query is like a "promise" to get all the data, so everytime you iterate you get back the original items? That does not explain why a normal property still sticks...
So, the fix is there but the explanation I am afraid is not... not from me at least :(
ForEach() is defined against only List<T> you will not be able to use it to for an ICollection<T>.
You have to options:
((List<string>) Reasons).ForEach(...)
Or
Reasons.ToList().ForEach(...)
Yet, my preferred approach
I would define this extension which can help automating this for you without wasting resources:
public static class ICollectionExtensions
{
public static void ForEach(this ICollection<T> collection, Action<T> action)
{
var list = collection as List<T>;
if(list==null)
collection.ToList().ForEach(action);
else
list.ForEach(action);
}
}
Now I can use ForEach() against ICollection<T>.
Just change your code inside your class
public List<string> Reasons { get; private set; }

Create a list of objects and display them in the console through another class

For a homework assignment i need to create a List<T>, add objects to it, and display the objects toString. Here is what i have:
public class Order
{
...
public List<OrderLine> orderLines;
public Order(User user1)
{
...
orderLines = new List<OrderLine>();
}
public void AddOrderLine(OrderLine newOrderLine)
{
orderLines.Add(newOrderLine);
}
}
public class OrderLine
{
public OrderLine(Product p1, int q1)
{
...
}
public override string ToString() //Required by teacher
{
string myOrderLine;
myOrderLine = String.Format("[Product name: {0}; Price: {1}; Quantity: {2}]",
product.name, product.price, quantity);
return myOrderLine;
}
}
When adding oerderLines.Add(newOrderLine) new OrderLine is an object created in another class that has a working toString. How do i display all the newOrderLines from the Program.CS?
I assume your main method looks something like this:
{
User user = new User();
Order order = new Order(user);
OrderLine orderLine1 = new OrderLine();
order.AddOrderLine(orderLine1);
}
If I understand what you say correctly, OrderLine already overrides ToString(), so that when you call ToString(), it returns something "human-meaningful". In that case, the only thing you need to do is something along the lines of
foreach (var orderLine in order.orderLines)
{
Console.WriteLine(orderLine.ToString());
}
Note that it is considered good practice not to publicly expose a field, like orderLines. Better make it private, and create a public property like:
public IEnumerable<OrderLine> OrderLines
{
get { return this.orderLines; }
}
Assuming you have the working .ToString() for the OrderLine class. If you just want to display them to the console, you can do a simple:
foreach(OrderLine orderline in orderLines)
System.Console.WriteLine(orderline.ToString());
edit:
Two simple ways to do this is either the way Mathias outlined quite nicely or put my code inside your Order class. For example, add a new method like this:
public void PrintOrders()
{
foreach(OrderLine orderline in this.orderLines)
System.Console.WriteLine(orderline.ToString());
}
You would use it like:
User user = new User();
Order order = new Order(user);
OrderLine orderLine = new OrderLine();
order.AddOrderLine(orderLine);
order.PrintOrders();
Please note that it is generally not considered good practice to have classes printing to the console. I just wanted to show you a different way of approaching the problem.
if you have something like this in your Program.cs to populate your Order object
var order = new Order();
order.AddOrderLine(...);
//this is how you can display each item ToString in the collection
foreach(var o in order.orderLines)
{
Console.WriteLine(o.ToString());
}
just check for any typo or syntax error as I only typed this here inline
Is this what you need?
public static void Main(string[] args)
{
Order order = new Order();
OrderLine line1 = new OrderLine(...);
OrderLine line2 = new OrderLine(...);
OrderLine line3 = new OrderLine(...);
order.AddOrderLine(line1);
order.AddOrderLine(line2);
order.AddOrderLine(line3);
foreach (OrderLine line in order.orderLines)
{
Console.WriteLine(line.ToString());
}
}

Categories