views:

68

answers:

3

This feels like a too easy question to be found with google, I think/hope I've got stuck in the details when trying to implement my own version of it. What I'm trying to do is to sort a list of MyClass objects depending on my Datatype object different search functions should be used.

I've had something like this in mind for the class Datatype:

class Datatype {
  public delegate int CMPFN(object x, object y);
  private CMPFN compareFunction;

  (...)

  private XsdDatatype((...), CMPFN compareFunction) {
      (...)
      this.compareFunction = compareFunction;
  }

  public CMPFN GetCompareFunction() {
    return this.compareFunction;
  }

  static private int SortStrings(object a, object b) {
      return ((MyClass)a).GetValue().CompareTo(((MyClass)b).GetValue());
  }
}

And later on I'm trying to sort a MyClass list something like this:

List<MyClass> elements = GetElements();
Datatype datatype = new Datatype((...), Datatype.SortStrings);
elements.Sort(datatype.GetCompareFunction()); // <-- Compile error!

I'm not overly excited about the cast in Datatype.SortStrings but it feels like this could work(?). The compiler however disagrees and gets me this error on the last line above and I'm a bit unsure exactly why CMPFN can't be converted/casted(?) to IComparer.

Cannot convert type 'proj.Datatype.CMPFN' to 'System.Collections.Generic.IComparer<proj.MyClass>'
+4  A: 

Delegates aren't duck-typed like that. You can create an Comparison<MyClass> from a CMPFN but you can't use a plain reference conversion - either implicit or explicit.

Three options:

  • Create the comparer like this:

    elements.Sort(new Comparison<MyClass>(datatype.GetCompareFunction()));
    
  • Use a lambda expression to create a Comparison<T> and use that instead:

    elements.Sort((x, y) => datatype.GetCompareFunction()(x, y));
    
  • Write an implementation of IComparer<MyClass> which performs the comparison based on a CMPFN

Note that the second approach will call GetCompareFunction once per comparison.

A much better solution would be to get rid of CMPFN entirely - why not just use (or implement) IComparer<MyClass> to start with? Note that that would remove the casts as well. (If you're happy using delegates instead of interfaces, you could express the comparison as a Comparison<MyClass> instead.)

I'm not sure why your current API is in terms of object, but you should be aware that in C# 3 and earlier (or C# 4 targeting .NET 3.5 and earlier) you wouldn't be able to convert an IComparer<object> into an IComparer<MyClass> (via a reference conversion, anyway). As of C# 4 you can, due to generic contravariance.

Jon Skeet
... surely you can't `new` an `IComparer` ?
Richard Szalay
@Richard: My bad. I've had my head in delegate land. Will edit. It's clearly much too early :)
Jon Skeet
I wonder if the [compiler apologized](http://meta.stackoverflow.com/questions/9134/jon-skeet-facts), as it should have...
Dean Harding
@Richard: For extra LOLs, you *can* new up an interface... if it's a COM interface. It's extremely weird :)
Jon Skeet
@Jon - Didn't know that!
Richard Szalay
A: 

Hi,

try somthing like this

class AttributeSort : IComparer<AttributeClass >
    {
        #region IComparer Members

        public int Compare(AttributeClass x, AttributeClass y)
        {                
            if (a == null || b == null)
                throw new ArgumentException("At least one argument is null");

            if (a.attributeNo == b.attributeNo) return 0;
            if (a.attributeNo < b.attributeNo) return -1;
            return 1;
        }

        #endregion
    }

you can call it then like this

List<AttributeClass> listWithObj ....

listWithObj.Sort(new AttributeSort());

Should work like you want. You can create a type-safe comparer class as well.

regards

ReaperXmac
Why the lack of generics? The OP is clearly using generics in the original code, so I don't see any call to go back to `ArrayList` and the nongeneric `IComparer` interface.
Jon Skeet
my bad, it was early and there was finished (and maybe old) code. :) But it's the generic version isn't much different so far.
ReaperXmac
+1  A: 

There are a number of overloads of List<T>.Sort, but there are none which take a delegate with the parameters you have defined (two objects).

However, there is an overload that takes a Comparison<T> delegate, which you can work with your code with a few minor modifications. Basically, you just replace your CMPFN delegate with Comparison<MyClass> - as an added bonus, you get strong-typing in your SortStrings function, too:

static private int SortStrings(MyClass a, MyClass b) {
    return a.GetValue().CompareTo(b.GetValue());
}

public Comparison<MyClass> GetCompareFunction() {
    return SortStrings; // or whatever
}

...

elements.Sort(datatype.GetCompareFunction()); 
Dean Harding