tags:

views:

80

answers:

2

Following up on a previous question, here is my general class structure..

http://stackoverflow.com/questions/3711224/combine-elements-in-a-list-based-on-type-and-summate-their-values-linq

I am trying to combine the items using their respective .Add methods, but because of Entity Framework considerations, I can only deal with base types (Not interfaces).

 using System.Linq;
using System.Collections.Generic;

interface Item<T>
{
    T Add(T item);
}
abstract class Item
{
    public ItemType Type;
    public int Value;
}

class SubItemOne : Item, Item<SubItemOne>
{
    public int Additional { get; set; }
    public SubItemOne Add(SubItemOne item)
    {
        // ..
    }
}

class SubItemTwo : Item, Item<SubItemTwo>
{
    public int MoreData { get; set; }
    public int OtherData { get; set; }

    public SubItemTwo Add(SubItemTwo item)
    {
        // ...
    }
}

class SubItemThree : Item, Item<SubItemThree>
{
    public int StillOtherData { get; set; }
    public int SecondaryData { get; set; }
    public int TertiaryData { get; set; }

    public SubItemThree Add(SubItemThree item)
    {
        // ...
    }
}

class ItemType
{
    public string Name;
}

class Test
{
    public static void Main()
    {
        List<ItemType> types = new List<ItemType>();
        types.Add(new ItemType { Name = "Type1" });
        types.Add(new ItemType { Name = "Type2" });
        types.Add(new ItemType { Name = "Type3" });

        List<Item> items = new List<Item>();

        for (int i = 0; i < 10; i++)
        {
            items.Add(new SubItemOne
            {
                Type = types.Single(t => t.Name == "Type1"),
                Additional = i
            });
        }

        for (int i = 0; i < 10; i++)
        {
            items.Add(new SubItemTwo
            {
                Type = types.Single(t => t.Name == "Type2"),
                MoreData = 10,
                OtherData = i + 10
            });
        }

        for (int i = 0; i < 10; i++)
        {
            items.Add(new SubItemThree
            {
                Type = types.Single(t => t.Name == "Type3"),
                SecondaryData = 1000,
                StillOtherData = 1874,
                TertiaryData = i * 10
            });
        }

        List<Item> combined = new List<Item>();

        // create a list with 3 items, one of each 'type', with the sum of the total values of that type.
        // types included are not always known at runtime.

        // I am trying to invoke the .Add Method on the items so that they can be collatted together. However
        // they all return as the base type.
    }
}
+1  A: 

The problem is that you're going to have to deal with the interfaces at a higher level, and thus won't be able to do so generically (i.e., using the generic type arguments). You're going to have to define a non-generic interface (even if it's in addition to your current generic interface) that these classes implement in order to do what you're after. Item<SubItemOne> is no more related to Item<SubItemTwo> in terms of assignment compatibility than string is to DateTime.

If you must do it this way, then you'll have to use reflection. This should do the trick:

Dictionary<ItemType, Item> sum = new Dictionary<ItemType, Item>();

foreach (var item in items)
{
    Item currSum;

    if (sum.TryGetValue(item.Type, out currSum))
    {
        sum[item.Type] = (Item)item.GetType().GetInterfaces().Single(
            i => i.Name == "Item").GetMethod("Add")
        .Invoke(currSum, new object[] { item });
    }
    else
    {
        sum.Add(item.Type, item);
    }
}

List<Item> combined = sum.Values.ToList();
Adam Robinson
Right. But it still treats it as the base object during combining. I can't figure out how to get it to call the respective classes additive method even if I cast to a base interface.
Stacey
@Stacey: Check my edit and see if that helps.
Adam Robinson
I think this should work. Thanks. I'm going to have to work with it some to try and make sure I understand exactly what is going on.
Stacey
@Stacey: Could you explain exactly what you mean by "it still treats it as the base object during combining"?
Adam Robinson
Well, the base object is abstract, but the collection is an ICollection<Item>. So from Linq operations, it returns collections of Item. So it isn't 'aware' that it isn't really the base item, though if I run the debugger, it is still the base item at runtime. I basically need to just figure out how to invoke the Item<T>.Add() method, but because I cannot get the <T> part, I cannot perform the appropriate casting.
Stacey
My other thought is to build an entire "Combiner" class that takes things marked as ICombinable. But even then, the base object is abstract and therefore cannot have the right methods.. but it is the base object that has to be passed through the collections!
Stacey
@Stacey: The problem you describe is the reason that I had to use reflection in my code.
Adam Robinson
Yeah, back to my ignorance. "One man's clarity is another's wtf". Your sample worked for my little peon 'sample', but in my actual program it is failing me. My real code is too vast and complex to post, and furthermore a lot of it I really don't want to put up public. I am having a difficult time understanding what your code does. Is there any chance you can help me better comprehend the approach you're taking, here?
Stacey
@Stacey: Sure: Because you can't cast in order to access the `Add` method, I'm inspecting the runtime type of the `item` object (which would be one of your three concrete types), then finding an interface that it implements whose name is `Item` (to get the generic interface). I then look for the `Add` method on that interface and invoke it on the `currSum` object, passing in the `item` object as its argument. Does this make sense?
Adam Robinson
But the only interface I have implemented is Item<T>, I don't have an "Item" interface. How does that work?
Stacey
I wish there was a better way to do this without reflection. I can't imagine what it would be , though.
Stacey
@Stacey: The "name" of the interface is `Item`, but it takes a generic argument `T`. While the `<T>` is part of the *signature*, it isn't part of the name. Unfortunately, I do not believe that there is a solution that does not use reflection.
Adam Robinson
@Adam Robinson - While it's still in the same vein, would casting to dynamic and calling Add() here be an option? I've seen that recommended in .Net 4.0-specific reflection scenarios, and because there are not any other interfaces that also define an Add() method there should be no ambiguity at runtime.
arootbeer
@arootbeet: That may very well be an option. I haven't had a chance to play around with `dynamic` yet, so I couldn't say for sure, but it does seem like it would make life easier.
Adam Robinson
@arootbeet: That worked! Oh my god, thank you both so much. I learned a little more about reflection, and about the dynamic system. To clarify, I have to cast both the object AND the parameter to dynamic so that it goes in properly.
Stacey
Though I am not happy having "Microsoft.CSharp.dll" staring at me in my References folder. Me and my OCD do not approve.
Stacey
I have included a full summary of the entire solution below.
Stacey
This takes care of the add Method. Any idea on how to get all of the results into a separate list of just a few items with the right class types and their results?
Stacey
grouped.SelectMany(x => x, g => Select(i => (ExplicitCastFromItem<T>)i)? I don't remember the SelectMany syntax off the top of my head, but basically you tell it how to get to the collections you want to aggregate, and then select from each of them. You'll want to perform an explicit cast to the type you're looking for.
arootbeer
+1  A: 

To detail on how the problem was solved. I would like to thank both people who assisted with this answer, (also I want to thank jarret, from my previous question involving the linq query) for helping me to get the resolution entirely.

I have found both solutions to run substantially well, though I do not like the Microsoft.CSharp.dll library staring at me in The Resources folder (I know you're there, even if I collapse you! Don't try to deny it!)

In all, LINQ continues to amaze me, but I was shocked and astounded that dynamic worked more than anything. To test, I threw a "NotImplementedException" in each of the three derivitive classes "Add" methods and ran the entire execution, when the compiler broke out to show the Exception, it was in the appropriate class. This tested true in both cases.

Additionally, this showed that parameters are in fact different from names when it comes to implementation initialization where reflection is concerned. I tested the same for creating an interface IItem and IItem<T> and got the same results. (Additionally, IItem`1 also yielded the same results when IItem<T> was the implemented interface).

Each time I get an answer from Stackoverflow, it continues to amaze and astound me that no matter how much I think I know, there are so many people that know more, but not only that, that they are browsing the same resource sites that I am, and willing to answer such piddly questions that eat up their time and resources. Thanks again to all of you who helped!

Using Adam Robinson's Method, I approached it from this angle.

        var grouped = items.GroupBy(i => i.Name);

        grouped.ForEach(x => {
            x.ForEach(e => {
                // results handled here
                // assumes that all objects inheriting "Item" implement IItem, which contracts "Add" method.
                e.GetType().GetInterfaces().First(i => i.Name == "IItem").GetMethod("Add").Invoke(e, new object[] { e });
            });
        });

Using arootbeer's answer.

        var grouped = items.GroupBy(i => i.Name);

        grouped.ForEach(x => {
            x.ForEach(e => {
                // results handled here
                ((dynamic)e).Add((dynamic)e);
            });
        });
Stacey
Who's @arootbeet? :)
arootbeer
Why I don't know what you're talking about! :D
Stacey
In case you missed it, I commented on your comment at the end of the comments on the accepted answer. I believe SelectMany is what you want.
arootbeer