53 votes

Une méthode anonyme en C# peut-elle s'appeler elle-même ?

J'ai le code suivant :

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;

                };
   ......
   }
}

Existe-t-il un autre moyen de configurer une méthode anonyme en C# de sorte qu'elle puisse s'appeler elle-même ?

92voto

Mehrdad Afshari Points 204872

Vous pouvez la décomposer en deux déclarations et utiliser la magie des variables capturées pour obtenir l'effet de récurrence :

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

30voto

Reed Copsey Points 315315

Si vous créez une fonction récursive, je vous recommande d'éviter les délégués anonymes. Créez simplement une méthode et faites-la s'appeler elle-même de manière récursive.

Les méthodes anonymes sont censées être anonymes - vous ne devriez pas les appeler par leur nom (de manière non anonyme).

24voto

JP Alioto Points 33482

Récursion anonyme en C# propose une excellente discussion sur ce sujet.

La récursion est belle et les lambdas sont l'abstraction ultime. Mais comment les utiliser ensemble ? Les lambdas sont fonctions anonymes et la récursion nécessite des noms...

Puisque le sujet est revenu sur le tapis, voici un exemple d'utilisation du 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;
}

Voici une utilisation pour appeler une fonction anonyme et récursive ...

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

Vous noterez que si vous n'utilisez pas le combinateur en Y et que vous configurez la récursion avec seulement le délégué, vous n'obtenez pas une récursion correcte. Par exemple ...

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

Mais tout fonctionne bien...

Console.WriteLine( badRec(5) );

// Output
// 120

Mais essayez ceci...

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

Quoi ? !?

Vous voyez, après la ligne badRec = x => x + 1; le délégué que vous avez en réalité est celui-ci ...

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

Donc, badRec incrémente la valeur de 1, ce que nous attendons. (4+1=5) mais badRecCopy renvoie en fait le carré de la valeur (5*( (5+1)-1 ) ce à quoi nous ne nous attendions certainement pas.

Si vous utilisez le Y-combinator, cela fonctionnera comme prévu...

Func<int,int> goodRec = Y<int,int>( exp => x => ( x <=1 ) ? 1 : x * exp( x - 1 ) );
Func<int,int> goodRecCopy = goodRec;

Et vous obtenez ce que vous attendez.

goodRec = x => x + 1;

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

// Output
// 5
// 120

Vous pouvez en savoir plus sur le Y-combinator (Lien PDF).

10voto

Andrew Hare Points 159332

Vous ne pouvez pas appeler build à l'intérieur de build puisque le corps de la méthode anonyme est l'initialisation de la variable elle-même. Vous essayez d'utiliser une variable avant qu'elle ne soit définie.

Non pas que je recommande cela (car il serait beaucoup plus simple de créer une vraie méthode récursive ici) mais si vous êtes intéressé, vous pouvez lire Récursion anonyme en C# :

La récursion est belle et les lambdas sont l'abstraction ultime. Mais comment les utiliser ensemble ? Les lambdas sont fonctions anonymes et la récursion nécessite des noms.

3voto

Jordão Points 29221

Si vous utilisez Y votre fonction devient un paramètre de la fonction elle-même, de sorte que vous pouvez l'appeler de manière récursive :

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

Prograide.com

Prograide est une communauté de développeurs qui cherche à élargir la connaissance de la programmation au-delà de l'anglais.
Pour cela nous avons les plus grands doutes résolus en français et vous pouvez aussi poser vos propres questions ou résoudre celles des autres.

Powered by:

X