views:

409

answers:

7

This is all in C#, using .NET 2.0.

I have two lists of objects. They are not related objects, but they do have certain things in common that can be compared such as a Guid based unique identifer. These two lists need be filtered by another list which just contains Guid's which may or may not match up with the ID's contained in the first two lists.

I have thought about the idea of casting each object list to just 'object' and sorting by that, but I'm not sure that I'll be able to access the ID property once it's cast, and I'm thinking that the method to sort the two lists should be somewhat dumb in knowing what the list to be sorted is.

What would be the best way to bring in each object list so that it can be sorted against the list with only the ID's?

+9  A: 

You should make each of your different objects implement a common interface. Then create an IComparer<T> for that interface and use it in your sort.

Matthew
If you can implement a common interface (because you don't own one or both of the objects), you will want to make adapter classes that DO implement the common interface. Then you're golden.
plinth
In other words, you're essentially casting each object to a class (interface) type that contains the common elements of both, then comparing the resulting (same-typed) objects. Using an interface avoids the explicit casting operations.
Loadmaster
A: 

I'm not sure that I fully understand what you want, but you can use linq to select out the matching items from the lists as well as sorting them. Here is a simple example where the values from one list are filtered on another and sorted.

        List<int> itemList = new List<int>() { 9,6,3,4,5,2,7,8,1 };
        List<int> filterList = new List<int>() { 2, 6, 9 };

        IEnumerable<int> filtered = itemList.SelectMany(item => filterList.Where(filter => filter == item)).OrderBy(p => p);
mdm20
A: 

I haven't had a chance to use AutoMapper yet, but from what you describe you wish to check it out. From Jimmy Bogard's post:

AutoMapper conventions

Since AutoMapper flattens, it will look for:

Matching property names

Nested property names (Product.Name maps to ProductName, by assuming a PascalCase naming convention)

Methods starting with the word “Get”, so GetTotal() maps to Total

Any existing type map already configured

Basically, if you removed all the “dots” and “Gets”, AutoMapper will match property names. Right now, AutoMapper does not fail on mismatched types, but for some other reasons.

David Robbins
A: 

I am not totally sure what you want as your end results, however....

If you are comparing the properties on two different types you could project the property names and corresponding values into two dictionaries. And with that information do some sort of sorting/difference of the property values.

        Guid newGuid = Guid.NewGuid();
        var classA = new ClassA{Id = newGuid};
        var classB = new ClassB{Id = newGuid};

        PropertyInfo[] classAProperties = classA.GetType().GetProperties();

        Dictionary<string, object> classAPropertyValue = classAProperties.ToDictionary(pName => pName.Name,
                                                                                pValue =>
                                                                                pValue.GetValue(classA, null));

        PropertyInfo[] classBProperties = classB.GetType().GetProperties();
        Dictionary<string, object> classBPropetyValue = classBProperties.ToDictionary(pName => pName.Name,
                                                                                pValue =>
                                                                                pValue.GetValue(classB, null));


internal class ClassB
{
    public Guid Id { get; set; }
}

internal class ClassA
{
    public Guid Id { get; set; }
}

classAPropertyValue
Count = 1
    [0]: {[Id, d0093d33-a59b-4537-bde9-67db324cf7f6]}

classBPropetyValue
Count = 1
    [0]: {[Id, d0093d33-a59b-4537-bde9-67db324cf7f6]}
Mark
+1  A: 

Using only .NET 2.0 methods:

class Foo
{
    public Guid Guid { get; }
}

List<Foo> GetFooSubset(List<Foo> foos, List<Guid> guids)
{
    return foos.FindAll(foo => guids.Contains(foo.Guid));
}

If your classes don't implement a common interface, you'll have to implement GetFooSubset for each type individually.

dtb
.NET 2.0 does not support lambda expressions nor it has the extension methods added by System.Linq. Your example requires at least .NET 3.0.
Karim
@Karim: FindAll is not an extension method added by LINQ but a regular method provided by the `List<T>` class which is part of the .NET framework since version 2.0. Lambda expressions require a C# 3.0 compiler, but not necessarily .NET 3.0.
dtb
A: 

Thist should essentially get you what you want - but you may be better of using linq

class T1
{
    public T1(Guid g, string n) { Guid = g; MyName = n; }
    public Guid Guid { get; set; }
    public string MyName { get; set; }
}
class T2
{
    public T2(Guid g, string n) { ID = g; Name = n; }
    public Guid ID { get; set; }
    public string Name { get; set; }
}
class Test
{
    public void Run()
    {
        Guid G1 = Guid.NewGuid();
        Guid G2 = Guid.NewGuid();
        Guid G3 = Guid.NewGuid();
        List<T1> t1s = new List<T1>() {
            new T1(G1, "one"),
            new T1(G2, "two"), 
            new T1(G3, "three") 
        };
        List<Guid> filter = new List<Guid>() { G2, G3};

        List<T1> filteredValues1 = t1s.FindAll(delegate(T1 item)
        {
            return filter.Contains(item.Guid);
        });

        List<T1> filteredValues2 = t1s.FindAll(o1 => filter.Contains(o1.Guid));
    }
}
dice
I had some overkill in there - editing it out.
dice
+2  A: 

Okay, if you have access to modify your original classes only to add the interface there, Matthew had it spot on. I went a little crazy here and defined out a full solution using 2.0 anonymous delegates. (I think I'm way addicted to 3.0 Lambda; otherwise, I probably would've written this out in foreach loops if I was using 2005 still).

Basically, create an interface with the common properties. Make yoru two classes implement the interface. Create a common list casted as the interface, cast and rip the values into the new list; remove any unmatched items.

//Program Output: 
List1:
206aa77c-8259-428b-a4a0-0e005d8b016c
64f71cc9-596d-4cb8-9eb3-35da3b96f583

List2:
10382452-a7fe-4307-ae4c-41580dc69146
97f3f3f6-6e64-4109-9737-cb72280bc112
64f71cc9-596d-4cb8-9eb3-35da3b96f583

Matches:
64f71cc9-596d-4cb8-9eb3-35da3b96f583
Press any key to continue . . .


using System;
using System.Collections.Generic;
using System.Text;

namespace ConsoleApplication8
{
    class Program
    {
        static void Main(string[] args)
        {
            //test initialization
            List<ClassTypeA> list1 = new List<ClassTypeA>();
            List<ClassTypeB> list2 = new List<ClassTypeB>();

            ClassTypeA citem = new ClassTypeA();
            ClassTypeB citem2 = new ClassTypeB();
            citem2.ID = citem.ID;

            list1.Add(new ClassTypeA());
            list1.Add(citem);
            list2.Add(new ClassTypeB());
            list2.Add(new ClassTypeB());
            list2.Add(citem2);


            //new common list. 
            List<ICommonTypeMakeUpYourOwnName> common_list = new List<ICommonTypeMakeUpYourOwnName>();

            //in english,  give me everything in list 1 and cast it to the interface
            common_list.AddRange(list1.ConvertAll<ICommonTypeMakeUpYourOwnName>(delegate(ClassTypeA x) { return (ICommonTypeMakeUpYourOwnName)x; }));

            //in english, give me all the items in the common list that don't exist in list2 and remove them. 
            common_list.RemoveAll(delegate(ICommonTypeMakeUpYourOwnName x) { return list2.Find(delegate(ClassTypeB y) {return y.ID == x.ID;}) == null; });

            //show list1 
            Console.WriteLine("List1:");
            foreach (ClassTypeA item in list1)
            {
                Console.WriteLine(item.ID);
            }
            //show list2
            Console.WriteLine("\nList2:");
            foreach (ClassTypeB item in list2)
            {
                Console.WriteLine(item.ID);
            }

            //show the common items
            Console.WriteLine("\nMatches:");
            foreach (ICommonTypeMakeUpYourOwnName item in common_list)
            {
                Console.WriteLine(item.ID);
            }
        }

    }

    interface ICommonTypeMakeUpYourOwnName
    {
        Guid ID { get; set; }
    }

    class ClassTypeA : ICommonTypeMakeUpYourOwnName
    {
        Guid _ID;
        public Guid ID { get { return _ID; } set { _ID = value; } }
        int _Stuff1;
        public int Stuff1 { get { return _Stuff1; } set { _Stuff1 = value; } }
        string _Stuff2;
        public string Stuff2 { get { return _Stuff2; } set { _Stuff2 = value; } }

        public ClassTypeA()
        {
            this.ID = Guid.NewGuid();
        }
    }

    class ClassTypeB : ICommonTypeMakeUpYourOwnName
    {
        Guid _ID;
        public Guid ID { get { return _ID; } set { _ID = value; } }
        int _Stuff3;
        public int Stuff3 { get { return _Stuff3; } set { _Stuff3 = value; } }
        string _Stuff4;
        public string Stuff4 { get { return _Stuff4; } set { _Stuff4 = value; } }

        public ClassTypeB()
        {
            this.ID = Guid.NewGuid();
        }

    }
}
Nathan
FYI... OP said 2.0; I'm assuming 2005 VS, so no 3.0 VS 2008 shortcuts.
Nathan
This works pretty good. Too bad I can't give out two accepted answers.
Chris