C#中的匿名方法能否调用自身?

62

我有以下代码:

class myClass
{
private delegate string myDelegate(Object bj);

protected void method()
   {
   myDelegate build = delegate(Object bj)
                {
                    var letters= string.Empty;
                    if (someCondition)
                        return build(some_obj); //This line seems to choke the compiler
                    else string.Empty;

                };
   ......
   }
}

有没有其他方法可以在C#中设置匿名方法,使其可以调用自身?


VS2008的确切投诉是:访问之前可能未初始化本地变量“build”。 - Matt
6个回答

101

你可以将其分解为两个语句,并利用捕获变量的神奇之处来实现递归效果:

myDelegate build = null;
build = delegate(Object bj)
        {
           var letters= string.Empty;
           if (someCondition)
               return build(some_obj);                            
           else string.Empty;
        };

14
没问题 ;) 不过需要指出的是,你的方法所得到的函数是非匿名的——在这种方式下,递归技巧仍然必须通过名称绑定来实现。 - Dario

32

如果你正在创建递归函数,我建议避免使用匿名委托。只需创建一个方法并让它递归调用自己。

匿名方法是匿名的 - 你不应该非匿名地调用它们。


这个/自己仍然足够匿名。 - Lodewijk
递归函数比上面命名的委托更易于阅读。此外,委托本身也需要定义,这对于该解决方案是一个优点,而对于另一种解决方案则是一个缺点。 - TarmoPikaro

25

C#中的匿名递归对这个主题进行了精彩的讨论。

递归很美妙,而lambda是最终的抽象。但它们如何结合使用?Lambda是匿名函数,递归需要名称...

既然这又出现了,这里有一个使用Y组合器的示例:

// This is the combinator
public static Func<A,R> Y<A,R>( Func<Func<A,R>, Func<A,R>> f )
{
    Func<A,R> g = null;
    g = f( a => g(a) );
    return g;
}

以下是使用它调用匿名递归函数的示例...

Func<int,int> exp = Y<int,int>( e => x => ( x <=1 ) ? 1 : x * e( x - 1 ) );
Console.WriteLine( exp(5) );

请注意,如果您不使用Y组合子并仅使用委托设置递归,则无法获得正确的递归。例如...

// This is BAD. Do not do this!
Func<int,int> badRec = null;
badRec = x => ( x <= 1 ) ? 1 : x * badRec( x - 1 );

但是一切都很顺利...

Console.WriteLine( badRec(5) );

// Output
// 120

但是试试这个...

Func<int,int> badRec = null;
badRec = x => ( x <= 1 ) ? 1 : x * badRec( x - 1 );

Func<int,int> badRecCopy = badRec;

badRec = x => x + 1;

Console.WriteLine( badRec(4) );
Console.WriteLine( badRecCopy(5) );

// Output
// 5
// 25

什么?!?

你看,经过这行代码 badRec = x => x + 1;,实际上你拥有的委托是这个...

badRecCopy = x => ( x <= 1 ) ? 1 : x * ( (x+1)-1 );
所以,badRec 将值增加 1,我们期望的结果是 (4+1=5),但 badRecCopy 现在实际返回值的平方 (5*((5+1)-1)),这几乎肯定不是我们期望的结果。如果使用 Y 组合子,它将按预期工作...
Func<int,int> goodRec = Y<int,int>( exp => x => ( x <=1 ) ? 1 : x * exp( x - 1 ) );
Func<int,int> goodRecCopy = goodRec;

你会得到你所期望的结果。

goodRec = x => x + 1;

Console.WriteLine( goodRec(4) );
Console.WriteLine( goodRecCopy(5) );

// Output
// 5
// 120

你可以阅读有关Y组合子的更多信息(PDF链接)。


4
Y-组合子大胜利! :-) - Jeffrey Hantin
我可能读错了,但是你的例子是阶乘而不是指数吗?我数学很差,所以不太确定,如果您能澄清一下就好了。 - Noobnewbier

10

由于匿名方法体本身是变量的初始化,因此您不能在 build 方法内部调用 build 方法。您试图在定义之前使用变量。

我并不建议这样做(因为创建一个真正递归的实际方法会更简单),但如果您感兴趣,可以阅读C#中的匿名递归

递归很美丽,而Lambda是终极抽象。但它们如何结合使用呢?Lambda是匿名函数,而递归需要名称。


+1 是因为对错误存在的良好描述。虽然可以很容易地解决(请参见Mehrdad的答案),但我认为一开始就不是一个好主意。 - Reed Copsey

3
如果您使用Y,则您的函数将成为函数本身的参数,以便您可以进行递归调用。
class myClass {
  private delegate string myDelegate(Object bj);
  protected void method() {
    myDelegate build = delegate(Object obj) {
      // f is the function itself, which is passed into the function
      return Functional.Y<Object, string>(f => bj => { 
        var letters = string.Empty;
        if (someCondition)
          return f(some_obj); // use f
        else return string.Empty;

      })(obj);
    };
  }
}

public static class Functional {
  public delegate Func<A, R> Recursive<A, R>(Recursive<A, R> r);
  public static Func<A, R> Y<A, R>(Func<Func<A, R>, Func<A, R>> f) {
    Recursive<A, R> rec = r => a => f(r(r))(a);
    return rec(rec);
  }
}

1

如果你已经到了递归匿名方法的地步,那么你可能想将其提升为类中的普通私有方法。


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