views:

84

answers:

3

In C# it is possible to create higher order functions, ie. functions g taking functions as arguments. Say I want to create such a function which given a function f and returns another function extending its functionality. How do I define argument names for the returned enhanced method? The motivation being, that I'm working with higher order methods in general, some of which produce new methods.. and these can be difficult to use as there is no parameter names etc. attached to them.

An example illustrating how g and f respectively could be defined in C#:

I define a method Extend that can extens methods taking a T as argument and returning an S.

static class M
{
    public static Func< T, S> Extend(Func< T, S> functionToWrap)
    {
      return (someT) =>
      {
        ...
        var result = functionToWrap(someT);
        ...
        return result;
      };
    }
}

We can then extend a method on our class without changing the method.

class Calc2
{
    public Func< int, int> Calc;
    public Calc2()
    {
      Calc = M.Extend< int, int>(CalcPriv);
    }
    private int CalcPriv(int positiveNumber)
    {
      if(positiveNumber < 0) throw new Exception(...);
      Console.WriteLine("calc " + i);
      return i++;
    }
}

Alas, the argument name positiveNumber is no longer available, since the only available information is Func<int, int> Calc. That is when I use the extended method by typing new Calc2().Calc(-1) I get no help from the IDE that in fact my argument is wrong.

It would be nice if we could define a delegate and cast it to this, however, this is not possible.

Any suggestions?

A: 

That's the beauty of anonymous delegates; they're anonymous. A Func is a delegate to a method that takes an int and returns an int. What the function actually does, and therefore the names of the parameters, is irrelevant.

The only way this would work is if Calc were of a named delegate type, defined with a signature identical to CalcPriv. It would all still work as written, including the anonymous extension, but you'd have a named parameter for Calc.

Another way to impart the information would be to xml-doc Calc with a ///<summary></summary> tag describing the parameter Calc was designed to take.

Lastly, you could derive from Func<T,TResult> to create a TakesPositiveInteger<T,TResult> class. This is going a little far, but if you wanna talk about self-documenting code...

KeithS
Changing the code to `public delegate int Calc(int someArg); public Calc CalcM; public Calc2() { CalcM = (Calc) M.Extend<int, int>(CalcPriv); }` won't work
Carlo V. Dango
+2  A: 

If you only want a fixed delegate type with named parameters then you can just define your own delegate type:

Func is just defined like this:

public delegate TResult Func<in T, out TResult>(T arg)

So you can define your own delegate type with the parametername you want.

But in your example you want to preserve the delegate type passed in, so this doesn't work here. In theory you could define your function like this:

public static T Extend(T functionToWrap)
{
}

Unfortunately there are no good generic constraints which restrict the input type to a delegate with the right signature(Or even just delegates at all). But without these constraints the implementation would become so ugly, and you'd lose so much static type safety that IMO it's not worth it.

One workaround is using:

new MyFunc(Extend(f))

where MyFunc defines the parameternames you want.

Or you could do the following:

public static T ConvertDelegate<T>(Delegate d)
{
    if (!(typeof(T).IsSubclassOf(typeof(Delegate))))
        throw new ArgumentException("T is no Delegate");
    if (d == null)
        throw new ArgumentNullException();
    MulticastDelegate md = d as MulticastDelegate;
    Delegate[] invList = null;
    int invCount = 1;
    if (md != null)
        invList = md.GetInvocationList();
    if (invList != null)
        invCount = invList.Length;
    if (invCount == 1)
    {
        return (T)(object)Delegate.CreateDelegate(typeof(T), d.Target, d.Method);
    }
    else
    {
        for (int i = 0; i < invList.Length; i++)
        {
            invList[i] = (Delegate)(object)ConvertDelegate<T>(invList[i]);
            }
            return (T)(object)MulticastDelegate.Combine(invList);
        }
    }

public static TDelegate Extend<TDelegate,TArg,TResult>(Func<TArg,TResult> functionToWrap)
where TDelegate:class
    {       
        Func<TArg,TResult> wrappedFunc= DoTheWrapping(functionToWrap);
        return ConvertDelegate<TDelegate>(wrappedFunc);
    }

BTW the ConvertDelegate function can be used to get Co/Contravariance on Delegates even prior to .net 4.

CodeInChaos
+1, I didn't realize you could construct a parameter-matching delegate like that.
Dan Bryant
I'd prefer the workaround where the caller constructs an new delegate of the desired type. It's still very short, easy to read and typesafe.
CodeInChaos
+1  A: 

It is possible to cast to a delegate with named parameters, by dynamically binding a newly constructed delegate to the underlying Func delegate method:

public delegate double CalcFunc(double value);

static class M
{
    public static Func<T, S> Extend<T,S>(Func<T, S> functionToWrap)
    {
      return (someT) => functionToWrap(someT);
    }
}

class Program
{
    private static double Calc(double input)
    {
        return 2*input;
    }

    [STAThread]
    static void Main()
    {
        Func<double, double> extended = M.Extend<double, double>(Calc);

        CalcFunc casted = (CalcFunc)Delegate.CreateDelegate(typeof(CalcFunc), extended.Target, extended.Method);
        Console.WriteLine(casted(2) + " == 4");
        Console.WriteLine("I didn't crash!");
        Console.ReadKey();
    }
}

One word of warning: this will not do any compile-time checking of the cast. If the signatures don't match exactly, you'll get a bind failure at runtime (excepting special support for contravariance in .NET 4).

Dan Bryant
Wow great, maybe the Extend method should take a delegate type as an argument too so it could be the one performing the Delegate.CreateDelegate(typeof(...)))
Carlo V. Dango
Does this work correctly if extended is a multi cast delegate? But since Extend isn't useful on multicast delegates anyways a check+exception should be enough in that case. And if the desired delegate type is known where you create the code you can simply use new CalFunc(extended).
CodeInChaos
@Carlo, see CodeInChaos's solution; the delegate constructor is cleaner and more type-safe.
Dan Bryant
@CodeInChaos, you're correct, this won't work if the delegate is multi-cast. You would have to manually re-construct the entire InvocationList worth of delegates.
Dan Bryant
Unfortunately I didn't succeed trying to invoke the delegate constructor if the delegate type is a generic parameter. For some reason Activator.CreateInstance didn't bind to that constructor.
CodeInChaos