Question

Je lis souvent que s doit être struct immuable - sont-ils pas, par définition,

?

Considérez-vous être immuable int?

int i = 0;
i = i + 123;

Semble bien - nous obtenons une nouvelle et lui attribuer i Retour à Point. Qu'en est-ce?

i++;

D'accord, nous pouvons penser comme un raccourci.

i = i + 1;

Qu'en est-il du (1, 2) Point.Offset()?

Point p = new Point(1, 2);
p.Offset(3, 4);

Est-ce vraiment muter le point ref? Ne devrions-nous penser comme un raccourci pour ce qui suit avec le retour d'un nouveau p = p.Offset(3, 4); points?

p = p.Offset(3, 4);

Le fond de cette pensée est la suivante - comment un type de valeur sans identité est mutable? Vous devez regarder au moins deux fois pour déterminer si elle a changé. Mais comment pouvez-vous faire sans identité?

Je ne veux pas compliquer le raisonnement à ce sujet en tenant compte de paramètres et la boxe p.Offset(3, 4);. Je suis également conscient que immuabilité exprime bien Foo mieux que <=> fait. Mais la question reste - sont la valeur non types immuables, par définition,

UPDATE

Je pense qu'il ya au moins deux concepts impliqués -. Mutabilité d'une variable ou d'un champ et la mutabilité de la valeur d'une variable

public class Foo
{
    private Point point;
    private readonly Point readOnlyPoint;

    public Foo()
    {
        this.point = new Point(1, 2);
        this.readOnlyPoint = new Point(1, 2);
    }

    public void Bar()
    {
        this.point = new Point(1, 2);
        this.readOnlyPoint = new Point(1, 2); // Does not compile.

        this.point.Offset(3, 4); // Is now (4, 6).
        this.readOnlyPoint.Offset(3, 4); // Is still (1, 2).
    }
}

Dans l'exemple que nous avons des champs - un mutable et un immuable. Du fait un champ de type de valeur contient la valeur totale, un type de valeur stockée dans un champ immuable doit être trop immuable,. Je suis encore assez surpris par le résultat -. Je ne l'ai pas EXSPECT le champ readonly restent inchangées

Les variables (en plus constantes) sont AllWays mutable, par conséquent, ils impliquent aucune restriction quant à la mutabilité des types de valeur.


La réponse ne semble pas être que l'avant donc je vais reformuler ma question directement.

Compte tenu de ce qui suit.

public struct Foo
{
    public void DoStuff(whatEverArgumentsYouLike)
    {
        // Do what ever you like to do.
    }

    // Put in everything you like - fields, constants, methods, properties ...
}

Pouvez-vous donner une version complète de et un exemple <=> d'utilisation - qui peut inclure les paramètres et la boxe <=> - de sorte qu'il est impossible de réécrire toutes les occurences de

foo.DoStuff(whatEverArgumentsYouLike);

avec

foo = foo.DoStuff(whatEverArgumentsYouLike);
Était-ce utile?

La solution

  

Un objet est immuable si son état   ne change pas une fois l'objet   été créé.

Réponse courte: Non, les types de valeur ne sont pas immuables par définition. Les deux structs et les classes peuvent être soit mutable ou immuable. Les quatre combinaisons sont possibles. Si une structure ou une classe possède des champs publics non readOnly propriétés publiques avec les organismes ou les méthodes qui définissent les champs privés, il est mutable parce que vous pouvez changer l'état sans créer une nouvelle instance de ce type.


Réponse longue: Tout d'abord, la question de l'immuabilité applique uniquement aux structs ou des classes avec des champs ou des propriétés. Les types les plus élémentaires (nombres, des chaînes et nuls) sont par nature immuable, car il n'y a rien (champ / propriété) pour changer leur sujet. A 5 est un 5 est un 5. Toute opération sur le 5 retourne seulement une autre valeur immuable.

Vous pouvez créer struct mutables tels que System.Drawing.Point. Les deux ont X et setters qui Y modifient les champs de la struct:

Point p = new Point(0, 0);
p.X = 5;
// we modify the struct through property setter X
// still the same Point instance, but its state has changed
// it's property X is now 5

Certaines personnes semblent confondre immutablity avec le fait que les types de valeur sont passés par valeur (d'où leur nom) et non par référence.

void Main()
{
    Point p1 = new Point(0, 0);
    SetX(p1, 5);
    Console.WriteLine(p1.ToString());
}

void SetX(Point p2, int value)
{
    p2.X = value;
}

Dans ce cas Console.WriteLine() écrit "{X=0,Y=0}". Ici, n'a pas été modifiée p1 parce que modifié SetX() qui est une p2 Copier de (5, 0). Cela se produit parce que est un Offset(3,4) type de valeur , non pas parce qu'il est immuable (ce n'est pas).

Pourquoi devrait types de valeurs immuables? Beaucoup de raisons ... Voir cette question. La plupart du temps c'est parce que les types de valeur mutables conduisent à toutes sortes de bugs pas si évidentes. Dans l'exemple ci-dessus le programmeur aurait pu être prévu Point après avoir appelé readOnlyPoint Offset(). Ou encore, imaginez le tri d'une valeur qui peut changer plus tard. Ensuite, votre collection triée ne sera plus trié comme prévu. La même chose vaut pour les dictionnaires et hash. Fabulous Eric Lippert ( Blog ) a écrit un et pourquoi il croit qu'il est l'avenir de C #. est ici l'un de ses exemples qui permet aux vous « modifier » une lecture seule variable.


MISE À JOUR: votre exemple avec:

this.readOnlyPoint.Offset(3, 4); // Is still (1, 2).

est exactement la Lippert ce que mentionné dans son article sur la modification des variables de lecture. Effectivement modifié un foo otherFoo, mais il a été Copier de DoStuff(), et il n'a jamais été attribué à quoi que ce soit, il est donc perdu.

que est pourquoi les types de valeur mutables sont mal: Ils vous permettent de penser vous modifiez quelque chose, quand parfois vous modifiez en fait une copie, ce qui conduit à des bogues inattendus . Si immuable était IFoo, faudrait foo1 retourner une nouvelle foo2, et vous n'auriez pas pu l'attribuer à foo3. Et puis vous allez « Oh droite, il est en lecture seule pour une raison. Pourquoi ai-je essayer de le changer? Une bonne chose que le compilateur m'a arrêté maintenant. »


Mise à jour: À propos de votre demande reformulée ... Je pense que je sais ce que vous voulez en venir. D'une certaine façon, vous pouvez « penser » de struct comme interne immuable, que la modification d'une struct est la même que le remplaçant par une copie modifiée. Il pourrait même être ce que le CLR fait en interne en mémoire, pour tout ce que je sais. (Voilà comment la mémoire flash fonctionne. Vous ne pouvez pas éditer seulement quelques octets, vous devez lire un bloc entier de kilo-octets en mémoire, modifier quelques-uns que vous voulez, et écrire tout le bloc arrière.) Cependant, même si elles étaient « en interne immuable », qui est un détail de mise en œuvre et pour nous les développeurs que les utilisateurs de struct (leur interface ou API, si vous voulez), ils peut être changé. Nous ne pouvons pas ignorer ce fait et « penser à eux comme immuable ».

Dans un comment vous avez dit « vous ne pouvez pas avoir une référence à la valeur du champ ou variable ». Vous présumez que chaque variable struct a une autre copie, de sorte que la modification d'une copie ne touche pas les autres. Ceci n'est pas entièrement vrai. Les lignes marquées ci-dessous ne sont pas remplaçables si ...

interface IFoo { DoStuff(); }
struct Foo : IFoo { /* ... */ }

IFoo otherFoo = new Foo();
IFoo foo = otherFoo;
foo.DoStuff(whatEverArgumentsYouLike); // line #1
foo = foo.DoStuff(whatEverArgumentsYouLike); // line #2

Les lignes 1 et 2 n'ont pas les mêmes résultats ... Pourquoi? Parce que et <=> se référer à la <=> même instance boxed de Foo. Tout ce qui est changé en ligne dans # <=> 1 reflète dans <=>. Ligne # 2 remplace avec une nouvelle <=> valeur et ne fait rien à <=> (en supposant que <=> retourne une nouvelle instance et ne se <=> <=> modifie pas).

Foo foo1 = new Foo(); // creates first instance
Foo foo2 = foo1; // create a copy (2nd instance)
IFoo foo3 = foo2; // no copy here! foo2 and foo3 refer to same instance

La modification n'affectera pas <=> ou <=> <=>. La modification reflètera dans <=> <=>, mais pas dans <=>. La modification reflète dans <=> mais pas dans <=> <=>.

Confondre? Stick à des types de valeurs immuables et vous éliminez l'envie de modifier l'un d'eux.


UPDATE: typo fixe dans le premier exemple de code

Autres conseils

types de mutabilité et la valeur sont deux choses distinctes.

Définir un type comme type de valeur, indique que le temps d'exécution copiera les valeurs au lieu d'une référence à l'exécution. Mutabilité, d'autre part, dépend de la mise en œuvre, et chaque classe peut la mettre en œuvre comme il veut.

Vous pouvez écrire struct qui sont mutables, mais il est préférable d'effectuer les types de valeurs immuables.

Par exemple DateTime crée toujours de nouvelles instances lorsque vous effectuez une opération. Point est mutable et peut être modifié.

Pour répondre à votre question: Non, ils ne sont pas immuables par définition, cela dépend du cas si elles doivent être mutable ou non. Par exemple, si elles doivent servir de clés de dictionnaire, ils devraient être immuables.

Si vous prenez votre logique assez loin, puis tous types sont immuables. Lorsque vous modifiez un type de référence, on pourrait dire que vous écrivez vraiment un nouvel objet à la même adresse, plutôt que de modifier quoi que ce soit.

Ou vous pourriez dire que tout est mutable, dans toutes les langues, parce que parfois la mémoire qui avait déjà été utilisé pour une chose, sera remplacé par un autre.

vous pouvez obtenir à une conclusion que vous aimez avec assez d'abstraction, et sans tenir compte des caractéristiques linguistiques assez,.

Et ce manque le point. Selon les spécifications .NET, les types de valeur sont mutables. Vous pouvez le modifier.

int i = 0;
Console.WriteLine(i); // will print 0, so here, i is 0
++i;
Console.WriteLine(i); // will print 1, so here, i is 1

mais il est toujours le même i. La seule variable est i a déclaré une fois. Tout ce qui arrive après cette déclaration est une modification.

Dans quelque chose comme un langage fonctionnel avec des variables immuables, ce ne serait pas légal. Le ++ i ne serait pas possible. Une fois qu'une variable a été déclarée, il a une valeur fixe.

Dans .NET, ce n'est pas le cas, il n'y a rien pour me empêcher de modifier le set après qu'il a été déclaré.

Après avoir réfléchi à ce sujet un peu plus, voici un autre exemple qui pourrait être mieux:

struct S {
  public S(int i) { this.i = i == 43 ? 0 : i; }
  private int i;
  public void set(int i) { 
    Console.WriteLine("Hello World");
    this.i = i;
  }
}

void Foo {
  var s = new S(42); // Create an instance of S, internally storing the value 42
  s.set(43); // What happens here?
}

Sur la dernière ligne, selon votre logique, nous pourrions dire que nous construisons en fait un nouvel objet, et écrasez l'ancienne avec cette valeur. Mais ce n'est pas possible! Pour construire un nouvel objet, le compilateur doit définir la variable à 42. Mais s.i il est privé! Il est accessible par un constructeur défini par l'utilisateur, qui interdit explicitement la valeur 43 (mise à 0 à la place), puis par notre méthode set(), qui a un mauvais effet secondaire. Le compilateur n'a aucun moyen de juste la création d'un nouvel objet avec les valeurs qu'il aime. La seule façon dont peut être réglé <=> à 43 est par modification l'objet en cours en appelant <=>. Le compilateur ne peut pas simplement faire, parce que cela changerait le comportement du programme (il imprimerait à la console)

Donc, pour tous struct d'être immuable, le compilateur aurait de tricher et de briser les règles de la langue. Et bien sûr, si nous sommes prêts à enfreindre les règles, nous pouvons prouver quoi que ce soit. Je pourrais prouver que tous les entiers sont égaux aussi, ou que la définition d'une nouvelle classe ramènerai votre ordinateur pour prendre feu. Tant que nous restons dans les règles de la langue, struct sont mutables.

  

Je ne veux pas compliquer le raisonnement   à ce sujet en considérant ref   paramètres et la boxe. Je suis également conscient   qui exprime p = p.Offset(3, 4);   immuabilité beaucoup mieux que   Fait p.Offset(3, 4);. Mais le   question reste - ne sont pas les types de valeur   immuable par définition?

Eh bien, alors vous n'êtes pas vraiment fonctionner dans le monde réel, êtes-vous? Dans la pratique, puisque, la propension des types de valeur pour faire des copies d'eux-mêmes comme ils se déplacent entre les fonctions mailles bien avec immuabilité, mais ils ne sont pas réellement immuable, sauf si vous les rendre immuable, comme vous l'avez dit, vous pouvez utiliser des références à leur juste comme toute autre chose.

  

ne sont pas les types de valeurs immuables par définition?

Non ils ne sont pas. Si vous regardez le struct par exemple System.Drawing.Point, il a un setter et un getter sur sa propriété X

Cependant, il est vrai de dire que tous les types de valeur devrait définir avec les API immuables.

Je pense que la confusion est que si vous avez un type de référence qui devrait agir comme un type de valeur, il est une bonne idée de le rendre immuable. L'une des principales différences entre les types de valeur et les types de référence est qu'un changement par un nom fait sur un type ref peut apparaître dans l'autre nom. Cela ne se produit pas avec les types de valeurs:

public class foo
{
    public int x;
}

public struct bar
{
    public int x;
}


public class MyClass
{
    public static void Main()
    {
        foo a = new foo();
        bar b = new bar();

        a.x = 1;
        b.x = 1;

        foo a2 = a;
        bar b2 = b;

        a.x = 2;
        b.x = 2;

        Console.WriteLine( "a2.x == {0}", a2.x);
        Console.WriteLine( "b2.x == {0}", b2.x);
    }
}

Produit:

a2.x == 2
b2.x == 1

Maintenant, si vous avez un type que vous souhaitez avoir la sémantique de valeur, mais ne veulent pas faire réellement un type de valeur - peut-être parce que le stockage, il faut, c'est trop ou que ce soit, vous devriez considérer que immuabilité fait partie de la conception. Avec un type ref immuable, toute modification apportée à une référence existante produit un nouvel objet au lieu de changer l'existant, de sorte que vous obtenez le comportement du type de valeur que quelle que soit la valeur que vous tenez ne peut pas être changé par un autre nom.

Bien sûr, la classe System.String est un excellent exemple d'un tel comportement.

L'année dernière, je l'ai écrit un billet de blog sur les problèmes que vous pouvez rencontrer en ne faisant pas struct  immuable.

Le poste complet peut être lu ici

Ceci est un exemple de la façon dont les choses peuvent aller horriblement mal:

//Struct declaration:

struct MyStruct
{
  public int Value = 0;

  public void Update(int i) { Value = i; }
}

Exemple de code:

MyStruct[] list = new MyStruct[5];

for (int i=0;i<5;i++)
  Console.Write(list[i].Value + " ");
Console.WriteLine();

for (int i=0;i<5;i++)
  list[i].Update(i+1);

for (int i=0;i<5;i++)
  Console.Write(list[i].Value + " ");
Console.WriteLine();

La sortie de ce code est:

0 0 0 0 0
1 2 3 4 5

Maintenant, nous allons faire la même chose, mais substituer le tableau pour un générique List<>:

List<MyStruct> list = new List<MyStruct>(new MyStruct[5]); 

for (int i=0;i<5;i++)
  Console.Write(list[i].Value + " ");
Console.WriteLine();

for (int i=0;i<5;i++)
  list[i].Update(i+1);

for (int i=0;i<5;i++)
  Console.Write(list[i].Value + " ");
Console.WriteLine();

La sortie est la suivante:

0 0 0 0 0
0 0 0 0 0

L'explication est très simple. Non, ce n'est pas la boxe / unboxing ...

Lors de l'accès aux éléments d'un tableau, le runtime obtenir les éléments du tableau directement, la méthode de mise à jour () fonctionne sur l'élément du tableau lui-même. Cela signifie que les se struct dans le tableau sont mises à jour.

Dans le second exemple, nous avons utilisé un générique <=>. Qu'est-ce qui se passe lorsque nous avons accès à un élément spécifique? Eh bien, la propriété indexeur est appelée, qui est une méthode. Les types de valeur sont toujours copiés quand ils sont retournés par une méthode, donc c'est exactement ce qui se passe: la méthode indexeur de la liste récupère la structure d'un tableau interne et il retourne à l'appelant. Parce qu'il concerne un type de valeur, une copie sera faite, et la méthode de mise à jour () sera appelée sur la copie, ce qui bien sûr n'a pas d'effet sur les éléments originaux de la liste.

En d'autres termes, assurez-vous toujours que vos struct sont immuables, parce que vous n'êtes jamais sûr quand sera une copie. La plupart du temps, il est évident, mais dans certains cas, il peut vraiment vous surprendre ...

Non, ils ne sont pas. Exemple:

Point p = new Point (3,4);
Point p2 = p;
p.moveTo (5,7);

Dans cet exemple, est un moveTo() en place opération. Il change la structure qui se cache derrière la référence p. Vous pouvez voir que par le regard à p2: Sa position sera également changé. Avec des structures immuables, aurait à Point retourner une nouvelle structure:

p = p.moveTo (5,7);

Maintenant, est immuable et i lorsque vous créez une référence à partout dans votre code, vous n'aurez pas de surprises. Regardons 5:

int i = 5;
int j = i;
i = 1;

Ceci est différent. N'est pas immuable int <=> est. Et la deuxième affectation ne copie pas une référence à la structure qui contient mais <=> copie le contenu de <=>. Donc, dans les coulisses, il arrive quelque chose de complètement différent: Vous obtenez une copie complète de la variable au lieu de seulement une copie de l'adresse en mémoire (la référence)

.

Un équivalent avec des objets serait le constructeur de copie:

Point p = new Point (3,4);
Point p2 = new Point (p);

Ici, la structure interne de est copiée dans <=> un nouvel objet / structure et contiendra la <=> référence. Mais cette opération est assez cher (contrairement à l'affectation entier ci-dessus) qui est la raison pour laquelle la plupart des langages de programmation font la distinction.

Alors que les ordinateurs deviennent plus puissants et obtenir plus de mémoire, cette distinction va disparaître, car il provoque une énorme quantité de bugs et problèmes. Dans la prochaine génération, il n'y aura des objets immuables, toute opération sera protégée par une transaction et même un sera un <=> objet complet soufflé. Tout comme la collecte des ordures, ce sera un grand pas en avant dans la stabilité du programme, causer beaucoup de chagrin dans les premières années, mais il permettra d'écrire un logiciel fiable. Aujourd'hui, les ordinateurs ne sont tout simplement pas assez rapide pour cela.

Non, les types de valeurs sont pas par définition immuable.

D'abord, je dois mieux ai posé la question: « Est-ce que les types de valeurs se comportent comme des types immuables? » au lieu de demander si elles sont immuables - Je suppose que cela a causé beaucoup de confusion

.
struct MutableStruct
{
    private int state;

    public MutableStruct(int state) { this.state = state; }

    public void ChangeState() { this.state++; }
}

struct ImmutableStruct
{
    private readonly int state;

    public MutableStruct(int state) { this.state = state; }

    public ImmutableStruct ChangeState()
    {
        return new ImmutableStruct(this.state + 1);
    }
}

[A suivre ...]

Pour déterminer si un type est mutable ou immuable, il faut définir ce que « type » fait référence. Quand un emplacement de stockage de type de référence est déclarée, la déclaration attribue simplement d'espace pour contenir une référence à un objet stocké ailleurs; la déclaration ne crée pas l'objet réel en question. Néanmoins, dans la plupart des contextes où l'on parle de types de référence particulier, on ne parlera pas d'un emplacement de stockage qui contient une référence , mais l'objet identifié par cette référence . Le fait que l'on peut écrire dans un emplacement de stockage contenant une référence à un objet implique nullement que l'objet lui-même est mutable.

En revanche, lorsqu'un emplacement de stockage de type de valeur est déclarée, le système attribuera à l'intérieur que les emplacements de stockage emplacement de stockage imbriqués pour chaque domaine public ou privé détenu par ce type de valeur. Tout sur le type de valeur est maintenue à cet endroit de stockage. Si l'on définit une variable de type de foo Point et ses deux champs, X et Y, maintenir respectivement 3 et 6. Si l'on définit l'en comme MyPoint = new Point(5,8) « instance » de la paire étant X=5 de champs , cette instance sera mutable si et seulement si mutable est Y=8. Si l'on définit une instance comme étant de la MyPoint valeurs a eu lieu dans ces domaines (par exemple « ) 3,6 », alors une telle instance est par définition immuable, étant donné que la modification d'un de ces domaines causerait de tenir une myPoints[] instance différente.

Je pense qu'il est plus utile de penser à un type de valeur « instance » comme étant les champs, plutôt que les valeurs qu'ils détiennent. Selon cette définition, tout type de valeur stockée dans un emplacement de stockage mutable, et pour lesquels une valeur non par défaut existe, sera toujours être mutable, quelle que soit la façon dont elle est déclarée. Une déclaration construit une nouvelle myPoints[0].X instance de <=>, avec des champs et <=>, puis <=> mute en remplaçant les <=> valeurs dans ses champs avec celles du nouvellement créé <=>. Même si un struct fournit aucun moyen de modifier l'un de ses champs en dehors de son constructeur, il n'y a aucun moyen d'un type struct peut protéger une instance d'avoir tous ses champs écrasés avec le contenu d'une autre instance.

Par ailleurs, un exemple simple dans lequel une structure mutable peut atteindre sémantique non réalisables par d'autres moyens: En supposant que <=> est une matrice à un seul élément qui est accessible à plusieurs threads, ont vingt fils exécuter simultanément le code suivant:

Threading.Interlocked.Increment(myPoints[0].X);

Si commence <=> égale à zéro et vingt threads exécuter le code ci-dessus, que ce soit simultanément ou non, sera égale à vingt <=>. Si l'on devait tenter d'imiter le code ci-dessus avec:

myPoints[0] = new Point(myPoints[0].X + 1, myPoints[0].Y);

alors si un thread lu entre le temps <=> un autre thread lu et écrit de nouveau la valeur révisée, les résultats de l'incrément seraient perdus (avec la conséquence que pourrait finir arbitrairement <=> avec une valeur comprise entre 1 et 20.

Objets / Structs sont immuables quand ils sont passés dans une fonction de telle manière que les données ne peuvent être modifiés, et le retour est une struct struct new. L'exemple classique est

String s = "abc";

s.toLower();

si la fonction est écrit de façon toLower ils une nouvelle chaîne est retournée qui remplace « s », il est immuable, mais si la fonction va lettre par lettre remplaçant la lettre dans « s » et ne jamais déclarer une « nouvelle chaîne » , il est mutable.

Licencié sous: CC-BY-SA avec attribution
Non affilié à StackOverflow
scroll top