Hvorfor er foranderlig structs “ondt”?

stemmer
439

Etter diskusjonene her på så jeg allerede lest flere ganger bemerkning som foranderlig structs er “ondt” (som i svaret på dette spørsmålet ).

Hva er det egentlige problemet med mutability og structs i C #?

Publisert på 13/01/2009 klokken 23:27
kilden bruker
På andre språk...                            


16 svar

stemmer
268

Structs er verdityper som betyr at de er kopiert når de er gått rundt.

Så hvis du endrer en kopi du endrer bare at kopien, ikke den originale og ikke eventuelle andre kopier som kan være rundt.

Hvis struct er uforanderlig da alle automatiske kopier som følge av å bli passert av verdien vil være den samme.

Hvis du ønsker å endre det du har å bevisst gjøre det ved å opprette en ny forekomst av struct med de endrede data. (Ikke en kopi)

Svarte 13/01/2009 kl. 23:42
kilden bruker

stemmer
161

Hvor skal jeg begynne ;-p

Eric Lippert blogg er alltid bra for et sitat:

Dette er enda en grunn til at foranderlig verdityper er onde. Prøv å alltid gjøre verdityper uforanderlig.

Først har du en tendens til å miste endringer ganske enkelt ... for eksempel, å få ting ut av en liste:

Foo foo = list[0];
foo.Name = "abc";

hva gjorde at endring? Noe nyttig ...

Det samme med egenskaper:

myObj.SomeProperty.Size = 22; // the compiler spots this one

tvinger deg til å gjøre:

Bar bar = myObj.SomeProperty;
bar.Size = 22;
myObj.SomeProperty = bar;

mindre kritisk, er det et problem størrelse; foranderlig gjenstander en tendens til å ha flere egenskaper; men hvis du har en struct med to ints, en string, en DateTimeog en bool, kan du raskt brenne gjennom mye minne. Med en klasse, kan flere ringer dele en referanse til den samme forekomsten (referanser er liten).

Svarte 13/01/2009 kl. 23:31
kilden bruker

stemmer
67

Jeg vil ikke si det onde , men mutability er ofte et tegn på overeagerness på den delen av programmerer for å gi maksimal funksjonalitet. I virkeligheten, dette er ofte ikke nødvendig, og som i sin tur gjør grensesnittet mindre, enklere å bruke og vanskeligere å bruke feil (= mer robust).

Et eksempel på dette er å lese / skrive og skrive / skrive konflikter i race conditions. Disse rett og slett ikke kan forekomme i uforanderlige strukturer, siden en skrive er ikke en gyldig operasjon.

Dessuten hevder jeg at mutability er nesten aldri faktisk nødvendig , programmerer bare tenker at det kan være i fremtiden. For eksempel, det rett og slett ikke fornuftig å endre en dato. Snarere opprette en ny dato basert off den gamle. Dette er en billig operasjon, slik at ytelsen er ikke en vurdering.

Svarte 13/01/2009 kl. 23:34
kilden bruker

stemmer
38

Foranderlig structs er ikke onde.

De er absolutt nødvendig i høy ytelse omstendigheter. For eksempel når cache linjer og eller søppelrydding bli en flaskehals.

Jeg vil ikke kalle bruk av en uforanderlig struct i disse helt lovlig bruk-tilfeller "ondt".

Jeg kan se det punktet at C # 's syntaks ikke hjelper å skille ut tilgangs av et medlem av en verditype eller en referansetype, så jeg er alt for foretrakk uforanderlige structs, som håndheve uforanderlighet, over foranderlig structs.

Men i stedet for å bare merking uforanderlige structs som "ondt", ville jeg råde til å omfavne språket og argumentere mer nyttig og konstruktiv regel tommelen.

For eksempel: "structs er verdityper som er kopiert som standard du trenger en referanse hvis du ikke ønsker å kopiere dem." Eller "prøve å jobbe med skrivebeskyttet structs først" .

Svarte 04/06/2013 kl. 08:10
kilden bruker

stemmer
34

Structs med offentlige foranderlig felt eller egenskaper er ikke onde.

Struct metoder (til forskjell fra eiendoms settere) som mutere "dette" er noe ondt, bare fordi .net ikke gir et middel for å skille dem fra metoder som ikke gjør det. Struct metoder som ikke mutere "dette" skal være startbar selv på skrivebeskyttet structs uten behov for defensive kopiering. Metoder som gjør mutere "dette" bør ikke være startbar hele tatt på skrivebeskyttet structs. Siden .net ikke ønsker å forby struct metoder som ikke modifiserer "dette" blir påkalt på skrivebeskyttede structs, men ikke ønsker å tillate skrivebeskyttet structs å bli mutert, det defensivt kopierer structs i lese- bare sammenhenger, uten tvil få det verste av begge verdener.

Til tross for problemene med håndteringen av selvmuterende metoder i skrivebeskyttet sammenhenger, men foranderlig structs ofte tilbyr semantikk langt overlegen til foranderlig klassetyper. Vurdere følgende tre metoden signaturer:

struct PointyStruct {public int x, y, z;};
class PointyClass {public int x, y, z;};

ugyldig Method1 (PointyStruct foo);
ugyldig method2 (ref PointyStruct foo);
ugyldig Method3 (PointyClass foo);

For hver metode, svare på følgende spørsmål:

  1. Forutsatt metoden bruker ikke noen "usikker" kode, kan det endre foo?
  2. Hvis ingen utenfor referanser til 'foo' eksisterer før metoden kalles, kan en ekstern referanse eksistere etter?

svar:

Spørsmål 1:
Method1(): no (klar hensikt)
Method2() : yes (klar hensikt)
Method3() : yes (usikker forsett)
Spørsmål 2:
Method1(): no
Method2(): ingen (med mindre usikre)
Method3() : ja

Method1 kan ikke endre foo, og aldri får en referanse. Method2 får en kortvarig referanse til foo, som den kan bruke modifisere feltene foo en rekke ganger, i hvilken som helst rekkefølge, før den returnerer, men det kan ikke vedvare som referanse. Før method2 returnerer, med mindre den bruker usikre kode, alle kopier som kan ha blitt laget av sin 'foo' referansen har forsvunnet. Method3, i motsetning method2, får et promiskuøst-delbar referanse til foo, og det er ikke godt å si hva det kan gjøre med det. Det kan ikke endre foo i det hele tatt, det kan endre foo og deretter gå tilbake, eller det kan gi en referanse til foo til en annen tråd som kan mutere det på noen vilkårlig måte på noen vilkårlig gang i fremtiden. Den eneste måten å begrense hva Method3 kan gjøre til et foranderlig klasse objektet gått inn i det ville være å kapsle foranderlig objekt i en skrivebeskyttet wrapper, som er stygg og klumpete.

Matriser av strukturer tilbyr fantastisk semantikk. Gitt RectArray [500] av type rektangel, er det klart og tydelig hvordan du for eksempel kopi element 123 til element 456 og deretter en stund senere satt bredden på elementet 123 til 555, uten forstyrrende element 456. "RectArray [432] = RectArray [321 ]; ...; RectArray [123] .Width = 555;". Å vite at rektangelet er en struct med et heltall felt kalt Bredde vil fortelle en alt man trenger å vite om utsagnene ovenfor.

Nå antar RectClass var en klasse med de samme feltene som rektangel og en ønsket å gjøre de samme operasjonene på en RectClassArray [500] av type RectClass. Kanskje matrisen er ment å holde 500 forhånds initialisert uforanderlige referanser til foranderlig RectClass stedene. i så fall ville den riktige koden bli noe sånt som "RectClassArray [321] .SetBounds (RectClassArray [456]); ...; RectClassArray [321] .X = 555;". Kanskje rekke antas å holde forekomster som ikke kommer til å forandre seg, så den riktige koden ville være mer som "RectClassArray [321] = RectClassArray [456]; ...; RectClassArray [321] = Ny RectClass (RectClassArray [321 ]); RectClassArray [321] .X = 555;" Å vite hva man skal gjøre, må en vite mye mer både om RectClass (f.eks tillater det en kopi konstruktør, en kopi-fra-metoden, etc.) og den tiltenkte bruken av tabellen. Ikke på langt nær så ren som bruker en struct.

For å være sikker, det er dessverre ingen fin måte for noen beholder klasse annet enn en matrise for å tilby rene semantikk av en struct array. Det beste man kan gjøre, hvis man ønsket en samling som skal indekseres med f.eks en streng, vil trolig være å tilby en generisk "ActOnItem" metode som ville godta en streng for indeksen, en generisk parameter, og en delegat som ville bli vedtatt ved henvisning både den generiske parameter og samlingen elementet. Som ville tillate nesten samme semantikk som struct arrays, men med mindre VB.NET og C # personer kan pursuaded å tilby en hyggelig syntaks, er koden kommer til å være clunky utseende, selv om det er rimelig ytelse (passerer en generisk parameter ville tillater bruk av en statisk delegere og ville unngå behovet for å skape noen midlertidige klasse tilfeller).

Personlig er jeg irritert på hat Eric Lippert et al. spy ut om foranderlig verdityper. De tilbyr mye renere semantikk enn promiskuøse referansetypene som brukes over alt. Til tross for noen av begrensningene med .net støtte til verdityper, er det mange tilfeller hvor foranderlig verdityper er en bedre passform enn noen annen form for enhet.

Svarte 29/09/2011 kl. 03:28
kilden bruker

stemmer
23

Verdi typer representerer i utgangspunktet uforanderlige konsepter. Fx, er det ingen mening å ha en matematisk verdi, for eksempel et heltall, vektor etc. og deretter være i stand til å modifisere det. Det ville være som å redefinere betydningen av en verdi. I stedet for å endre en verditype, er det mer fornuftig å tildele en annen unik verdi. Tenk på det faktum at verdityper sammenlignes ved å sammenligne alle verdiene av eiendommene. Poenget er at hvis egenskaper er de samme da det er den samme universelle fremstilling av denne verdien.

Som Konrad nevner det ikke fornuftig å endre en dato enten, som verdien som representerer den unike tidspunkt og ikke en forekomst av en tid objekt som har en hvilken som helst tilstand eller en kontekst-avhengighet.

Håper dette gir noen mening for deg. Det handler mer om konseptet du prøver å fange med verdityper enn praktiske detaljer, for å være sikker.

Svarte 13/01/2009 kl. 23:52
kilden bruker

stemmer
17

Det er en annen par hjørne saker som kan føre til unpredictible oppførsel fra programmerere synspunkt. Her par av dem.

  1. Uforanderlige verdityper og skrivebeskyttet felt

// Simple mutable structure. 
// Method IncrementI mutates current state.
struct Mutable
{
    public Mutable(int i) : this() 
    {
        I = i;
    }

    public void IncrementI() { I++; }

    public int I {get; private set;}
}

// Simple class that contains Mutable structure
// as readonly field
class SomeClass 
{
    public readonly Mutable mutable = new Mutable(5);
}

// Simple class that contains Mutable structure
// as ordinary (non-readonly) field
class AnotherClass 
{
    public Mutable mutable = new Mutable(5);
}

class Program
{
    void Main()
    {
        // Case 1. Mutable readonly field
        var someClass = new SomeClass();
        someClass.mutable.IncrementI();
        // still 5, not 6, because SomeClass.mutable field is readonly
        // and compiler creates temporary copy every time when you trying to
        // access this field
        Console.WriteLine(someClass.mutable.I);

        // Case 2. Mutable ordinary field
        var anotherClass = new AnotherClass();
        anotherClass.mutable.IncrementI();

        //Prints 6, because AnotherClass.mutable field is not readonly
        Console.WriteLine(anotherClass.mutable.I);
    }
}

  1. Foranderlig verdityper og utvalg

Anta at en rekke av våre Form struct og vi kaller IncrementI metode for det første elementet i denne matrisen. Hva oppførsel du forventer fra denne samtalen? Skulle det endre matrise verdi eller bare en kopi?

Mutable[] arrayOfMutables = new Mutable[1];
arrayOfMutables[0] = new Mutable(5);

// Now we actually accessing reference to the first element
// without making any additional copy
arrayOfMutables[0].IncrementI();

//Prints 6!!
Console.WriteLine(arrayOfMutables[0].I);

// Every array implements IList<T> interface
IList<Mutable> listOfMutables = arrayOfMutables;

// But accessing values through this interface lead
// to different behavior: IList indexer returns a copy
// instead of an managed reference
listOfMutables[0].IncrementI(); // Should change I to 7

// Nope! we still have 6, because previous line of code
// mutate a copy instead of a list value
Console.WriteLine(listOfMutables[0].I);

Så, foranderlig structs er ikke onde, så lenge du og resten av teamet klart å forstå hva du gjør. Men det er for mange hjørne tilfeller når programmet oppførsel ville være forskjellig fra forventet en, som kan føre til subtile vanskelig å produsere og vanskelig å forstå feil.

Svarte 14/07/2011 kl. 10:26
kilden bruker

stemmer
14

Hvis du noen gang har programmert i et språk som C / C ++, structs er fine å bruke som foranderlig. Bare passere dem med ref, rundt, og det er ingenting som kan gå galt. Det eneste problemet jeg finner er restriksjoner i C # kompilatoren og at det i noen tilfeller kan jeg ikke tvinge dum ting å bruke en referanse til struct, i stedet for en kopi (som når en struct er en del av en C # klasse ).

Så, foranderlig structs er ikke onde, har C # gjort dem ondt. Jeg bruker foranderlig structs i C ++ hele tiden, og de er veldig praktisk og intuitivt. I kontrast, har C # gjort meg til helt forlate structs som medlemmer av klasser på grunn av måten de håndterer stedene. Deres bekvemmelighet har kostet oss våre.

Svarte 18/10/2013 kl. 11:22
kilden bruker

stemmer
10

Hvis du holder deg til hva structs er ment for (i C #, Visual Basic 6, Pascal / Delphi, C ++ struct type (eller klasser) når de ikke brukes som pekere), vil du finne at en struktur er ikke mer enn et forbindelsen variabel . Dette betyr: du vil behandle dem som en fullpakket sett av variabler, under et felles navn (en rekord variabel du refererer medlemmer fra).

Jeg vet det ville forvirre mange mennesker dypt brukes til OOP, men det er ikke nok grunn til å si slike ting er iboende onde, hvis den brukes riktig. Noen strukturer er inmutable som de har tenkt (dette er tilfellet for Pythons namedtuple), men det er en annen paradigme å vurdere.

Ja: structs bære mye minne, men det vil ikke være nettopp mer minne ved å gjøre:

point.x = point.x + 1

sammenlignet med:

point = Point(point.x + 1, point.y)

Minneforbruket vil være minst den samme, eller enda mer i tilfelle inmutable (selv om det tilfellet ville være forbigående, for det aktuelle stabelen, avhengig av språket).

Men til slutt, strukturer er strukturer , ikke objekter. I POO, den viktigste egenskapen til et objekt er deres identitet , som mesteparten av tiden er ikke mer enn sin minneadresse. Struct står for datastruktur (ikke et riktig objekt, og slik at de ikke har identitet hvertfall), og data kan bli endret. På andre språk, rekord (i stedet for struct , slik tilfellet er for Pascal) er ordet og holder samme formål: bare en datapost variabel, skal leses fra filer, endret og dumpet i filer (som er hoved bruke og, på mange språk, kan du selv definere data justering i posten, mens det er ikke nødvendigvis tilfelle for ordentlig kalt objekter).

Ønsker et godt eksempel? Structs er vant til å lese filer enkelt. Python har dette biblioteket fordi, siden det er objektorientert og har ingen støtte for structs, det måtte gjennomføre det på en annen måte, som er noe stygg. Språk gjennomførings structs har den funksjonen ... innebygd. Prøv å lese en bitmap header med en passende struct på språk som Pascal eller C. Det vil være lett (hvis struct er riktig bygget og justert, i Pascal ville du ikke bruke en post-basert tilgang, men fungerer for å lese vilkårlige binære data). Så, for filer og direkte (lokal) minnetilgang, structs er bedre enn stedene. Som for i dag, vi er vant til JSON og XML, og så glemmer vi å bruke binærfiler (og som en bivirkning, bruk av structs). Men ja, de finnes, og har en hensikt.

De er ikke onde. Bare bruke dem til rett formål.

Hvis du tenker i form av hammere, vil du ønsker å behandle skruer som negler, for å finne skruer er vanskeligere å stupe i veggen, og det vil være skruer skyld, og de vil være de onde.

Svarte 02/12/2015 kl. 15:29
kilden bruker

stemmer
10

Tenk deg at du har en rekke 1.000.000 structs. Hver struct som tilsvarer en egenkapital med ting som bid_price, OFFER_PRICE (kanskje desimaler) og så videre, er denne laget av C # / VB.

Tenk deg at tabellen er opprettet i en blokk med minne som er tildelt i uovervåkede haug slik at en annen egen kode tråden er i stand til å samtidig få tilgang til array (kanskje noen high-perf kode gjør matte).

Tenk deg C # / VB kode er å lytte til et marked feed av prisendringer, kan denne koden må få tilgang til noen element i matrisen (for hvilken sikkerhet) og deretter endre noen pris felt (er).

Tenk deg dette blir gjort titalls eller hundrevis av tusenvis av ganger per sekund.

Vel lar innse fakta, i dette tilfellet vi virkelig ønsker disse structs å være foranderlig, må de være fordi de blir delt av en annen egen kode så skaper kopier er ikke skal hjelpe; de trenger å være, fordi å lage en kopi av noen 120 byte struct på disse prisene er galskap, særlig når en oppdatering kan faktisk påvirke bare en byte eller to.

Hugo

Svarte 21/03/2010 kl. 22:37
kilden bruker

stemmer
7

Når noe kan mutert, får det en følelse av identitet.

struct Person {
    public string name; // mutable
    public Point position = new Point(0, 0); // mutable

    public Person(string name, Point position) { ... }
}

Person eric = new Person("Eric Lippert", new Point(4, 2));

Fordi Personer foranderlig, er det mer naturlig å tenke på å endre Eric posisjon enn kloning Eric, flytte klone, og ødelegge det opprinnelige . Begge operasjonene ville lykkes i å endre innholdet i eric.position, men en er mer intuitiv enn den andre. Likeledes er det mer intuitivt å passere Eric rundt (som referanse) etter metoder for å endre ham. Å gi en metode en klone av Eric er nesten alltid kommer til å være overraskende. Alle som ønsker å mutere Personmå huske å be om en henvisning til Person, eller de kommer til å gjøre gale ting.

Hvis du gjør den type uforanderlige, går problemet borte; hvis jeg ikke kan endre eric, gjør det ingen forskjell for meg om jeg får ericeller en klone av eric. Mer generelt er en type trygt å passere verdi dersom alle dens observer tilstand holdes i medlemmer som enten:

  • uforanderlige
  • referanse typer
  • trygt å passere verdien

Hvis disse vilkårene ikke er oppfylt da en foranderlig verditype oppfører seg som en referansetype fordi en grunne kopi vil fortsatt tillate mottakeren til å endre de opprinnelige dataene.

Den intuitiv en uforanderlig Personavhenger av hva du prøver å gjøre selv. Hvis Personbare representerer et sett av data om en person, det er ingenting unintuitive om det; Personvariabler virkelig representerer abstrakte verdier , ikke objekter. (I så fall vil det trolig være mer hensiktsmessig å endre navnet til PersonData.) Hvis Personfaktisk modellering en person selv, ideen om å stadig skape og flytte kloner er dum selv om du har unngått fallgruve av å tro at du endrer den opprinnelige. I så fall vil det trolig være mer naturlig å bare gjøre Personen referansetype (det vil si en klasse.)

Riktignok så funksjonell programmering har lært oss at det er fordeler med å gjøre alt uforanderlig (ingen kan hemmelighet holde på en referanse til ericog mutere ham), men siden det ikke er idiomatisk i OOP det fortsatt kommer til å være unintuitive til noen andre som arbeider med kode.

Svarte 02/10/2013 kl. 13:52
kilden bruker

stemmer
5

Personlig når jeg ser på kode følgende ser ganske klumpete til meg:

data.value.set (data.value.get () + 1);

snarere enn bare

data.value ++; eller data.value = data.value + 1;

Data innkapsling er nyttig når du passerer en klasse rundt og du ønsker å sikre verdien er endret på en kontrollert måte. Men når du har offentlig sett og få funksjoner som gjør litt mer enn satt verdien til hva noensinne er gått i, hvordan er dette en forbedring bare passerer en offentlig datastruktur rundt?

Når jeg oppretter en egen struktur inne i en klasse, jeg opprettet denne strukturen til å organisere et sett av variabler i en gruppe. Jeg ønsker å være i stand til å endre denne strukturen i klassen omfang, ikke få eksemplarer av den struktur og opprette nye forekomster.

For meg er dette forhindrer en gyldig bruk av strukturer som brukes til å organisere offentlige variabler, hvis jeg ville adgangskontroll Jeg vil bruke en klasse.

Svarte 07/02/2011 kl. 17:31
kilden bruker

stemmer
5

Det har ikke noe å gjøre med structs (og ikke med C #, heller), men i Java kan du få problemer med foranderlig gjenstander når de er f.eks nøklene i en hash kartet. Hvis du endrer dem etter å ha lagt dem til et kart, og det skifter hash-kode , kan onde ting skje.

Svarte 13/01/2009 kl. 23:34
kilden bruker

stemmer
4

Det er flere problemer med Mr. Eric Lippert eksempel. Det er contrived å illustrere poenget at structs kopieres og hvordan det kan være et problem hvis du ikke er forsiktig. Ser vi på eksempelet jeg ser det som et resultat av en dårlig programmering vane og egentlig ikke et problem med enten struct eller klassen.

  1. En struct er ment å ha bare offentlige medlemmer, og bør ikke krever noen innkapsling. Hvis den gjør det så det bør være en type / klasse. Du trenger ikke to konstruksjoner for å si det samme.

  2. Hvis du har klassen omslutter en struct, ville du kalle en metode i klassen til å mutere medlem struct. Dette er hva jeg ville gjøre som en god programmering vane.

En skikkelig gjennomføring vil være som følger.

struct Mutable {
public int x;
}

class Test {
    private Mutable m = new Mutable();
    public int mutate()
    { 
        m.x = m.x + 1;
        return m.x;
    }
  }
  static void Main(string[] args) {
        Test t = new Test();
        System.Console.WriteLine(t.mutate());
        System.Console.WriteLine(t.mutate());
        System.Console.WriteLine(t.mutate());
    }

Det ser ut som det er et problem med programmering vane i motsetning til et problem med struct selv. Structs er ment å være foranderlig, er at ideen og intensjonen.

Resultatet av endringene voila oppfører seg som forventet:

1 2 3 Trykk på en tast for å fortsette. . .

Svarte 12/05/2015 kl. 18:02
kilden bruker

stemmer
4

Det er mange fordeler og ulemper for foranderlig data. Millioner dollar ulempe er aliasing. Hvis den samme verdien som brukes på flere steder, og en av dem endrer det, så vil det synes å ha magisk endret til andre steder som bruker det. Dette er relatert til, men ikke identisk med, race conditions.

Millioner dollar fordel er modularitet, noen ganger. Foranderlig staten kan tillate deg å skjule skiftende informasjon fra kode som ikke trenger å vite om det.

The Art of Interpreter går inn i disse handel offs i noen detalj, og gir noen eksempler.

Svarte 13/01/2009 kl. 23:45
kilden bruker

stemmer
1

Jeg tror ikke de er onde hvis den brukes riktig. Jeg ville ikke sette den i min produksjonskode, men jeg ville for noe sånt strukturert enhetstesting spotter, hvor levetiden til en struct er relativt liten.

Bruke Eric eksempel, kanskje du vil lage en ny forekomst av at Eric, men foreta justeringer, så det er innholdet av testen (dvs. duplisering, deretter endre). Det spiller ingen rolle hva som skjer med det første tilfellet av Eric om vi bare bruker Eric2 for resten av testskriptet, med mindre du planlegger å bruke ham som en test sammenligning.

Dette vil være mest nyttig for testing eller for å modifisere eldre kode som grunne definerer et bestemt objekt (punktet structs), men ved å ha en uforanderlig struct, hindrer dette den er bruken irriterende.

Svarte 19/12/2013 kl. 12:25
kilden bruker

Cookies help us deliver our services. By using our services, you agree to our use of cookies. Learn more