【问题标题】:Can an anonymous method in C# call itself?C# 中的匿名方法可以调用自身吗?
【发布时间】:2010-11-15 13:46:33
【问题描述】:

我有以下代码:

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' 在访问之前可能未初始化。

标签: c# recursion anonymous-methods


【解决方案1】:

如果你使用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);
  }
}

【讨论】:

    【解决方案2】:

    Anonymous Recursion in C# 对此话题进行了精彩的讨论。

    递归很漂亮,而 lambdas 很漂亮 终极抽象。但是怎么可能 它们可以一起使用吗? Lambda 是 匿名函数和递归 需要名字...

    既然这又弹出来了,下面是一个使用 Y-combinator 的例子:

    // 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-combinator 并仅使用委托设置递归,则不会获得正确的递归。比如……

    // 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 =&gt; x + 1; 行之后,您实际拥有的代表是这个...

    badRecCopy = x => ( x <= 1 ) ? 1 : x * ( (x+1)-1 );
    

    因此,badRec 将值增加 1,这是我们预期的 (4+1=5),但 badRecCopy 现在实际上返回的是值 (5*( (5+1)-1 ) 的平方,这是我们几乎肯定没有预料到的。

    如果您使用 Y-combinator,它将按预期工作......

    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-combinator(PDF 链接)的更多信息。

    【讨论】:

    • Y-combinator 赢了! :-)
    • 我可能读错了 - 但是你的例子是阶乘而不是 exp?我的数学很差,所以我不太确定,如果你能澄清一下就好了
    【解决方案3】:

    如果您要使用递归匿名方法,您可能希望将其提升为您的类中的普通私有方法。

    【讨论】:

      【解决方案4】:

      你可以把它分解成两条语句,利用捕获变量的魔力来达到递归的效果:

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

      【讨论】:

      • True ;) 但是生成的函数不是匿名的 - 在您的方法中,递归技巧 still 只是可以通过名称绑定来实现。
      【解决方案5】:

      您不能在build 本身内部调用build,因为匿名方法的主体是变量本身的初始化。您正在尝试在定义变量之前使用它。

      不是我推荐这个(因为在这里创建一个真正的递归方法会非常更简单),但如果你有兴趣,你可以阅读Anonymous Recursion in C#

      递归很漂亮,而 lambdas 很漂亮 终极抽象。但是怎么可能 它们可以一起使用吗? Lambda 是 匿名函数和递归 需要名字。

      【讨论】:

      • +1 很好地描述了错误存在的原因。解决方法很容易(请参阅 Mehrdad 的回答),但我认为这首先不是一个好主意。
      【解决方案6】:

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

      匿名方法是匿名的——你不应该通过名字来调用它们(非匿名)。

      【讨论】:

      • This/self 仍然足够匿名。
      • 递归函数比上面的命名委托更容易阅读。还需要定义委托本身,这也是该解决方案的减号和该解决方案的加号。
      猜你喜欢
      • 2020-07-05
      • 1970-01-01
      • 2011-05-05
      • 1970-01-01
      • 1970-01-01
      • 2011-06-26
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      相关资源
      最近更新 更多