Click here to Skip to main content
15,889,266 members
Please Sign up or sign in to vote.
0.00/5 (No votes)
See more:
Hi,

I have managed to use this doohickey [^] to convert a Func<TElement, TKey> into a IComparer<telement>. I can now create a SortedList as so:
C#
public OrderedQueue(Func<T, IComparable> orderByFunc, bool @descending = false )
{
  Queue = new SortedSet<T>(orderByFunc.ToComparer());
}

....
new OrderedQueue<WorkflowDataAccess.Projections.Message>(m => m.Due);

(yes, I am still playing with a generic Ordered Queue)

now I want more. I want to be able to pass in multiple functions as below:
C#
new OrderedQueue<WorkflowDataAccess.Projections.Message>(m=>m.Urgency, m => m.Due);



So I figured I would have the first func.ToComparer and then another extension that on that IComparer that could then add the function. This is where I am stuck.

Maybe I'm going about it the wrong way, or there is an easier way to do this, but I have some time to play ^_^

Here are my extension objects including the doohickey I linked above:
C#
public static class FuncExtensions
{
    public static IComparer<TElement> ToComparer<TElement, TKey>(this Func<TElement, TKey> func)
    {
        return  new ProjectionComparer<TElement,TKey>(func);
    }

    public static IComparer<TElement> AddComparison<TElement, TKey>(this IComparer<TElement> comparer,
        Func<TElement, TKey> func)
    {
        //What now?
    }

    internal class ProjectionComparer<TElement, TKey> : IComparer<TElement>
    {
        private readonly Func<TElement, TKey> _keySelector;
        private readonly IComparer<TKey> _comparer;

        internal ProjectionComparer(Func<TElement, TKey> keySelector,
            IComparer<TKey> comparer = null)
        {
            this._keySelector = keySelector;
            this._comparer = comparer ?? Comparer<TKey>.Default;
        }

        public int Compare(TElement x, TElement y)
        {
            TKey keyX = _keySelector(x);
            TKey keyY = _keySelector(y);
            return _comparer.Compare(keyX, keyY);
        }
    }
}
Posted
Comments
Richard Deeming 17-Jun-15 11:21am    
For comparison, here's the version I use:
https://gist.github.com/RichardD2/7e14217426d999db3d21[^]

The easiest you can do is crate another comparer class that will concatenate the two comparers.
C#
class MultiComparer<TElement> : IComparer<TElement>
{
    private readonly IEnumerable<IComparer<<TElement>> comparers;
    
    public MultiComparer(IEnumerable<IComparer<<TElement>> comparers)
    {
        this.comparers = comparers;
    }
    
    public int Compare(TElement x, TElement y)
    {
        return comparers
            .Select(comparer => comparer.Compare(x, y))
            .FirstOrDefault(result => result <> 0);
    }
}

But maybe it would be better to change ProjectionComparer to support this out of the box.
I didn't run this through the compiler but should be correct enough to understand.
 
Share this answer
 
v3
Comments
Andy Lanng 17-Jun-15 10:53am    
Nice. I was half way there. I've posted the current version below. I'd love to get your feedback on it ^_^
Thanks
Andreas Gieriet 17-Jun-15 17:54pm    
My 5!
Cheers
Andi
An alternative to Solution #1 could be a Comparer that can be used like this:
C#
IComparer<DateTime> comparer = ChainedComparer<DateTime>.First(d => d.Year).Then(d => d.Month);

With the following implementation:
C#
// Comparer for a prioritized sequence of comparison attributes.
// Implemented as "fluent interface", i.e. usage:
//     ChainedComparer<MyType>.First(v=>v.Prop1).Then(v=>v.Prop2)...Then(v=>v.PropN);
public class ChainedComparer<TValue> : IComparer<TValue>
{
    // sequence of comparison stages: 1st has 1st priority, next has next priority, etc.
    private List<Func<TValue, TValue, int>> _stages;
    // private construction. For named constructor see First(...)
    private ChainedComparer() { _stages = new List<Func<TValue,TValue,int>>(); }
    // named constructor - takes selector for first property for comparison
    public static ChainedComparer<TValue> First<TDimension>(Func<TValue, TDimension> selector)
        where TDimension : IComparable<TDimension>
    {
        return new ChainedComparer<TValue>().Then(selector);
    }
    // selector for next comparison stage
    public ChainedComparer<TValue> Then<TDimension>(Func<TValue, TDimension> selector)
        where TDimension : IComparable<TDimension>
    {
        _stages.Add((a, b) => selector(a).CompareTo(selector(b)));
        return this;
    }
    // implementation of IComparer<T> interface
    public int Compare(TValue a, TValue b)
    {
        return _stages.Select(compare => compare(a, b)).FirstOrDefault(res => res != 0);
    }
}

Cheers
Andi
 
Share this answer
 
v3
Comments
Tomas Takac 18-Jun-15 2:50am    
Very nice. This is what I meant when I said OP should extend his comparer to support concatenation out of the box. Very elegant. +5
Andreas Gieriet 18-Jun-15 7:16am    
Thanks for your 5!
Cheers
Andi
This is what I came up with after reviewing Solution 1 by Tomas Takac


C#
using System;
using System.Collections.Generic;
using System.Linq;

namespace Extensions.Linq
{
    public static class FuncExtensions
    {
        public static IComparer<TElement> ToComparer<TElement,TKey>(this Func<telement,> func) where TKey : IComparable 
        {
            return new ProjectionComparers<TElement,TKey>(func);
        }

        public static IComparer<TElement> ToComparer<TElement,TKey>(this IEnumerable<Func<TElement,TKey>> funcs) where TKey : IComparable 
        {
            return new ProjectionComparers<TElement,TKey>(funcs.ToArray());
        }

        internal class ProjectionComparers<TElement,TKey> : IComparer<TElement> where TKey :IComparable 
        {
            private readonly List<comparedelegate> _compareDelegates = new List<comparedelegate>();

            private delegate int CompareDelegate(TElement x, TElement y);

            internal ProjectionComparers(params Func<TElement,TKey>[] funcs)
            {
                foreach (var f in funcs)
                {
                    var func = f;

                    _compareDelegates.Add(
                        (x, y) =>
                        {
                            TKey keyX = func(x);
                            TKey keyY = func(y);
                            return Comparer<TKey>.Default.Compare(keyX, keyY);
                        });
                }
            }

            //internal ProjectionComparers(params KeyValuePair<Func<TElement,TKey>>,
            //    IComparer<TKey>>[] comparers)
            //{

            //    foreach (var kvp in comparers)
            //    {
            //        var comparer = kvp;

            //        _compareDelegates.Add(
            //            (x, y) =>
            //            {
            //                TKey keyX = comparer.Key(x);
            //                TKey keyY = comparer.Key(y);
            //                return (comparer.Value ?? Comparer<tkey>.Default).Compare(keyX, keyY);
            //            });
            //    }
            //}

            public int Compare(TElement x, TElement y)
            {
                return _compareDelegates
                    .Select(compareDelegate => compareDelegate(x, y))
                    .FirstOrDefault(result => result != 0);
            }
        }
    }
}


Not yet tested so I'm sure I'll be tweaking this solution.

EDIT: removed the ctor with Comparers and insisted that TKey is comparable. The suits my case fine
 
Share this answer
 
v6
Comments
Tomas Takac 17-Jun-15 13:45pm    
I see where you are heading. But you cannot make the comparer generic as ProjectionComparers<telement,tkey> because TKey varies with each accessor. Now it seems it's better to keep the responsibilities separated: one class for the simple comparer another one for concatenation.

You know what would be really cool? If you could write something like this: ToComparer(x => new { x.Urgency, x.Due }) - similar to GroupBy in LINQ. This would however require parsing expressions so it's probably not worth it.
Andy Lanng 17-Jun-15 15:27pm    
Ok, I think i've fixed the soltion now. all the gt; and lt; tags were causing it issues.
If you look now, you can see that I pass through the keys in every case. It is event possible to pass through the comparer for each function but as I don't need to do that I removed it.

I have tested this and it works a treat :)
Tomas Takac 17-Jun-15 16:04pm    
All functions (i.e. properties you use in comparison) must have the same return type. But maybe that's ok for you. Good luck with your project!
Richard Deeming 17-Jun-15 16:20pm    
Building a comparer based on an anonymous type isn't too horrendous - the main problem is making the API calls reasonable, without too many generic type parameters.

For example: https://gist.github.com/RichardD2/a4e4b06e6fe94096a772[^]

IComparer<Person> comparer = AnonymousComparer.Compare<Person>()
.AddFromAnonymousType(p => new { p.Name, p.Age })
.Build();
Tomas Takac 17-Jun-15 16:34pm    
This is what I had in mind. Great work. Oh, and I didn't know about the ExpressionVisitor class. Very nice.

This content, along with any associated source code and files, is licensed under The Code Project Open License (CPOL)



CodeProject, 20 Bay Street, 11th Floor Toronto, Ontario, Canada M5J 2N8 +1 (416) 849-8900