dotNetkow
dotNetkow

Reputation: 5313

Get full object graph string C#

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

Answers (3)

Sten Petrov
Sten Petrov

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

Adam Schiavone
Adam Schiavone

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

Adam Schiavone
Adam Schiavone

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

Related Questions