tags:

views:

722

answers:

2

In C#, how does one create a delegate type that maps delegate types to a delegate type? In particular, in my example below, I want to declare a delegate Sum such that (borrowing from mathematical notation) Sum(f,g) = f + g. I then want to invoke Sum(f,g) -- such as Sum(f,g)(5) [this meaning f(5) + g(5)].

class  Example
{
delegate  int  IntToInt  ( int i ) ;

public static int Double ( int i )  { return i * 2 ; }
public static int Square ( int i )  { return i * i ; }

delegate  IntToInt  IntToIntPair_To_IntToInt  ( IntToInt f, IntToInt g ) ;

public static IntToInt Sum ( IntToInt f, IntToInt, g )  { return f + g ; }

public static void Main ( )
 {
 IntToInt  DoubleInstance  =  Double ;
 IntToInt  SquareInstance  =  Square ;

 IntToIntPair_To_IntToInt  SumInstance  =  Sum ;

 System.Console.WriteLine
       ( SumInstance ( DoubleInstance, SquareInstance ) ( 5 ) ) ;
 // should print 35 = 10 + 25 = Double(5) + Square(5)
 }
}
+11  A: 

You just need to express the specific types. For example:

Func<Func<int, int>, Func<int, int>>

represents a function which takes a (function converting an int to a second int) and returns a (function converting an int to a second int). Or to take two functions and return a third:

Func<Func<int, int>, Func<int, int>, Func<int, int>>

For example:

Func<Func<int, int>, Func<int, int>> applyTwice = (f => x => f(f(x));

This can be returned generically by a method:

public static Func<Func<T,T>, Func<T,T>> ApplyTwice<T>()
{
    return func => x => func(func(x));
}

If you want to sum two functions, you might do:

public static Func<int, int> Sum(Func<int, int> first, Func<int, int> second)
{
    return x => first(x) + second(x);
}

Now to apply it:

Func<int, int> doubler = x => x * 2;
Func<int, int> squarer = x => x * x;
Func<int, int> doublePlusSquare = Sum(doubler, squarer);

Console.WriteLine(doublePlusSquare(5)); // Prints 35

(Untested, but should be okay...)


If you don't have C# 3 and .NET 3.5 available to you, then declare the following delegates:

public delegate TResult Func<TResult>();
public delegate TResult Func<T, TResult>(T arg);
public delegate TResult Func<T1, T2, TResult>(T1 arg1, T2 arg2);

(There's more on my C# Versions page.)

Then you'll need to use anonymous methods, e.g.

public static Func<int, int> Sum(Func<int, int> first, Func<int, int> second)
{
    return delegate(int x) { return first(x) + second(x); };
}

Func<int, int> doubler = delegate (int x) { return x * 2; };
Func<int, int> squarer = delegate (int x) { return x * x; };
Func<int, int> doublePlusSquare = Sum(doubler, squarer);

Console.WriteLine(doublePlusSquare(5)); // Prints 35
Jon Skeet
Dammit, you get one sentence off an answer and bam, skeeted again.
Jeff Yates
Jon: My software is somewhat dated. Does it matter that I'm using CLR Version 2.0.50727.42 ?
JaysonFix
@JaysonFix: Yes, unfortunately, the lambda syntax (x=>x*2 for example) and the Func<T> delegate types are C#3/.NET3.5 constructs. To do this in .NET2.0 and C#2 will take more effort.
Jeff Yates
You will need to declare your own IntToInt and IntFuncToIntFunc types so that you can do something similar.
Jeff Yates
Editing to reflect this...
Jon Skeet
Jeff: Unfortunately, I'm using C#2.
JaysonFix
Jon: I'm getting a compiler-error: error CS1661: Cannot convert anonymous method block to delegate type 'Example.Func<Example.Func<int,int>,Example.Func<int,int>,Example.Func<int,int>>' because the specified block's parameter types do not match the delegate parameter types
JaysonFix
(You say you're using C#2 and CLR 2.0.5.... etc.) Note that C# 2.0 and 3.5 both run on the same CLR version, so you can use C# 3.5 in an old project - you just can't use the new library types. In short, if you have a new compiler, you _can_ use the simpler lambda notation and type inference options, you'll just need to declare Func<> yourself.
Eamon Nerbonne
Summary: if possible, try to upgrade to use C# 3.5, and simply link to the old library versions for the best of both worlds: simpler syntax and backwards compatibility.
Eamon Nerbonne
public static Func<Func<int, int>, Func<int, int>, Func<int, int>> Sum(Func<int, int> first, Func<int, int> second){ return x => first(x) + second(x);}should only return Func<int, int> because that's the type of doublePlusSquare.
J.W.
@J.W - Oops, thanks, editing.
Jon Skeet
I am still learning, so I verified your comments "(Untested, but should be okay...)" :-).Alternatively, you could use Console.WriteLine(doublePlusSquare (doubler, squarer).Invoke(5));That should work too.
J.W.
squarer = delegate { int x }should besquarer = delegate ( int x )Otherwise, works well for me.Thanks Jon and everyone!
JaysonFix
Darn it. I really should try to post on a machine where I can test this stuff easily :)
Jon Skeet
One small thing: it seems the line reading "public delegate TResult Func<TResult>();" can be omitted from the C#2 solution.
JaysonFix
A: 

Like Jon said:

Func<int, int> f = i => i * 2;
Func<int, int> g = i => i * i;
Func<int, int> sum = i => f(i) + g(i);

However, if you'd want to create a Sum method for other types than Func<int,int>, you'd have to go with

static Func<T, T> Sum<T>(Func<T, T> f, Func<T, T> g)
{
    ParameterExpression p = Expression.Parameter(typeof(T), "i");
    Expression<Func<T, T>> sumExpression =
            Expression.Lambda<Func<T, T>>(
                Expression.Add(
                    Expression.Invoke(Expression.Constant(f), p),
                    Expression.Invoke(Expression.Constant(g), p)),
                p);
    return sumExpression.Compile();
}

This works for any type T that defines the "+" operator. Just be careful of the performance penalty you'd get for compiling a lambda expression.

Ruben
Of course, this is specific to C# 3.0 / .NET 3.5.
Ruben
I think you mean "types other than int".
Jon Skeet
Func<int,int> actually; forgot to escape the < >.
Ruben