C#方法组的扩展方法

11

我想要为一个方法实现一个扩展方法。考虑以下代码示例 (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];
        };
    }
}

我希望能够在不进行显式转换的情况下调用扩展方法。在上述两个“不起作用”的示例中,类型系统应该能够自己找出要使用哪个重载...

为什么我不能只使用a.Next.AddCaching<int,int>()

注意:这只是一个例子,我不想讨论添加缓存到方法调用的最佳方式,因为还有许多其他可能性。


2
相关 - https://dev59.com/WEbRa4cB1Zd3GeqP1YxS#543977 - Habib
谢谢提供链接,但在我的情况下,类型系统具有足够的信息来识别我正在谈论的重载(如“工作”示例所示)。 - Thomas
1
这个答案可能会让你感兴趣 https://dev59.com/WXE85IYBdhLWcg3wShef#2852595 - Eric Lippert
1
这个答案的后半部分也与你的问题相关。https://dev59.com/M1fUa4cB1Zd3GeqPK8Fk#6190125 - Eric Lippert
1
请注意,在您的备忘录中,如果存在高速缓存命中,则最终会执行两次缓存搜索。你可以做得更好;请参见我上面链接的答案,了解一种将其减少为单个缓存搜索的技术。 - Eric Lippert
@EricLippert 啊,是的,你链接的第二个答案让我明白了 (当接收器缺乏自己的类型时,我们不会发现扩展方法)。我也会像你建议的那样删除第二个缓存搜索。谢谢 :) - Thomas
3个回答

8
根据Eric Lippert博客,方法组是一种无类型表达式。你不能做什么,只能处理它。
这正是你不能将其隐式转换为特定委托并向其添加扩展方法的确切原因。

1
好吧,那真是太遗憾了,也许这是C# 6.0需要改进的地方?:) 不过,当我执行 ExtensionMethods.AddCaching<int,int>(a.Next) 时,仍然存在一些隐式转换。 - Thomas

4
您可以通过将方法公开为Func来实现与您正在寻找的外观风格类似的效果,如下所示(https://dotnetfiddle.net/BTyJdU)。显然,这涉及修改类,因此无法仅使用扩展方法实现。
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];
        };
    }
}

我还制作了一个仅使用扩展方法的 fiddle。它涉及对对象调用扩展而不是方法: 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];
        };      
    }
}

-1

您可以为委托编写扩展方法。在您的示例中:

为什么我不能只使用 a.Next.AddCaching()?

在这个问题中,a.Next不是一个类型。扩展方法只适用于类型。想一想,在您的AddCaching扩展方法中,您会在this之后写什么?您需要一个类型。在这种情况下,您使用了委托Func<TKey,TVal>。这意味着它将扩展该委托。为了使您的示例编译,您需要编写:

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

这将正确编译。此外,由于您在委托中定义了通用类型,因此实际上可以像这样调用它:

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

它将知道它正在使用委托中的 <int,int>

所以,你几乎做到了,你只需要将 a.Next 强制转换为一个类型,即委托 Func<int,int>,就可以编译了。这是语言中扩展任何其他类型所适用的相同规则。


你可能想要包装这个方法。接下来是一个方法,思路是类似于AOP检查缓存,如果该项不存在,则调用方法,然后将结果添加到缓存中。 - leat
@leat AOP 在 C# 中并不是真正支持的,但是您可以使用属性来接近它。它们使用反射,因此可能会有性能损失,在大多数情况下不会很严重。在 Next 声明上方,创建一个类似 [Cacheable] 的属性,并为其连接逻辑。 :-) - Michael Yanni

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接