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]
4
  • 3
    foreach hides an enumerator, and "the concept of an index is foreign to the concept of enumeration", so I guess you're out of luck. Commented Dec 2, 2015 at 0: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. Commented Dec 2, 2015 at 0:46
  • What are the types of allSales and sale.orders.detail.discounts? Commented Dec 2, 2015 at 0: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. Commented Dec 2, 2015 at 0:58

3 Answers 3

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}]")
        }
    }
}
Sign up to request clarification or add additional context in comments.

3 Comments

yep, all IEnumerable<T>. see my question edits, I clarify further that the goal of this would be reuse. Thanks!
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.
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.
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.

Comments

-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 }]"

Comments

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.