web-dev-qa-db-fra.com

Accéder à un membre non statique via Lazy <T> ou une expression lambda

J'ai ce code:

public class MyClass
{
    public int X { get; set; }
    public int Y { get; set; }

    private Lazy<int> lazyGetSum = new Lazy<int>(new Func<int>(() => X + Y));
    public int Sum{ get { return lazyGetSum.Value; } }

}

Me donne cette erreur:

Un initialiseur de champ ne peut pas référencer le champ non statique, la méthode ou la propriété .

Je pense qu'il est très raisonnable d'accéder à un membre non statique via paresseux, comment faire cela?

* EDIT *

La réponse acceptée résout parfaitement le problème, mais vous pouvez lire la réponse de Joh Skeet .

44
Sawan

Vous pouvez le déplacer dans le constructeur:

private Lazy<int> lazyGetSum;
public MyClass()
{
   lazyGetSum = new Lazy<int>(new Func<int>(() => X + Y));
}

Voir la réponse @JohnSkeet ci-dessous pour plus de détails sur la raison du problème. Accès à un membre non statique via Lazy <T> ou toute expression lambda

72
JleruOHeP

Voici une version simplifiée de votre problème:

class Foo
{
    int x = 10;
    int y = this.x;
}

Et un légèrement moins simplifié:

class Foo
{
    int x = 10;
    Func<int> y = () => this.x;
}

(La this est généralement implicite, mais je l'ai explicite ici par souci de clarté.)

Dans le premier cas, l'utilisation de this est très évidente.

Dans le second cas, il s'agit de légèrement moins évident, car différé en raison de l'expression lambda. Ce n'est toujours pas autorisé cependant ... parce que le compilateur essaierait de créer un délégué utilisant this comme cible, comme ceci:

class Foo
{
    int x = 10;
    Func<int> y = this.GeneratedMethod;

    private int GeneratedMethod()
    {
        return x;
    }
}

Ceci est interdit par la section 10.5.5.2 de la spécification C # 5:

Un initialiseur de variable pour un champ d'instance ne peut pas faire référence à l'instance en cours de création.

Le correctif le plus simple consiste simplement à placer l'initialisation dans le corps du constructeur, où vous êtes capable de faire référence à this. Donc dans votre code:

public class MyClass
{
    public int X { get; set; }
    public int Y { get; set; }

    private Lazy<int> lazyGetSum;

    public MyClass()
    {
        lazyGetSum = new Lazy<int>(() => X + Y);
    }

    public int Sum{ get { return lazyGetSum.Value; } }
}

Notez que j'ai également simplifié l'expression lambda - il est très rarement utile d'utiliser new Func<int>(...).

56
Jon Skeet

L'erreur vous dit exactement ce qui ne va pas. Vous ne pouvez pas accéder à une propriété dans un initialiseur de champ. 

Supposons que votre classe ressemble à:

public class MyClass
{
    public int X { get; set; }
    public int Y { get; set; }

    private Lazy<int> lazyGetSum = new Lazy<int>(new Func<int>(() => 2 + 3));
    public int Sum { get { return lazyGetSum.Value; } }

}

Ensuite, il compilerait sans aucun problème. Étant donné que dans votre code, vous accédez aux propriétés X et Y lors de l'initialisation du champ. Vous obtenez l'erreur. 

Vous pouvez également les initialiser dans le constructeur si vous voulez:

public class MyClass
{
    public int X { get; set; }
    public int Y { get; set; }

    private Lazy<int> lazyGetSum; 
    public int Sum { get { return lazyGetSum.Value; } }

    public MyClass()
    {
        lazyGetSum = new Lazy<int>(new Func<int>(() => X + Y));
    }

}
3
Habib

Je pense que vous devez définir static vos champs pour l’utiliser comme ceci;

public  class MyClass
    {
        public static int X { get; set; }
        public static int Y { get; set; }

        private Lazy<int> lazyGetSum = new Lazy<int>(new Func<int>(() => X + Y));
        public int Sum { get { return lazyGetSum.Value; } }
    }

Et bien sûr, vous devez initialiser vos champs. Vous ne pouvez pas le faire d'une autre manière.

EDIT: Ou vous pouvez définir avec votre constructeur sans aucune définition de static.

public MyClass()
    {
        lazyGetSum = new Lazy<int>(new Func<int>(() => X + Y));
    }

avec

private Lazy<int> lazyGetSum; 
public int Sum { get { return lazyGetSum.Value; } }
0
Soner Gönül

Si vous souhaitez utiliser la méthode non statique. Vous pouvez également utiliser une alternative build pour Lazy self qui obtient le paramètre Func dans la valeur .Value et non dans le constructeur comme Lazy. 

Votre code ressemblerait à ceci:

public class MyClass
{
    public int X { get; set; }
    public int Y { get; set; }

    private readonly LazyValue<int> lazyGetSum = new LazyValue<int>();
    public int Sum { get { return lazyGetSum.GetValue(() => X + Y); } }
}

Il implémente la propriété dans la propriété. Où vous pouvez vous attendre,

0
Alex Siepman

J'ai trouvé cette question lorsque j'avais un problème similaire et que je voulais trouver un bon modèle à utiliser.

Le problème de "déplacer l'initialisation vers le constructeur" suggéré dans d'autres réponses est que le lambda pour la fonction d'initialisation apparaît maintenant dans le constructeur (et qu'un constructeur explicite est maintenant requis dans une classe qui n'en avait pas besoin auparavant).

Très bien pour cet exemple de jouet, mais dans une classe plus complexe comportant de nombreuses propriétés, il est utile d’avoir toute la logique relative à la propriété au même endroit.

La réponse de Alex Siepman suggère une alternative intéressante, mais le site tiers hébergeant la classe LazyValue<> semble être "Service indisponible" pour le moment, et de toute façon je ne cherchais pas une solution tierce, mais simplement un modèle efficace à utiliser avec classe normale Lazy<>. Je crains également que, dans certains cas d'utilisation, la création de l'instance de délégué et la fermeture associée à chaque accès à la propriété ne soient pas triviales.

Avoir une ligne dans le constructeur semble inévitable, à cause des problèmes mis en évidence dans d'autres réponses, mais il me semble préférable d'éviter de placer la logique de propriété dans le constructeur lui-même, car elle sera jusqu'à présent séparée de la propriété dans les diffs, etc.

Les deux modèles suivants semblent fonctionner, mais je ne suis pas sûr s'il y a une alternative encore meilleure que j'ai manquée.

Modèle 1: ne vous embêtez pas avec un lambda - déclarez une fonction réelle à côté de la propriété et enveloppez-la dans un délégué implicite:

public class MyClass
{
  public MyClass()
  {
    lazyGetSum = new Lazy<int>(GetSum);
  }

  public int X { get; set; }
  public int Y { get; set; }

  private Lazy<int> lazyGetSum;
  public int Sum { get { return lazyGetSum.Value; } }
  private int GetSum() { return X + Y; }
}

Modèle 2: Déclarez une fonction de propriété init et appelez-la depuis le constructeur.

public class MyClass
{
  public MyClass()
  {
    LazyGetSumInit();
  }

  public int X { get; set; }
  public int Y { get; set; }

  private Lazy<int> lazyGetSum;
  public int Sum { get { return lazyGetSum.Value; } }
  private void LazyGetSumInit() { lazyGetSum = new Lazy<int>(new Func<int>(() => X + Y)); }
}

En voyant les deux côte à côte, je pense que je préfère le second, à l'exception du nom apparemment maladroit de la fonction.

[Dans ma vraie implémentation, j'avais un nom similaire à InitSum, de sorte qu'il s'agisse d'un détail d'implémentation de la propriété qui est "paresseux" et qu'il peut en principe être changé entre une implémentation paresseuse et non paresseuse sans changer le code constructeur.]

0
Steve