嵌套方法?它们有什么用处?

13

我正在学习C#和Python的一些新东西。结果发现这两种语言都支持嵌套方法(C#有点)。

Python:

def MyMethod():

    print 'Hello from a method.'

    def MyInnerMethod():
        print 'Hello from a nested method.'

    MyInnerMethod()

C#(使用.NET 3.5的新功能):

static void Main()
{
    Console.WriteLine("Hello from main.");

    Func<int, int> NestedMethod =
        (x) =>
        {
            Console.WriteLine("In nested method. Value of x is {0}.", x);
            return x;
        };

    int result = NestedMethod(3);
}

为什么嵌套方法如此重要?它们有哪些作用呢?


**该 C# 代码尚未经过测试。如果无法编译,请随意编辑。


事实上,C#示例语法(匿名委托语法)并不是3.5中的新内容;它在2.0中就已经存在了。如果您使用了lambda函数,那就是C# 3语法。 - Erik Forbes
@Erik:该死,我知道我会在某个地方搞砸的。;-) - Bob Dylan
如果你觉得我的音乐不错,那你一定要看看我的代码。 - Bob Dylan
8个回答

17
首先要认识到我无法给您一个完整的列表。如果您问“螺丝刀有什么用处?”,我会谈论螺钉和油漆罐盖,但会错过它们在白蚁检查方面的价值。当您问“嵌套函数有什么用处?”时,我可以告诉您有关作用域、闭包和入口点的信息。
首先,嵌套函数可以是类的一种替代方法。我最近编写了一些渲染代码,为特定的标记代码提供了文件名并返回了位图。这自然而然地导致了一些被命名为grab_markup_from_filename()和render_text_onto_image()的函数。最清晰的组织方式是一个名为generate_png_from_markup_filename()的入口点。该入口点使用嵌套函数来完成其任务,因为没有带状态的对象,所以不需要类。我的另一个选择是创建一个具有隐藏辅助函数的私有方法的模块,但那样会更加混乱。
def generate_png_from_markup_filename(filename):
    def grab_markup_from_filename():
        ... 
    ... # code that calls grab_markup_from_filename() to make the image
    return image

其次,我使用闭包的嵌套。最常见的例子是用来创建装饰器。关键是返回对内部函数的引用,这样内部函数和外部参数值就不会被垃圾回收。

def verify_admin(function_to_call):
    def call_on_verify_admin(*args, **kwargs):
        if is_admin(global.session.user):
            return function_to_call(*args, **kwargs)
        else:
           throw Exception("Not Admin")
    return call_on_verify_admin  # the return value of verify_admin()

这是使用它的方式:

 def update_price(item, price):
     database.lookup(item).set_field('price', price)
 update_price = verify_admin(update_price)

或者更简洁地说:

 @verify_admin
 def update_price(item, price):
     database.lookup(item).set_field('price', price)

是的,它应该很难追踪。请记住,“def”只是另一种语句。

因此,嵌套类有两个有用的场景。当然还有其他场景,这是一个工具。


首先,是否使用内部函数取决于程序员。我个人认为你为什么不想使用私有函数呢?例如,如果我有一个类使用了许多私有函数,我可以将这些私有函数根据其他类的要求进行移动。它们可能属于通用的帮助器类。使用本地函数会使您的方法更难阅读和测试。我相信有一些用例最好使用本地函数,只是我想不出来。我在谈论C#,而在JavaScript的野西中,任何事情都可以发生。 - msteel9999
其实我已经想到了自己的问题的答案。使用内部函数可以让你命名和分离一部分逻辑,但仍然可以访问父函数中的外部作用域变量。我想如果你不测试私有函数,那么这就没那么重要了。 - msteel9999

7
嵌套方法很有用,因为有许多情况下,您希望传递行为,而不仅仅是数据。在许多情况下,更清晰易懂,更容易跟踪,并且更容易快速编写,只需在要使用它的方法体中定义该行为即可。
在.NET中,LINQ是一个很好的例子。假设您想创建一个列表,其中每个值都是原始列表中其值的两倍:
list.Select(i => i*2)

其中i => i*2等同于:

Func<int,int> double = delegate(int x) { return x*2; }

使用嵌套函数比创建一个单独的函数要简单清晰得多。

此外,您声明的函数可能在其他地方没有意义(即在方法范围之外)。 在C#中,您甚至可以引用在方法体内声明的变量 - 您无法使用非嵌套函数做到这一点。

当然,您也可以滥用嵌套函数...


我不知道委托被称为“嵌套方法”。 - Esteban Araya
实质上,您是在使用委托来创建嵌套方法,不是吗? - Bob Dylan
是的,“嵌套方法”可能不是正确的术语,但这就是操作员所描述的。 - Nader Shirazie
(而且,早些时候,“the op”指的是“你” :)) - Nader Shirazie
嵌套函数是指嵌套的函数定义,而不是将一个函数传递给另一个函数。 - nima
显示剩余2条评论

5
嵌套方法还允许您控制调用方的范围,同时允许您访问在内部创建的成员而不将其暴露给类似作用域中的其他函数(即在类内部)。

5

嵌套方法可以增加您的代码局部性,这是一件好事,因为您不必在类中添加仅在一个地方使用的小型私有方法。


3
尝试以下Python风格的伪代码示例,了解此技术的一个用例:
def MyMethod(base):
    base = expensivePreparation(base)
    def MyInnerMethod(some_modifier):
        doSomethingCoolWith(base, some_modifier)

    return MyInnerMethod

process = MyMethod(some_obj)
process(arg1)
process(arg2)

1
沿着上述要点,这些可以被视为高阶函数,即自己接受或返回函数的函数。
看看这个例子:
     static void Main(string[] args)
    {

        Func<int, int> NestedMethod = delegate(int x)     
        {             Console.WriteLine("In nested method. Value of x is {0}.", x);        
            return x;         
        };

        HigerOrderTest(NestedMethod)();
    }

     private static Action HigerOrderTest(Func<int, int> highFunction)
    {
          var sam = highFunction(3);

          Action DoIt =() =>
          {
              Console.WriteLine("Output is {0}.", sam);    
          };
             return DoIt;
    }

尽管这是有效的,但很奇怪,但很酷。+1 - Kredns

1

这里已经有很多有用的答案了,但我还是要说一下。

  • 内部类:不行!虽然尝试得很好,但太麻烦了。嵌套函数/过程更快更简单。Java中只有一个方法的类真的很糟糕,而嵌套函数(或过程)可以避免这种语法上的荒谬。
  • 函数分解:将长程序分解成较小的程序,这些程序对其他地方没有用处。Pascal在很久以前就支持了这个功能,我相信即使在那时候(70年代初),这也不是一个新发明。
  • 闭包:在其他地方提到过,但很有用。使用封闭函数中的局部变量来初始化生成的函数,并返回自定义函数(或以其他方式使用生成的代码)。

1: http://steve-yegge.blogspot.com/2006/03/execution-in-kingdom-of-nouns.html "King Java"

2: http://en.wikipedia.org/wiki/Function_object#In_Java "Function object / functor"


0

你可能已经意识到将函数传递给其他函数的有用性,例如为sort()例程提供比较函数以及map()filter()等明显的示例。同样,从另一个函数中接收函数作为结果通常也很有用。最常见的用法是closures,但currying也很常见。

闭包的一个核心优点是能够在运行时创建一个新函数,该函数包含执行所需的所有临时信息。在不支持闭包的语言实现中,这种状态管理通常通过类完成,即设置对象实例变量,然后在需要时读取。闭包提供了一种将状态与操作捆绑在一起的方法。

因此,作为基本的理解要点,将嵌套函数视为编写即时函数的一种方式可能会有所帮助,以执行非常特定的操作,其中这些操作的详细信息将在运行时知道,但您只想在稍后执行该操作。

嵌套函数还有其他的用途,但你必须从某个地方开始。


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