web-dev-qa-db-fra.com

Comment créer des objets immuables en C #?

Dans une question sur Les meilleures pratiques pour la validation du modèle C # , la réponse la plus votée dit:

J'ai tendance à effectuer toute ma validation dans le constructeur. C'est indispensable car je crée presque toujours des objets immuables.

Comment créer exactement un objet immuable en C #? Utilisez-vous simplement le mot clé readonly?

Comment cela fonctionnerait-il si vous voulez valider le constructeur de votre classe de modèle généré par Entity Framework?

Est-ce que cela ressemble à ci-dessous?

public partial readonly Person
{
    public Person()
}
28
delete

La question intéressante ici est votre question à partir des commentaires:

Quel type d'objet auriez-vous pour lequel vous n'avez pas besoin de modifier les valeurs à un moment donné? Je suppose que ce n'est pas une classe modèle, n'est-ce pas? J'ai dû changer le nom d'une personne dans ma base de données - cela ne cadrerait pas avec cette idée.

Eh bien, considérons les choses qui sont déjà immuables. Les chiffres sont immuables. Une fois que vous avez le numéro 12, il est 12. Vous ne pouvez pas le changer. Si vous avez une variable qui contient 12, vous pouvez changer le contenu de la variable en 13, mais vous changez la variable , pas le nombre 12

Même avec des cordes. "abc" est "abc", et cela ne change jamais. Si vous avez une variable qui contient "abc", vous pouvez la remplacer par "abcd", mais cela ne change pas "abc", cela change la variable.

Qu'en est-il d'une liste? {12, "abc"} est la liste qui est suivie de "abc" et cette liste ne change jamais. La liste {12, "abcd"} est une liste différente

Et c'est là que les choses se passent hors des rails. Parce qu'en C #, vous pouvez le faire de toute façon. Vous pouvez dire qu'il y a identité référentielle entre ces deux listes si les listes sont autorisées à modifier leur contenu sans changer leur identité. 

Vous appuyez sur le clou quand vous parlez de "modèle". Modélisez-vous quelque chose qui change? Si tel est le cas, il est alors sage de le modéliser avec un type qui change. L'avantage de cela est que les caractéristiques du modèle correspondent au système modélisé. L'inconvénient est qu'il devient très difficile de faire quelque chose comme une fonctionnalité de "restauration", dans laquelle vous "annulez" un changement. 

Autrement dit, si vous mutez {12, "abc"} en {12, "abcd"} et que vous souhaitez ensuite annuler la mutation, comment procédez-vous? Si la liste est immuable, conservez les deux valeurs et choisissez celle que vous voulez définir comme "valeur actuelle". Si la liste est modifiable, la logique d'annulation doit conserver une "fonction d'annulation" qui sait comment annuler la mutation.

En ce qui concerne votre exemple spécifique, vous pouvez certainement créer une base de données immuable. Comment changez-vous le nom d'une personne dans votre base de données immuable? Vous pas. Vous créez une base de données new qui contient les données souhaitées. Le truc avec les types immuables est de le faire efficacement, sans copier des milliards d'octets. La conception de structures de données immuables nécessite de trouver des moyens astucieux de partager des états entre deux structures presque identiques.

67
Eric Lippert

Déclarer tous les champs en lecture seule est un bon pas vers la création d'un objet immuable, mais cela ne suffit pas. En effet, un champ en lecture seule peut toujours être une référence à un objet mutable.

En C #, l’immuabilité n’est pas imposée par le compilateur. Il faut juste faire attention.

10
Mark Byers

Cette question a deux aspects:

  1. Type immuable lorsque vous instanciez un objet
  2. Type immuable lorsque EF instancie un objet

Le premier aspect exige une structure comme celle-ci:

public class MyClass
{
  private readonly string _myString;
  public string MyString
  {
    get
    {
      return _myString;
    }
  }

  public MyClass(string myString)
  {
    // do some validation here

    _myString = myString;
  }
}

Maintenant le problème - EF. EF nécessite un constructeur sans paramètre et EF doit avoir des setters sur les propriétés. J'ai demandé très similaire question ici .

Votre type doit ressembler à:

public class MyClass
{
  private string _myString;
  public string MyString
  {
    get
    {
      return _myString;
    }
    private set
    {
      _myString = value;
    }
  }

  public MyClass(string myString)
  {
    // do some validation here

    _myString = myString;
  }

  // Not sure if you can change accessibility of constructor - I can try it later
  public MyClass()
  {}
}

Vous devez également informer EF de la définition privée de la propriété MyString - celle-ci est configurée dans les propriétés d’enitity du fichier EDMX. Il est évident qu’il n’y aura pas de validation lorsque EF matérialisera des objets à partir de DB. En outre, vous ne pourrez pas utiliser de méthodes comme ObjectContext.CreateObject (vous ne pourrez pas remplir l'objet).

Le modèle Entity Object T4 et la génération de code par défaut créent la méthode Create Factory CreateMyClass à la place du constructeur avec des paramètres. Le modèle POCO T4 ne génère pas la méthode d'usine.

Je n'ai pas d'abord essayé avec EF Code.

6
Ladislav Mrnka
5
Alexei Levenkov

Un objet de valeur immuable est un objet de valeur qui ne peut pas être modifié. Vous ne pouvez pas modifier son état, vous devez en créer de nouveaux

Découvrez le blog d'Eric Lippert:

Types d'immuabilité http://blogs.msdn.com/b/ericlippert/archive/2007/11/13/immutability-in-c-part-one-kinds-of-immutability.aspx

Jettes un coup d'oeil à 

Modèle d'objet immuable en C # - qu'en pensez-vous?

2
ukhardy

Comment cela fonctionnerait-il si vous voulez valider le constructeur de votre classe de modèle généré par Entity Framework?

Cela ne fonctionnerait pas dans ce contexte car EF requiert que les propriétés de la classe d'entité soient publiques, sinon il ne peut pas l'instancier.

Mais vous pouvez utiliser des objets immuables plus loin dans votre code.

1
user151323