Reputation: 5313
I'm looping through various collections and if a particular error condition is met, then I need the full object graph, i.e. which index has the issue.
Sample Code:
foreach (var sale in allSales) {
foreach (var discount in sale.orders.detail.discounts) {
if (errorConditionMet) {
// print full object graph. For example, perhaps it's the second Sale (index one), but first discount object (index zero):
// We have "discount" object, but want to print:
// allSales[1].sale.orders.detail.discounts[0]
}
It's possible to just maintain counters (and is perhaps more performant):
string.Format("allSales[{0}].sale.orders.detail.discounts[{1}]", saleCount, discountCount);
// prints: allSales[1].sale.orders.detail.discounts[0]
but I'm wondering if this is possible with C# Reflection? I'll need this in multiple classes, so it would be great to pass an object to a method and return the object graph, totally dynamic:
var resultOne = GetViaReflection(discount);
// returns: allSales[1].sale.orders.detail.discounts[0]
var resultTwo = GetViaReflection(currentAnimal);
// returns: animals[3].animal.types[2]
Upvotes: 1
Views: 731
Reputation: 11040
If we set aside the obvious issue that allSales
can change and make the index useless for a second....
var salesWithErrors = allSales.Select((sale,saleIdx =>
new { Sale = sale, // not really needed for the particular example
Index = saleIdx,
DiscountsWithErrors = sale.orders.detail.discounts
.Select((d,i)=>new {
Discount = d,
Index = i,
})
.Where(di=>isErrorConditionMet(d.Discount))
})
.Where(saleDiscountErrors => saleDiscountErrors.DiscountsWithErrors.Any())
var results = string.Join(Environment.NewLine,
salesWithErrors.SelectMany(sde=>sde.DiscountsWithErrors.Select(d=>new{
SaleId = sde.Sale.Id,
SaleIndex = sde.Index,
DiscountId = d.Discount.Id
DiscountIndex = d.Index
})
.Select(sdi=>$"allSales[{sdi.SaleIndex}].sale.orders.detail.discounts[{sdi.DiscountIndex}]"));
Instead of outputting indexes within the ephemeral collection you could (should) instead output IDs of objects that are more durable and let you find them in your database,
...
.Select(sdi=>$"allSales[{sdi.SaleId}].sale.orders.detail.discounts[{sdi.DiscountId }]"
Upvotes: -1
Reputation: 2452
Just thinking about this, but I don't think this is possible in the way you are thinking.
I had a SO question similar to this a while back that was looking for method names. The saving grace there was that I could go back up the call stack, but I don't believe there is anything like the call stack to the object hierarchy. Objects simply don't know anything about those other objects which have references to them. For example:
public class A {
public List<B> Objects { get; set; }
}
public class B {
public B(int i) { }
//blah
}
public static void Main(string[] args)
{
//Establish a simple object Heiarchy
//Root: A
/*
A
|-B1
|-B2
|-B3
*/
var alpha = new A()
{
Objects = new List<B>()
{
new B(1),
new B(2),
new B(3)
}
}
//MagicMethod<T>(object objectToTrace) is the mythical method that we're looking for
//What you're looking for is something like this:
MagicMethod(alpha.Objects[1]); //Should output "alpha.Objects[1]"
//But what if we make another reference to the same object?
var beta = alpha.Objects[1];
//Now what would MagicMethod() produce?
MagicMethod(beta); //would have to produce "beta"
//How is it possible that when We've called MagicMethod() with
//fundamentally the same argument, we get two different outputs?
}
As you can see, our MagicMethod()
cant possibly know which reference it should be printing. So even if an object had a record off all the places in which a reference to itself were held it could not possibly pick the right one.
I hope I was able to convey my line of thinking to you. I'll say it here: I have no idea if this is true, but I just can't imagine a way that it could be true.
Upvotes: 1
Reputation: 2452
Use a regular for loop?
I dont know the types of allSales
and sale.orders.detail.discounts
but I think its safe to assume they are at least IEnumerable<T>
And List<T>
will get us some more features from an IEnumerable<T>
//If I wrap the IEnumerable in a list I get access to an indexer and count
var allSalesList = new List<T>(allSales);
for (int i = 0; i < allSalesList.Count; i++) {
var sale = allSales[i];
//If I wrap the IEnumerable in a list I get access to an indexer and count
var discounts = new List<T>(sale.orders.detail.discounts);
for (int j = 0; i < discounts.Count; j++) {
var discount = discounts[j];
if (errorConditionMet) {
// print full object graph. For example, perhaps it's the second Sale (index one), but first discount object (index zero):
// We have "discount" object, but want to print:
// allSales[1].sale.orders.detail.discounts[0]
//Your formatting here. I'm using C# 6 string interpolation, but its basically a string format.
Console.WriteLine($"allSales[{i}].sale.orders.detail.discounts[{j}]")
}
}
}
Upvotes: 1