tags:

views:

84

answers:

6

Hi,

I have a collection like this,

Class Base{}
Class A : Base {}
Class B : Base {}

List<Base> collection = new List<Base>();
collection.Add(new A());
collection.Add(new B());
collection.Add(new A());
collection.Add(new A());
collection.Add(new B());

Now I want to sort the collection based on type (A/B). How I can do this? Please help me.

A: 

EDIT: I think this is what you want:

If you don't mind sorting 'out of place' and reassigning the list, this should work:

collection = collection.GroupBy(item => item.GetType())
                       .SelectMany(g => g)
                       .ToList();

or depending on your needs, something like:

collection = collection.OrderBy(item => item.GetType().FullName)
                       .ToList();

If it must be in-place, then writing a custom comparer and list.Sort is probably the best choice.


To group the items by type, you can use GroupBy:

var groupedItems = collection.GroupBy(item => item.GetType());

This uses deferred execution.

Alternatively, you can put the 'groups' into a data-structure like this:

var itemsByTypeLookUp = collection.ToLookup(item => item.GetType());

foreach(A a in itemsByTypeLookUp[typeof(A)])
{
   ...
}

If you are only looking for a certain type:

var itemsOfTypeA = collection.OfType<A>();
Ani
This groups them, but doens't sort the list...
Reed Copsey
@ Reed Copsey: Thanks, edited.
Ani
+3  A: 

You can use the type information itself:

collection.Sort( (a,b) => 
  {
      bool aType = a.GetType() == typeof(A);
      bool bType = b.GetType() == typeof(A);
      return aType.CompareTo(bType);
  });

This will work for the two types you specified, but doesn't scale beyond them. It does allow you to specify the order explicitly (ie: if you want "B" elements before "A", you could make it work using this technique).

If you need to support many types, and the ordering doesn't need to be specified in advance, you could do something like:

collection.Sort( (a,b) => a.GetType().FullName.CompareTo(b.GetType().FullName) );

This would handle any number of types (ie: a C and a D subtype, too), and order them by their full type name.

Reed Copsey
@Anthony: Yes - there's no checking in here for null. Right now, anything other than A or B will be treated as B, too...
Reed Copsey
Yes, I took another look at it, and it turns out it's not *exactly* what I would go far because of not accounting for type `C : Base`. I would likely do `a.GetType().Name.CompareTo(b.GetType().Name`) (and whatever null checks, should they be relevant). But who knows, maybe there are only two types, and maybe `A` is really `Foo` and `B` is really `Bar` and `Foo` should come before `Bar`.
Anthony Pegram
@Anthony: There is really not enough info in the original question to completely determine this - this gives you more control over how the sort occurs, but it's tough to know what the OP really wants...
Reed Copsey
@Reed, I completely agree. It's all hypothetical.
Anthony Pegram
@Anthony: I reworked it - sound better now? (I used FullName, btw, in case there are namespace conflicts, too...)
Reed Copsey
I have only two types, so it worked. Thanks a lot.
Jai
@Reed, it's good for me (and apparently Jai, too). Note to Jai: If there's a possibility that null references could be in this list, you want to handle those or filter them out beforehand.
Anthony Pegram
Ya. As of now it wont have null references :)
Jai
+1  A: 

Does

collection.Where(entry => entry is A).Concat(collection.Where(entry => entry is B))

do what you need?

arootbeer
+1  A: 

This is going to order so A will be the first and B the second.

var xx = list.OrderBy(x => x.GetType() == typeof(B)).ToList();

This following console project confirms:

class Program
{
    public class X { }
    public class A : X { }
    public class B : X { }
    static void Main()
    {
        List<X> list = new List<X>();
        list.Add(new B());
        list.Add(new A());
        list.Add(new B());
        list.Add(new A());
        list.Add(new A());

        // A.GetType() == typeof(B) will be "0" making the type A go first
        // B.GetType() == typeof(B) will be "1" making the type B go last
        var xx = list.OrderBy(x => x.GetType() == typeof(B)).ToList();

        Console.ReadLine();
    }
}

In this case I am assuming you only have A and B. If you have more types you would have to create a comparer to return a value for each type. You could also have a property on the base class which would set the order of the elements, then you could sort the list with this property.

BrunoLM
What if there is type `C : Base` ?
Anthony Pegram
@Anthony: He would have to handle each type with a `Comparer`. Unless the base class have a property called `Order` and you could order by this property. Since he stated only `A` and `B` I believe this is the shortest solution for him.
BrunoLM
@Jon Hanna provides an interesting way to handle the order, with another function and you can provide whatever index you want. Of course, that function would have to be modified with each new type. Or, in your case, you could order by `x => x.GetType().Name`, which would put A before B before C. Of course, it could be that A and B are not really A and B and an alphabetic sort wouldn't work. So, yes, barring more information, it's difficult to say what answer is appropriate. I merely throw out class C as a thought.
Anthony Pegram
@Anthony, if you wanted a code-defined sort order, but to also have *an* order (guaranteeing objects of same type are together) with other types you could do a OrderBy as per Bruno's approach or my approach followed by a ThenBy on type full name.
Jon Hanna
+1  A: 
private static int OrderOnType(Base item)
{
  if(item is A)
    return 0;
  if(item is B)
    return 1;
  return 2;
}

Then take your pick from:

collection.OrderBy(x => OrderOnType(x))

or

collection.Sort((x, y) => OrderOnType(x).CompareTo(OrderOnType(y)));

Depending on whether you want in-place sorting or not. You could put OrderOnType into the lambda if you really wanted, but this seems more readable to me, and I prefer to keep lambdas for when they add rather than reduce readability.

Jon Hanna
+1  A: 
collection.OrderBy(i => i.GetType() == typeof(A) ? 0 : 1);

Will give you a sequence with all the As then all the Bs

Lee
What if there is type `C : Base` ?
Anthony Pegram
@Anthony Pegram - Then this approach won't work. I suppose you could order on type Name, but then what if you wanted the order to be A,C,B in that case? If this method is too simplistic, the question needs more details added.
Lee
@Lee, I agree. It was a thought exercise. Based on Jai's comment to Reed's answer, there are just the 2 types, which makes a number of these answers appropriate.
Anthony Pegram