Click here to Skip to main content
Click here to Skip to main content

Object Comparer

, 15 Jun 2005
Rate this:
Please Sign up or sign in to vote.
A fun but probably useless foray into comparing objects of different types.

Introduction

This is probably pretty obscure--what if you want to compare two objects and you don't know their types? What if one of those types might be an enum? What if one of those types might be a string containing a number, and you want to compare it to another number? What if both objects might be numbers and you want to compare them at the appropriate precision? Well, if you ever have to do something so strange, this little class is for you.

Why do this? Well, it was pretty much just a silly exercise, but IComparable only works if:

  • implemented by the "compare with" object.
  • the "compare to" object is of the same type.

For example:

int a=5;
a.CompareTo(6.2);

throws an exception! What I've done is made a smarter (and slower!) object comparer, one that attempts to optimally match types before making the comparison.

How It Works

The best way to describe how it works is with a flowchart. But before going into that, I want to point out a few things. Comparing strings is the least desirable comparison, so the program strives to convert strings to either a numeric value type or, at worst case, the type of the object being compared to. This is true even when comparing two strings--if they are both numeric values, the algorithm will convert them to a type double. A nifty little regex expression:

[+-\.,]?\d*[\.,]?\d*e?[+-\.,]?\d+[\.,]?\d*

is used to tell if the string is a numeric value. I wrote this expression, so it may not be the best in town.

If the comparer isn't dealing with strings, but they're both value types, then it attempts to convert one of the objects to the same type as the other object, whichever has the larger range. So, when an int object is compared to a double object, the int is converted to a double, and thus precision is not lost.

This also works with enums. Unfortunately, the ConvertTo and ConvertFrom methods of the TypeConverter class don't automatically handle conversion of enums, so I have to explicitly use the Convert.ToDouble() method (again, converting the enum to a double).

The value type conversion is done based on what I determined to be the precedence of the different value types. If a value type is unsigned while the other value type is not, then the unsigned type is converted to a decimal type and the normal precedence determination is made, so that comparing an unsigned byte of 255 to a signed byte of 127 will return the result that 255 > 127.

The precedence that I've established is:

protected ArrayList precedence=new ArrayList(new string[]
{
  "System.Boolean",
  "System.Byte",
  "System.Sbyte",
  "System.Char",
  "System.UInt16",
  "System.Int16",
  "System.UInt32",
  "System.Int32",
  "System.UInt64",
  "System.Int64",
  "System.Decimal",
  "System.Float",
  "System.Double",
}
);

Booleans are also a bane, as the TypeConverter class will not successfully convert a boolean to an integer type using Convert.To, nor will an integer type successfully convert from a boolean, using ConvertFrom. More special case code!

Here's the flowchart:

Probably the most interesting code is the precedence code and the conversion of one object to another.

Precedence Algorithm

protected bool MatchTypesByPrecision(object a, object b, Type typea, 
                     Type typeb, out object a2, out object b2)
{
  a2=a;
  b2=b;
  bool ret=false;

  if (a is Enum)
  {
    a=Convert.ToDouble(a);
    typea=typeof(System.Double);
  }

  if (b is Enum)
  {
    b=Convert.ToDouble(b);
    typeb=typeof(System.Double);
  }

  int uidxa=unsignedTypes.IndexOf(typea.FullName);
  int uidxb=unsignedTypes.IndexOf(typeb.FullName);

  if ( (uidxa != -1) && (uidxb == -1) )
  {
    a=Convert.ToDecimal(a);
    typea=typeof(System.Decimal);
  }
  else if ( (uidxb != -1) && (uidxa == -1) )
  {
    b=Convert.ToDecimal(b);
    typeb=typeof(System.Decimal);
  }

  int idxa=precedence.IndexOf(typea.FullName);
  int idxb=precedence.IndexOf(typeb.FullName);
  if (idxa < idxb)
  {
    ret=MatchTypesToB(a, b, typea, typeb, out a2, out b2);
  }
  else if (idxa > idxb)
  {
    ret=MatchTypesToA(a, b, typea, typeb, out a2, out b2);
  }
  else
  {
    a2=a;
    b2=b;
    ret=true;
  }

  return ret;
}

Converting One Object To Another

protected bool MatchTypesToA(object a, object b, Type typea, 
                   Type typeb, out object a2, out object b2)
{
  bool ret=false;
  a2=a;
  b2=b;
  // Try convert to.
  TypeConverter tcb=TypeDescriptor.GetConverter(typeb);
  if (tcb.CanConvertTo(typea))
  {
    b2=tcb.ConvertTo(b, typea);
    ret=true;
  }
  else
  {
    // Try convert from.
    TypeConverter tca=TypeDescriptor.GetConverter(typea);
    if (tca.CanConvertFrom(typeb))
    {
      b2=tca.ConvertFrom(b);
      ret=true;
    }
  }
  return ret;
}

Note that both ConvertTo and ConvertFrom attempts are made, as sometimes the source object will not implement a ConvertTo for the target object, but the target object will implement a ConvertFrom for the source object. There is a complementary "MatchTypesToB" method, which looks very similar.

Unit Testing

I've written 30 unit tests (using the AUT engine, download from here) trying out various comparisons, but this is by no means exhaustive. If you find a problem, let me know!

Conclusion

What can I say? This was a fun exercise, but even I don't have a use for this right now! Do you?

License

This article has no explicit license attached to it but may contain usage terms in the article text or the download files themselves. If in doubt please contact the author via the discussion board below.

A list of licenses authors might use can be found here

About the Author

Marc Clifton

United States United States
Marc is the creator of two open source projets, MyXaml, a declarative (XML) instantiation engine and the Advanced Unit Testing framework, and Interacx, a commercial n-tier RAD application suite.  Visit his website, www.marcclifton.com, where you will find many of his articles and his blog.
 
Marc lives in Philmont, NY.

Comments and Discussions

 
GeneralDynamic casting question. PinmemberPreky21-Nov-05 21:33 
QuestionSystem.Float? PinmemberChris Lennon20-Jun-05 17:27 
GeneralNice! A couple minor issues. PinmemberMarc Brooks16-Jun-05 8:46 
GeneralRe: Nice! A couple minor issues. PinmemberRichard Deeming20-Jun-05 5:10 
GeneralRe: Nice! A couple minor issues. PinprotectorMarc Clifton20-Jun-05 10:34 
GeneralRe: Nice! A couple minor issues. PinprotectorMarc Clifton20-Jun-05 10:35 
GeneralGreat article Pinmembernemopeti15-Jun-05 21:47 

General General    News News    Suggestion Suggestion    Question Question    Bug Bug    Answer Answer    Joke Joke    Rant Rant    Admin Admin   

Use Ctrl+Left/Right to switch messages, Ctrl+Up/Down to switch threads, Ctrl+Shift+Left/Right to switch pages.

| Advertise | Privacy | Mobile
Web04 | 2.8.140721.1 | Last Updated 15 Jun 2005
Article Copyright 2005 by Marc Clifton
Everything else Copyright © CodeProject, 1999-2014
Terms of Service
Layout: fixed | fluid