1

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]
dotNetkow
  • 4,365
  • 4
  • 33
  • 48
  • 3
    `foreach` hides an enumerator, and [_"the concept of an index is foreign to the concept of enumeration"_](http://stackoverflow.com/questions/43021/how-do-you-get-the-index-of-the-current-iteration-of-a-foreach-loop), so I guess you're out of luck. – CodeCaster Dec 02 '15 at 00:42
  • Objects don't have indexes... (or parents, or arrays they are stored in, IEnumerable have no notion of search either). So not very clear how you expect to use reflection to find something that does not exist. – Alexei Levenkov Dec 02 '15 at 00:46
  • What are the types of `allSales` and `sale.orders.detail.discounts`? – Adam Schiavone Dec 02 '15 at 00:48
  • It's not so much the indexes... it's also the object path. I'll be needing this in multiple locations, so if I can pass GetViaReflection(discount) which returns 'allSales[1].sale.orders.detail.discounts[0]' or GetViaReflection(differentObject) that would be great. Avoiding having to hardcode the object graph part. – dotNetkow Dec 02 '15 at 00:58

3 Answers3

1

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}]")
        }
    }
}
Adam Schiavone
  • 2,202
  • 3
  • 27
  • 61
  • yep, all IEnumerable. see my question edits, I clarify further that the goal of this would be reuse. Thanks! – dotNetkow Dec 02 '15 at 01:02
  • 1
    I just saw that after I hit submit! Ah well. I think this answers your original question, but the generic case of this (involving reflection) will probably come in somebody else's answer. – Adam Schiavone Dec 02 '15 at 01:03
  • Your answer is the simplest way to go. I was inspired by the FluentValidation library that does this & wanted to recreate it. Turns out it builds a "property chain", built up as it goes downward. In essence: some reflection, but no magic bullet. – dotNetkow Dec 02 '15 at 18:32
1

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.

Community
  • 1
  • 1
Adam Schiavone
  • 2,202
  • 3
  • 27
  • 61
-1

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 }]"
Sten Petrov
  • 10,338
  • 1
  • 40
  • 56