11

I want to implement an extension method for a method. Consider the following code sample (http://dotnetfiddle.net/HztiOo) :

using System;
using System.Collections.Generic;

public class Program
{
    public static void Main()
    {
        A a = new A();
        // Noticed that Next() is called twice
        Console.WriteLine(a.Next(1));
        Console.WriteLine(a.Next(1));

        // Works
        var withCache = ((Func<int,int>)a.Next).AddCaching();
        withCache = new Func<int,int>(a.Next).AddCaching();
        withCache = ExtensionMethods.AddCaching<int,int>(a.Next);

        // Doesn't work :(
        // withCache = a.Next.AddCaching<int,int>();
        // Func<int,int> withCache = a.Next.AddCaching();

        // Notice that Next() is only called once
        Console.WriteLine(withCache(1));
        Console.WriteLine(withCache(1));
    }
}

public class A
{
    public int Next(int n)
    {
        Console.WriteLine("Called Next("+n+")");
        return n + 1;
    }
}

public static class ExtensionMethods
{
    public static Func<TKey,TVal> AddCaching<TKey,TVal>(this Func<TKey,TVal> fetcher)
    {
        var cache = new Dictionary<TKey, TVal>();
        return k =>
        {
            if (!cache.ContainsKey(k)) cache[k] = fetcher(k);
            return cache[k];
        };
    }
}

I would like to be able to call the extension method without an explicit cast. In both "doesn't work" examples above, the type system should be able to figure out which overload to use on its own...

Why can't I just use a.Next.AddCaching<int,int>() ?

Note: this is just an example, I am not interested in discussing the best way to add a cache to a method invocation, as there are many other possibilities for this kind of extensions.

Thomas
  • 714
  • 5
  • 16
  • 2
    related - http://stackoverflow.com/a/543977/961113 – Habib Apr 02 '14 at 14:15
  • Thanks for the link, however in my case the type system has enough information to identify which overload I am talking about (as can be seen in the "works" examples). – Thomas Apr 02 '14 at 14:31
  • 1
    This answer might interest you http://stackoverflow.com/questions/2852161/c-sharp-memoization-of-functions-with-arbitrary-number-of-arguments/2852595#2852595 – Eric Lippert Apr 02 '14 at 15:45
  • 1
    The second half of this answer is also germane to your question. http://stackoverflow.com/questions/6187979/delegate-as-first-param-to-an-extension-method/6190125#6190125 – Eric Lippert Apr 02 '14 at 15:48
  • 1
    Note that in your memoizer you end up doing two cache searches if there is a cache hit. You can do better than that; see the answer I linked above for a technique that reduces that to a single cache search. – Eric Lippert Apr 02 '14 at 15:51
  • @EricLippert ah yes that second answer you linked made it quite clear to me (*we do not discover extension methods when the receiver is something that lacks its own type*). I will also remove the second cache search as you suggested. Thanks :) – Thomas Apr 02 '14 at 16:08

3 Answers3

8

According to Eric Lippert blog method group is typeless expression. And you can't do anything, just deal with it.

That's exact reason why you can't implicitly cast it to specific delegate and add extension method to it

Uriil
  • 11,948
  • 11
  • 47
  • 68
  • 1
    Well, that's a damn shame, maybe something to improve for C# 6.0? :) Still, there is *some* implicit casting when I do `ExtensionMethods.AddCaching(a.Next)` – Thomas Apr 02 '14 at 15:22
4

You can achieve something stylistically similar to what you are looking for by exposing you method as a Func, as follows (https://dotnetfiddle.net/BTyJdU). Obviously this involves modifying the class, so it can't be achieved with an extension method only.

using System;
using System.Collections.Generic;

public class Program
{
    public static void Main()
    {
        A a = new A();
        // Noticed that Next() is called twice
        Console.WriteLine(a.Next(1));
        Console.WriteLine(a.Next(1));

        // Works now :)
        var withCache = a.Next.AddCaching<int,int>();
        withCache = a.Next.AddCaching();

        // Notice that Next() is only called once
        Console.WriteLine(withCache(1));
        Console.WriteLine(withCache(1));
    }
}

public class A
{
    public Func<int,int> Next;

    public A()
    {
        Next = NextInternal;    
    }

    private int NextInternal(int n)
    {
        Console.WriteLine("Called Next("+n+")");
        return n + 1;
    }
}

public static class ExtensionMethods
{
    public static Func<TKey,TVal> AddCaching<TKey,TVal>(this Func<TKey,TVal> fetcher)
    {
        var cache = new Dictionary<TKey, TVal>();
        return k =>
        {
            if (!cache.ContainsKey(k)) cache[k] = fetcher(k);
            return cache[k];
        };
    }
}

I've also put together a fiddle which uses extension methods only. It involves calling an extension on the object rather than the method: https://dotnetfiddle.net/XaLndp

using System;
using System.Collections.Generic;

public class Program
{
    public static void Main()
    {
        A a = new A();
        // Noticed that Next() is called twice
        Console.WriteLine(a.Next(1));
        Console.WriteLine(a.Next(1));

        // An alternative, that uses extension methods only
        var withCache = a.AddCaching<A,int,int>(x => x.Next);

        // Notice that Next() is only called once
        Console.WriteLine(withCache(1));
        Console.WriteLine(withCache(1));
    }
}

public class A
{
    public int Next(int n)
    {
        Console.WriteLine("Called Next("+n+")");
        return n + 1;
    }
}

public static class ExtensionMethods
{   
    public static Func<TKey,TVal> AddCaching<T,TKey,TVal>(this T wrapped, Func<T,Func<TKey,TVal>> fetcher)
    {
        var cache = new Dictionary<TKey, TVal>();
        return k =>
        {
            if (!cache.ContainsKey(k)) cache[k] = fetcher(wrapped)(k);
            return cache[k];
        };      
    }
}
DaveF
  • 391
  • 2
  • 9
-1

You are able to write extension methods for delegates. In your example:

Why can't I just use a.Next.AddCaching() ?

In that question, a.Next isn't a type. Extension methods only work for types. Think about it. In your AddCaching extension method, what would you write after this? You need a type. In this case, you used the delegate Func<TKey,TVal>. That means it'll extend that delegate. For you example to compile, you need to write:

((Func<int,int>)a.Next).AddCaching<int,int>()

This will compile properly. Additionally, since you are defining the generic types in the delegate, you can actually call it like this:

((Func<int,int>)a.Next).AddCaching()

It'll know it is using <int,int> from the delegate.

So, you were close, you just needed to cast a.Next to a type, the delegate Func<int,int> for it to compile. It is the same rules that apply to extending any other type in the language.

Michael Yanni
  • 1,476
  • 4
  • 17
  • 29
  • You may want to wrap the method. And Next is a method, the idea is that you do something like AOP check the cache, if the item doesn't exist you call the method then add the result to the cache. – leat Oct 14 '17 at 18:28
  • @leat AOP isn't really supported in C#, but you can get close with using Attributes. They use reflection, so there will be a performance hit, but nothing major in most cases. Above the Next declaration, make an attribute like [Cacheable] and wire up the logic for it. :-) – Michael Yanni Oct 23 '17 at 16:17