Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Is it possible to combine delegates of different types (use return value as parameter)?

I have a collection that I first need to filter and then select one out of it, but how the collection should be processed is different depending on some parameters. So I went with 2 delegates, but somehow I should combine them:

delegate IEnumerable<T> FilterDelegate(IEnumerable<T> collection);
delegate T SelectorDelegate(IEnumerable<T> collection, ref T previous);

//Combine above two to this one:
delegate T GetItemDelegate(IEnumerable<T> collection, ref T previous);

I tried something like this but it fails because the delegates aren't of matching types:

static GetItemDelegate CreateDelegate(FilterDelegate filter, SelectorDelegate select)
{
    return Delegate.Combine(filter, select) as GetItemDelegate;
}

Question:

Is it possible to create a new delegate that invokes the first one and uses the return value as input parameter for the second one? Without the ref parameter I got it to work with lambdas, but of course I can't use out or ref then.

Compilable example:

namespace DelegateTest
{
   interface INumericValue
   {
       int Number { get; }
   }
   class Test : INumericValue
   {
       public Test(int i)
       {
           Number = i;
       }
       public int Number { get; }
   }
   class Program
   {
      delegate IEnumerable<T> FilterDelegate<T>(IEnumerable<T> collection) where T : class, INumericValue;
      delegate T SelectDelegate<T>(IEnumerable<T> collection, ref T previous) where T : class, INumericValue;

      delegate T CombinedDelegate<T>(IEnumerable<T> collection, ref T previous) where T : class, INumericValue;

       static void Main()
       {
           Test previous = new Test(6);
           List<Test> collection = new List<Test>();
           FilterDelegate<Test> filter = Filter;
           SelectDelegate<Test> select = Select;

           CombinedDelegate<Test> combined = Delegate.Combine(filter, select) as CombinedDelegate<Test>;

           for (int i = 0; i < 10; i++)
               collection.Add(new Test(i));

           //Expected result Test with Number = 7
           Test result = combined(collection, ref previous);
       }

       static IEnumerable<T> Filter<T>(IEnumerable<T> collection) where T : class, INumericValue
       {
          return collection.Where(c => c.Number > 3);
       }
       static T Select<T>(IEnumerable<T> collection, ref T previous) where T : class, INumericValue
       {
           var previousNumber = previous.Number;
           return previous = collection.FirstOrDefault(c => c.Number > previousNumber);
       }
   }
}
like image 852
Alexander Derck Avatar asked Jun 23 '16 12:06

Alexander Derck


3 Answers

You can do it like this, but not sure it's much cleaner than just chaining invocations:

class Program {
    delegate IEnumerable<T> FilterDelegate<T>(IEnumerable<T> collection) where T : class, INumericValue;

    delegate T SelectDelegate<T>(IEnumerable<T> collection, ref T previous) where T : class, INumericValue;

    delegate T CombinedDelegate<T>(IEnumerable<T> collection, ref T previous) where T : class, INumericValue;

    static void Main() {
        Test previous = new Test(6);
        List<Test> collection = new List<Test>();
        FilterDelegate<Test> filter = Filter;
        SelectDelegate<Test> select = Select;
        for (int i = 0; i < 10; i++)
            collection.Add(new Test(i));
        // use explicit types to be able to use ref in lambda
        CombinedDelegate<Test> combined = (IEnumerable<Test> c, ref Test p) => @select(filter(c), ref p);
        Test result = combined(collection, ref previous);
        //Expected result Test with Number = 7
    }

    static IEnumerable<T> Filter<T>(IEnumerable<T> collection) where T : class, INumericValue {
        return collection.Where(c => c.Number > 3);
    }

    static T Select<T>(IEnumerable<T> collection, ref T previous) where T : class, INumericValue {
        // this didn't compile so I changed it.
        foreach (var item in collection) {
            if (item.Number > previous.Number) {
                previous = item;
                break;
            }
        }
        return previous;
    }
}
like image 169
Evk Avatar answered Oct 24 '22 15:10

Evk


You can use ref and out parameters with anonymous methods:

static GetItemDelegate<T> CreateDelegate<T>(FilterDelegate<T> filter, SelectorDelegate<T> select)
{
    return delegate (IEnumerable<T> collection, ref T previous) {
        return select(filter(collection), ref previous);
    };
}
like image 44
Lee Avatar answered Oct 24 '22 14:10

Lee


This is almost a comment to Lee's answer as it is just his answer with old-style C# 2.0 "anonymous methods" converted to C# 3.0 lambda syntax:

static GetItemDelegate<T> CreateDelegate<T>(FilterDelegate<T> filter, SelectorDelegate<T> select)
{
  return
   (IEnumerable<T> collection, ref T previous) => select(filter(collection), ref previous);
}

We note that C# 3.0 was released back in 2007 (Visual Studio 2008).

Remark: The correct declarations of your delegate types are:

delegate IEnumerable<T> FilterDelegate<T>(IEnumerable<T> collection);
delegate T SelectorDelegate<T>(IEnumerable<T> collection, ref T previous);

//Combine above two to this one:
delegate T GetItemDelegate<T>(IEnumerable<T> collection, ref T previous);

The first one is entirely similar to Func<IEnumerable<T>, IEnumerable<T>> of course.

like image 25
Jeppe Stig Nielsen Avatar answered Oct 24 '22 15:10

Jeppe Stig Nielsen