Click here to Skip to main content
15,885,782 members
Articles / Programming Languages / C#
Article

Object Comparer

Rate me:
Please Sign up or sign in to vote.
4.30/5 (14 votes)
15 Jun 20053 min read 59.7K   454   23   7
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:

C#
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:

C#
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:

Image 1

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

Precedence Algorithm

C#
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

C#
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

Image 2

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


Written By
Architect Interacx
United States United States
Blog: https://marcclifton.wordpress.com/
Home Page: http://www.marcclifton.com
Research: http://www.higherorderprogramming.com/
GitHub: https://github.com/cliftonm

All my life I have been passionate about architecture / software design, as this is the cornerstone to a maintainable and extensible application. As such, I have enjoyed exploring some crazy ideas and discovering that they are not so crazy after all. I also love writing about my ideas and seeing the community response. As a consultant, I've enjoyed working in a wide range of industries such as aerospace, boatyard management, remote sensing, emergency services / data management, and casino operations. I've done a variety of pro-bono work non-profit organizations related to nature conservancy, drug recovery and women's health.

Comments and Discussions

 
GeneralDynamic casting question. Pin
Preky21-Nov-05 21:33
Preky21-Nov-05 21:33 
QuestionSystem.Float? Pin
Chris Lennon20-Jun-05 17:27
Chris Lennon20-Jun-05 17:27 
GeneralNice! A couple minor issues. Pin
Marc Brooks16-Jun-05 8:46
Marc Brooks16-Jun-05 8:46 
GeneralRe: Nice! A couple minor issues. Pin
Richard Deeming20-Jun-05 5:10
mveRichard Deeming20-Jun-05 5:10 
GeneralRe: Nice! A couple minor issues. Pin
Marc Clifton20-Jun-05 10:34
mvaMarc Clifton20-Jun-05 10:34 
GeneralRe: Nice! A couple minor issues. Pin
Marc Clifton20-Jun-05 10:35
mvaMarc Clifton20-Jun-05 10:35 
GeneralGreat article Pin
nemopeti15-Jun-05 21:47
nemopeti15-Jun-05 21:47 

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

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