9

What I'm looking for is probably not going to be possible without resorting to reflection. If that's the case, I'd still want to know the best way to pull it off.

Essentially, this is what I want my code to look like:

var instance = new MyClass();
instance.Add<int, string>(x => x.ToString());
instance.Add<string, Warehouse>(x => Warehouse.LookupByName(x));
instance.Add<Warehouse, IList<Supplier>>(x => x.Suppliers());

instance.Chain(3);    // should call each lambda expression in turn

My question is, how can I store these delegates, each with a different signature, in a list in MyClass? And how can I call them later on when I want to, using the return value from each one as the input parameter to the next one?

The inside of MyClass may very well be a mess of List's and all that. But I'm not even sure where to start on this.

(Originally, I wanted to call new MyClass<int, string, Warehouse, IList<Supplier>>(). However, since there's no "type parameter array", I gave up on that approach.)

John Saunders
  • 160,644
  • 26
  • 247
  • 397
Elliot Nelson
  • 11,371
  • 3
  • 30
  • 44
  • 1
    Why not `instance.Add>(x => Warehouse.LookupByName(x.ToString()).Suppliers);`, or even `var supplierLookupFunction = new Func>(x => Warehouse.LookupByName(x.ToString()).Suppliers);`? – Chris Shain Feb 01 '12 at 18:28
  • Please don't prefix titles with "C# Generics: " and such. The tags do a better job of it. – John Saunders Feb 01 '12 at 18:31

4 Answers4

12

Well, you could store them all as Delegate - but the tricky thing is invoking them later.

If you're able to validate that the next delegate at any time is of the right type, e.g. by holding a Type reference for "the current output" you could always store a List<Func<object, object>> and make your Add method something like:

public void Add<TIn, TOut>(Func<TIn, TOut> func)
{
    // TODO: Consider using IsAssignableFrom etc
    if (currentOutputType != typeof(TIn))
    {
        throw new InvalidOperationException(...);
    }
    list.Add(o => (object) func((TIn) o));
    currentOutputType = typeof(TOut);
}

Then to invoke them all:

object current = ...; // Wherever
foreach (var func in list)
{
    current = func(current);
}
Jon Skeet
  • 1,421,763
  • 867
  • 9,128
  • 9,194
  • This is what I was about to answer. A similar solution was discussed here: http://stackoverflow.com/questions/3813261/how-to-store-delegates-in-a-list, in that instance using Action instead of Func since they needed no return value. – David Merriman Feb 01 '12 at 18:36
  • I have a modified version of this solution almost working, so I think this answer is just about perfect based on my question. Now, it does not provide compile-time enforcement that the chain is valid... I might also consider the Linq-esque answer by Servy. I would probably be willing to type `instance = instance.Chain(blah);` each time, if it meant I'd get compile-time checking that the sequence is valid. – Elliot Nelson Feb 01 '12 at 19:13
  • @ElliotNelson: Yes, you could certainly do that - you could still even have a single object, and just return some sort of "token" (with an internal reference back to the list) to preserve the generics. – Jon Skeet Feb 01 '12 at 19:21
2

The Linq Select statement essentially does this...

var temp = instance.Select(x => x.ToString())
.Select(x => WareHouse.LookupByName(x))
.Select(x=> x.Suppliers());

List<List<Suppliers>> = temp.ToList(); //Evaluate statements

You can also store each intermediate Select call as an Enumerable to have the stated method you use in the OP.

Servy
  • 202,030
  • 26
  • 332
  • 449
1
class Program
{
    static void Main(string[] args)
    {
        var instance = new MyClass();
        instance.Add<int, string>(i => i.ToString());
        instance.Add<string, int>(str => str.Length);
        instance.Add<int, int>(i => i*i);

        Console.WriteLine(instance.Chain(349));
        Console.ReadLine();

    }
}

public class MyClass
{
    private IList<Delegate> _Delegates = new List<Delegate>();

    public void Add<InputType, OutputType>(Func<InputType, OutputType> action)
    {
        _Delegates.Add(action);
    }

    public object Chain<InputType>(InputType startingArgument)
    {
        object currentInputArgument = startingArgument;
        for (var i = 0; i < _Delegates.Count(); ++i)
        {
            var action = _Delegates[i];
            currentInputArgument = action.DynamicInvoke(currentInputArgument);
        }
        return currentInputArgument;
    }
}
Ian Newson
  • 7,679
  • 2
  • 47
  • 80
0

If you want compile time type checking, what you are doing sounds suspiciously like plain old generic delegates. Assuming that there is some value to storing the individual functions that were Added (other than the Int to String conversion) and composing them later, you can do something like this:

var lookupWarehouseByNumber = new Func<int, Warehouse>(i => Warehouse.LookupByName(i.ToString()));
var getWarehouseSuppliers = new Func<Warehouse, IEnumerable<Supplier>>(w => w.Suppliers);
var getWarehouseSuppliersByNumber = new Func<int, IEnumerable<Supplier>>(i => getWarehouseSuppliers(lookupWarehouseByNumber(i)));
Chris Shain
  • 50,833
  • 6
  • 93
  • 125