Chris
Chris

Reputation: 6325

How to compare two distinctly different objects with similar properties

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 identifier. These two lists need to be filtered by another list which just contains GUIDs which may or may not match up with the IDs 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 IDs?

Upvotes: 5

Views: 7554

Answers (7)

Nathan
Nathan

Reputation: 569

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();
        }

    }
}

Upvotes: 2

dice
dice

Reputation: 2880

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));
    }
}

Upvotes: 0

dtb
dtb

Reputation: 217401

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.

Upvotes: 1

Mark Coleman
Mark Coleman

Reputation: 40863

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

Upvotes: 0

David Robbins
David Robbins

Reputation: 10046

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.

Upvotes: 0

mdm20
mdm20

Reputation: 4563

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);

Upvotes: 0

Matthew
Matthew

Reputation: 2280

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.

Upvotes: 15

Related Questions