Search
Thursday, December 13, 2018 ..:: Home ::.. Register  Login
   Calendar  
     
  
   Search  
     
  
   Blogroll  
     
  
   Disclosure  
All blog entries are the opinions of the author and do not necessarily reflect the opinions of their employer. All the code presented is for explanation and demonstration purposes only. Any damages incurred to your site and/or data are not the responsibility of the author. Every effort is taken to ensure the code properly compiles, however sometimes there are some hiccups and you might be required to do your own debugging.
     
  
   TechTidBits (Blog)  

IComparer, how does it compare? Now introduce Generics into the mix and see how much fun it is?

Jun 15

Written by:
Monday, June 15, 2009 9:20 PM  RssIcon

Last week I need to order a list of files and directories.  My problem was I was adding multiple files implementing the IComparer interface.  That's good'n'all, but there had to be a better way than one more file for one more sort order?  But how?!  SG to the rescue!

Last week I needed to order a directory differently depending on what the context was.  In one case I needed to order in ASC by last saved date (FileInfo.LastWriteTime) and by filename DESC (FileInfo.Name) in another.  THEN to make things worse, I found myself needing the exact same code but for DirectoryInfo!  Yup sorting by dates and also filenames.  Although the files are small, they were growing for each sort order, that just smelled wrong!  Here's what I was working with.

Too many comparers

Each one is small, and I put them all into one file, but that only masks the real problem, it's not very organized IMHO.  Here is just two of the methods.  Notice, the only REAL difference between them is the negative on the comparison CompareTo method.  Is it just me, or does that smell bad?

namespace RegularComparer
{
  public class CompareFileDatesASC : IComparer
  {
    #region IComparer Members

    public int Compare( FileInfo x, FileInfo y )
    {
      return x.LastWriteTime.CompareTo( y.LastWriteTime );
    }

    #endregion
  }

  public class CompareFileDatesDESC : IComparer
  {
    #region IComparer Members

    public int Compare( FileInfo x, FileInfo y )
    {
      return -x.LastWriteTime.CompareTo( y.LastWriteTime );
    }

    #endregion
  }
}

As I mentioned before, the problem is each new sort order takes it's own NEW class.  That's not very efficient IMHO.

I went to ask SG and his FIRST question was, "do they (meaning FileInfo and DirectoryInfo) share a common parent class?"  Here's what I found which was great news!  Cause that meant I could NOW use the generic IComparer.

FileSystemInfo UML

I was able "rotate my thinking" a bit around if you will and focus on just WHAT I was trying to sort on.  In my case names or  last saved dates.  So here's what I ended up with.

Focused and generic comparers

And here's what the whole namespace look like (it's much more conscise now). 

namespace GenericComparer
{
  public class CompareDatesASC : IComparer where T : FileSystemInfo
  {
    #region IComparer Members

    public int Compare( T x, T y )
    {
      return x.LastWriteTime.CompareTo( y.LastWriteTime );
    }

    #endregion
  }

  public class CompareDatesDESC : IComparer where T : FileSystemInfo
  {
    #region IComparer Members

    public int Compare( T x, T y )
    {
      return -x.LastWriteTime.CompareTo( y.LastWriteTime );
    }

    #endregion
  }

  public class CompareFileNamesASC : IComparer where T : FileSystemInfo
  {
    #region IComparer Members

    public int Compare( T x, T y )
    {
      return x.Name.CompareTo( y.Name );
    }

    #endregion
  }

  /// 
  /// instead of using the negate operator, swap the y and x variables, another alternative
  /// 
  /// 
  public class CompareFileNamesDESC : IComparer where T : FileSystemInfo
  {
    #region IComparer Members

    public int Compare( T x, T y )
    {
      return y.Name.CompareTo( x.Name );
    }

    #endregion
  }
}

Now the focus is on WHAT I'm trying to sort on, not the underlying mechanics of it all.  Please notice one small piece of beautiful magic above, namely the way the compare class limits it's scope to JUST objects which are FileSystemInfo.  This small slight of hand guarantees we're strong typed and will always work!

Next you might be wondering how do you use this new found knowledge?  Here ya go! :>  (how's that for spoon feeding?)  My sample project has three listboxes which get and sort your Windows directory (only cause I know there's a lot of files in there with different names and saved dates which will show different results depending on the sort order).

Sample UI.png

Here's the code.

    public void GetAndSortFiles()
    {
      string windowsPath = System.Environment.ExpandEnvironmentVariables( "%WinDir%" );
      DirectoryInfo windowsDirectory = new DirectoryInfo( windowsPath );


      FileInfo[] windowsFiles = windowsDirectory.GetFiles();
      regularListBox.DataSource = windowsFiles;


      Array.Sort( windowsFiles, new CompareDatesASC() );
      sortedASCListBox.DataSource = windowsFiles;


      Array.Sort( windowsFiles, new CompareDatesDESC() );
      sortedDESCListBox.DataSource = windowsFiles;
    }

The meat of this code is the Array.Sort call and the new generic class passed in.  Basically I'm just telling the Sort method to use one of my CompareDatesASC for FileInfo objects.  But as you can see, it's pretty easy to use the new sort classes.

So what you might ask?  Well, this way as I need more sort types, the number of classes only grows by two (IF I'm looking to implement both the ASC and DEC).  the old way would mean FOUR new methods for both files and directories.

You might be asking is that all?  Nooooooooooooooooot really.  I found out something interesting while doing all this.  If you use the Smart Tag on one fo your compare classes, you'll notice, VS is giving you the option to implement the IComparer interface.

Smart Tag

HUH?  WTF?  It's RIGHT THERE!!??!?!?!?!?!?!  Has VS been eating some moldy bread?  WTF is going on here?  Well, as it turns out, we ARE implementing the IComparer interface, BUUUUUUUT we're not doing it "explicitly" as VS in indicating we're not doing.  So what's the difference you might ask?  GOOD question!  If you let VS give you "an explicity" implementation, this is what you'll get.

  public class CompareDatesASC : IComparer where T : FileSystemInfo
  {
    #region IComparer Members

    public int Compare( T x, T y )
    {
      return x.LastWriteTime.CompareTo( y.LastWriteTime );
    }

    #endregion

    #region IComparer Members

    int IComparer.Compare( T x, T y )
    {
      throw new NotImplementedException();
    }

    #endregion
  }

It will compile, but not the smartest thing in the world to leave the other one there.  My main malfunction with this "explicit" implementation was the missing access modifier (notice there's no public/internal/private in front of the second IComparer?  What is up with THAT?  Try it out for yourself!  Try putting in any access modifier you want, go on, I dare ya?! 

Back?  Cool!  What did you find?  Did you find this link on MSDN (which I had one of those V8-DOH! moments as soon as I found it!)

Implementing explicit interfaces

What's that say?  Don't put your modifiers on your explictly implemented interfaces!  What's an "explicitly implemented interface" you ask?  You see up top there for the method name?  You see the class name as part of the method name (IComparer.Compare( T x, T y )), THAT's explicitly implemented.

Now that you know how to implement your own generic IComparer classes, it's time to go grab a coffee and get coding!

 

 

Source Code

MSDN: 13.4.1 Explicit interface member implementations

Tags:
Categories:
Location: Blogs Parent Separator TechTidBits

Your name:
Gravatar Preview
Your email:
(Optional) Email used only to show Gravatar.
Your website:
Title:
Comment:
Add Comment   Cancel 
     
  
Copyright 1999-2012 by PCHenry.com   Terms Of Use  Privacy Statement