anjhinz
anjhinz

Reputation: 41

Can I Instantiate an IComparer class at runtime for OrderBy regardless of type?

Does anybody know if this is possible? I have a custom Attribute class that defines a Type that implements IComparer for a property. I would like to access that Type via reflection and instantiate it for use in IEnumerable.OrderBy():

[System.AttributeUsage(System.AttributeTargets.Property)]
public class SortComparer : System.Attribute
{
    public Type ComparerType;

    public SortComparer(Type ComparerType)
    {
        this.ComparerType = ComparerType;
    }
}


var property = typeof(PerTurbineResultViewModel).GetProperty(SortColumn);

var sortComparer = property.GetCustomAttributes(typeof(SortComparer), true).FirstOrDefault() as SortComparer;




if (sortComparer != null)
{
    var insta = Activator.CreateInstance(sortComparer.ComparerType);
    this.Results = lstResults.Select(r => new ResultViewModel(r)).
                        OrderBy(p => property.GetValue(p, null), insta));
}

The above does not compile since OrderBy<TSource, TResult> requires the second argument to be of type IComparer<TResult> (which is unknown at compile time).

Is there a way to instantiate the 'insta' variable and cast it as IComparer<TResult> using the type information found in 'property'?

EDIT: The first option got me very close:

Func<ResultViewModel, PropertyInfo> sel = t => property;

this.Results = infoGeneric.Invoke(Results, new object[] { vals, sel, insta }) as IEnumerable<ResultViewModel>;

Except I get a runtime exception for the property selector:

// Object of type 'System.Func`2[ResultViewModel,System.Reflection.PropertyInfo]' cannot be converted to type 'System.Func`2[ResultViewModel,System.Reflection.RuntimePropertyInfo]'.

RuntimePropertyInfo seems to be internal... is there another way to pass in the property selector?

Upvotes: 4

Views: 812

Answers (2)

guneysus
guneysus

Reputation: 6502

Sometimes it is better to use functional approach by instantiating helper classes by factory functions.

  • A generic class that implements IComparer interface [1]

    internal class GenericEqualityComparer<T> : IEqualityComparer<T>
    {
      private readonly Func<T, T, bool> _comparer;
    
      public GenericEqualityComparer(Func<T, T, bool> comparer) {
          _comparer = comparer;
      }
    
      public bool Equals(T x, T y)
      {
          return _comparer(x, y);
      }
    
      public int GetHashCode(T obj)
      {
          return base.GetHashCode();
      }
    }
    
  • Use a factory method to instantiate generic comparer class [2] and pass Func<T, T, int> compare delegate as parameter to this class

      IComparer<Point> comparer = comparator<Point>((a, b) =>
      {
          return (a.X + a.Y) - (b.X + b.Y);
      });
    
      comparer.Compare(new Point(0, 0), new Point(0, 0)); // should return zero
      comparer.Compare(new Point(100, 0), new Point(0, 0)); // should return a value greater than zero
      comparer.Compare(new Point(0, 0), new Point(100, 0)); // should return a value less than zero
    

Upvotes: 0

Jon Skeet
Jon Skeet

Reputation: 1502286

Basically you've got two options:

  • Invoke OrderBy using reflection as well: get the generic method definition, then call MethodInfo.MakeGenericMethod to get the constructed version, then invoke it.
  • Use dynamic in C# 4 to get the built-in mini-compiler to do the heavy lifting for you

EDIT: As property.GetValue() only returns object, you'll almost certainly have to go via the reflection route. Either that, or you could use a third, somewhere horrible but really easy option...

... make all your comparers implement IComparer<object>, and cast within them. Then your TResult would be object, and you can just cast:

object tmp = Activator.CreateInstance(sortComparer.ComparerType);
IComparer<object> comparer = (IComparer<object>) tmp;
this.Results = lstResults.Select(r => new ResultViewModel(r))
                         .OrderBy(p => property.GetValue(p, null), comparer);

Upvotes: 3

Related Questions