2

Is it possible to return a generic Func from a method? What I want to do is something like the GetSortFunc below.

public class Example
{
    private List<MyObject> _objects;
    public Example()
    {
        _objects = new List<MyObject>
                        {
                            new MyObject {Id = 1, Name = "First", Value = 100.0},
                            new MyObject {Id = 2, Name = "Second", Value = 49.99},
                            new MyObject {Id = 3, Name = "Third", Value = 149.99}
                        };
    }

    public void Sort(SomeEnum sortOptions)
    {
        _objects = _objects.OrderBy(GetSortFunc(sortOptions));
    }

    private Func<MyObject, TKey> GetSortFunc(SomeEnum sortOptions)
    {
        switch (sortOptions)
        {
            case SomeEnum.First:
                return x => x.Id;
            case SomeEnum.Second:
                return x => x.Name;
            case SomeEnum.Third:
                return x => x.Value;
        }
    }
}

The SomeEnum and MyObject looks like this:

public enum SomeEnum
{
    First,
    Second,
    Third
}

public class MyObject
{
    public int Id { get; set; }
    public string Name { get; set; }
    public double Value { get; set; }
}

Is this possible to do or am I on the wrong track?

4
  • 2
    yep it is possible :-) Commented Jan 21, 2014 at 12:56
  • Looks like it would be a lot less work for you to just use .OrderBy() as it is Commented Jan 21, 2014 at 12:57
  • First of all, this is possible, secondly, whether it is possible or not, you can compile your code and find out. Commented Jan 21, 2014 at 12:59
  • @Alex yeah of course it would be easier to just call OrderBy in this example, but I'm going to have to switch between OrderBy and OrderByDescending in my implementation and don't want a big mess of if/else and/or switch statements Commented Jan 21, 2014 at 13:11

3 Answers 3

3

The problem is that the return type will vary with the type of TKey. Also, the type parameters of OrderBy will vary. I suggest you just duplicate the OrderBy calls:

IEnumerable<MyObject> ApplySortOrder(IEnumerable<MyObject> items, SomeEnum sortOptions)
{
    switch (sortOptions)
    {
        case SomeEnum.First:
            return items.OrderBy(x => x.Id);
        case SomeEnum.Second:
            return items.OrderBy(x => x.Name);
        case SomeEnum.Third:
            return items.OrderBy(x => x.Value);
    }
}

Alternatively, make GetSortFunc return delegate and call OrderBy dynamically:

private Delegate GetSortFunc(SomeEnum sortOptions)
{
    switch (sortOptions)
    {
        case SomeEnum.First:
            return new Func<MyObject, int>(x => x.Id);
        case SomeEnum.Second:
            return new Func<MyObject, string>(x => x.Name);
        case SomeEnum.Third:
            return new Func<MyObject, int>(x => x.Value);
    }
}

//...

Enumerable.OrderBy(_objects, (dynamic)GetSortFunc(sortOptions));

This would pick the right overload at runtime.

Sign up to request clarification or add additional context in comments.

3 Comments

Thanks! I'll probably go with the second example as I'm going to have to switch between OrderBy and OrderByDescending which would make the first approach a bit messy
The compiler doesn't like the dynamic approach, says that extension methods cannot be dynamically dispatched
Call it like this: Enumerable.OrderBy(items, ...). The two little problems mentioned in the comments here are just details. I did not test the code. The idea works.
2

Simplest way fix your function change it like

private Func<MyObject, object> GetSortFunc(SomeEnum sortOptions)
{
    switch (sortOptions)
    {
        case SomeEnum.First:
            return x => x.Id;
        case SomeEnum.Second:
            return x => x.Name;
        case SomeEnum.Third:
            return x => x.Value;
        default:
            return x => x.Id;
    }
}

3 Comments

It's so obvious! I feel a bit stupid now for not thinking of this :)
Seems to work. I think the runtime can determine that the object is actually of type int/string/double
I didn't know this would work. It seems to try to cast to IComparable or something like that.
2

A generic Func, or any other generic type, can only be returned from context which has a declared generic parameter. This generic parameter needs to exist on either the method or one of the containing types of the method. In this case there is no generic parameter hence the code cannot function

This is really not a great example of a function that should be generic though. If it were generic it would need to look something like this

private Func<MyObject, TKey> GetSortFunc<TKey>(SomeEnum sortOptions) 
{
    switch (sortOptions)
    {
        case SomeEnum.First:
            return x => (TKey)(object)x.Id;
        case SomeEnum.Second:
            return x => (TKey)(object)x.Name;
        case SomeEnum.Third:
            return x => (TKey)(object)x.Value;
    }
}

All that ugly casting exists because the C# compiler can't find an existing conversion between int and string and the TKey parameter (because it could be literally any type). Also it won't work unless the type of the property matches TKey which is generally a sign of code that can't be generic.

Comments

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.