Domanda

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.

È stato utile?

Soluzione

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

Altri suggerimenti

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];
        };      
    }
}

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.

Autorizzato sotto: CC-BY-SA insieme a attribuzione
Non affiliato a StackOverflow
scroll top