Hvorfor kan jeg ikke definere en standardkonstruktør for en struct i .NET?

stemmer
205

I NET, en verditype (C # structkan) ikke ha en konstruktør med noen parametere. Ifølge dette innlegget dette er pålagt av CLI-spesifikasjonen. Det som skjer er at for hver verdi-type en standardkonstruktør er skapt (av kompilatoren?) Som initialisert alle medlemmer til null (eller null).

Hvorfor er det forbudt å definere en slik standard konstruktør?

En triviell bruk er for rasjonale tall:

public struct Rational {
    private long numerator;
    private long denominator;

    public Rational(long num, long denom)
    { /* Todo: Find GCD etc. */ }

    public Rational(long num)
    {
        numerator = num;
        denominator = 1;
    }

    public Rational() // This is not allowed
    {
        numerator = 0;
        denominator = 1;
    }
}

Ved hjelp av gjeldende versjon av C #, er en standard Rational 0/0som ikke er så kult.

PS : Vil standardparametre bidra til å løse dette for C # 4.0 eller vil CLR definerte standardkonstruktør bli kalt?


Jon Skeet svarte:

For å bruke ditt eksempel, hva ville du vil skal skje når noen gjorde:

 Rational[] fractions = new Rational[1000];

Skulle den gå gjennom konstruktør 1000 ganger?

Klart det skal, det er derfor jeg skrev standardkonstruktør i første omgang. CLR bør bruke standard nullstilling konstruktør når ingen eksplisitt standard konstruktør er definert; på den måten du bare betaler for det du bruker. Så hvis jeg ønsker en beholder på 1000 som ikke er standard Rationals (og ønsker å optimalisere bort 1000 konstruksjoner) Jeg bruker en List<Rational>i stedet for en matrise.

Av denne grunn, i mitt sinn, er ikke sterk nok til å hindre definisjon av en standardkonstruktør.

Publisert på 02/12/2008 klokken 12:39
kilden bruker
På andre språk...                            


10 svar

stemmer
167

Merk: svaret nedenfor ble skrevet lenge før C # 6, som planlegger å innføre muligheten til å erklære parameterless konstruktører i structs - men de likevel ikke vil bli kalt i alle situasjoner (f.eks for opprettelse array) (i slutten denne funksjonen ble ikke lagt til C # 6 ).


EDIT: Jeg har redigert svaret nedenfor grunn Grauenwolf innsikt i CLR.

CLR gir verdityper å ha parameterless konstruktører, men C # ikke. Jeg tror dette er fordi det ville innføre en forventning om at konstruktøren ville bli kalt når det ikke ville. For eksempel, tenk på dette:

MyStruct[] foo = new MyStruct[1000];

CLR er i stand til å gjøre dette veldig effektivt bare ved å tildele riktig minne og nullstilling det ut. Hvis det måtte kjøre MyStruct konstruktør 1000 ganger, ville det være mye mindre effektiv. (Faktisk gjør det ikke - hvis du gjør har en parameterless konstruktør, vil det ikke bli løp når du oppretter en matrise, eller når du har en ikke-initialisert instansvariabel.)

Den grunnleggende regelen i C # er "standardverdien for alle typer kan ikke stole på noen initialisering". Nå er de kunne ha tillatt parameterless konstruktører skal defineres, men da ikke nødvendig at konstruktøren skal utføres i alle tilfeller - men det ville ha ført til mer forvirring. (Eller i det minste, så jeg tror argumentet går.)

EDIT: For å bruke ditt eksempel, hva ville du vil skal skje når noen gjorde:

Rational[] fractions = new Rational[1000];

Skulle den gå gjennom konstruktør 1000 ganger?

  • Hvis ikke, ender vi opp med 1000 ugyldige rationals
  • Hvis den gjør det, så vi har potensielt kastet bort en masse arbeid hvis vi er i ferd med å fylle i rekken med reelle verdier.

EDIT: (Svare litt mer av spørsmålet) Den parameterless konstruktør er ikke skapt av kompilatoren. Verdityper trenger ikke å ha konstruktører så langt som CLR er bekymret - selv om det viser seg at det kan hvis du skriver det i IL. Når du skriver " new Guid()" i C # som avgir forskjellig IL til hva du får hvis du kaller en normal konstruktør. Se denne SO spørsmålet for litt mer på det aspektet.

Jeg mistenker at det ikke er noen verdityper i rammen med parameterless konstruktører. Ingen tvil NDepend kunne fortelle meg om jeg spurte det pent nok ... Det faktum at C # forbyr det er en stor nok hint for meg å synes det er sannsynligvis en dårlig idé.

Svarte 02/12/2008 kl. 12:48
kilden bruker

stemmer
36

En struct er en verditype og en verditype må ha en standardverdi så snart den er deklarert.

MyClass m;
MyStruct m2;

Hvis du definerer to feltene som ovenfor uten forekomster heller, deretter bryte debugger, mvil være null, men m2vil ikke. Gitt dette, ville en parameterless konstruktør gir ingen mening, faktisk alt noen konstruktør på en struct gjør er tilordne verdier, ting selv allerede eksisterer bare ved å erklære det. Faktisk m2 kan ganske lykkelig brukes i eksempelet ovenfor, og har sine metoder kalt, om noen, og sine felt og egenskaper manipulert!

Svarte 02/12/2008 kl. 16:58
kilden bruker

stemmer
14

Du kan lage en statisk egenskap som initialiserer og returnerer en standard "rasjonell" nummer:

public static Rational One => new Rational(0, 1); 

Og bruke den som:

var rat = Rational.One;
Svarte 11/01/2009 kl. 17:14
kilden bruker

stemmer
13

Kortere forklaring:

I C ++, struct og klasse var bare to sider av samme sak. Den eneste virkelige forskjellen er at man var offentlig som standard, og den andre var privat.

I NET , det er en mye større forskjell mellom en struct og en klasse. Det viktigste er at struct gir verdi-type semantikk, mens klasse gir referanse-type semantikk. Når du begynner å tenke på konsekvensene av denne endringen, andre endringer begynner å være mer fornuftig også, inkludert konstruktøren atferd du beskriver.

Svarte 02/12/2008 kl. 14:03
kilden bruker

stemmer
9

Selv om CLR tillater det, ikke C # ikke tillate structs å ha en standard parameter-mindre konstruktøren. Årsaken er at for en verditype, kompilatorer som standard verken generere en standardkonstruktør, heller ikke de genererer et anrop til standardkonstruktør. Så selv om du har skjedd å definere standard konstruktør, vil det ikke bli kalt, og det vil bare forvirre deg.

For å unngå slike problemer, forbyr C # kompilatoren definisjon av en standard konstruktør av brukeren. Og fordi den ikke genererer en standardkonstruktør, kan du ikke initial felt når du definerer dem.

Eller den store grunnen er at en struktur er en verditype og verdityper er initialisert av en standardverdi og konstruktøren brukes til initialisering.

Du trenger ikke på å bruke din struct med newsøkeordet. Det stedet fungerer som en int; kan du direkte tilgang til den.

Structs kan ikke inneholde eksplisitte parameterless konstruktører. Struct medlemmer blir automatisk initialisert til standardverdiene.

En standard (parameter-mindre) konstruktør for en struct kunne sette forskjellige verdier enn det all-nullstilt tilstand som ville være uventet oppførsel. .NET runtime forbyr derfor standard konstruktører for en struct.

Svarte 19/01/2015 kl. 12:04
kilden bruker

stemmer
3

Bare spesielle tilfelle det. Hvis du ser en teller fra 0 og nevneren fra 0, late som den har de verdiene du virkelig ønsker.

Svarte 03/12/2008 kl. 07:59
kilden bruker

stemmer
1

Jeg har ikke sett tilsvarende sent løsning jeg kommer til å gi, så her er det.

bruke forskyvninger til å flytte verdier fra standard 0 til en verdi du vil. her egenskaper må brukes i stedet for direkte tilgang til felt. (Kanskje med mulig c # 7 funksjonen du bedre definere eiendoms scoped felt slik at de forblir beskyttet mot direkte tilgang i kode.)

Denne løsningen fungerer for enkle structs med bare verdityper (ingen ref typen eller ha nullverdier struct).

public struct Tempo
{
    const double DefaultBpm = 120;
    private double _bpm; // this field must not be modified other than with its property.

    public double BeatsPerMinute
    {
        get => _bpm + DefaultBpm;
        set => _bpm = value - DefaultBpm;
    }
}

Dette er annerledes enn dette svaret, denne tilnærmingen er ikke særlig casing men dens bruk av forskjøvet som vil fungere for alle områder.

eksempel med enums som felt.

public struct Difficaulty
{
    Easy,
    Medium,
    Hard
}

public struct Level
{
    const Difficaulty DefaultLevel = Difficaulty.Medium;
    private Difficaulty _level; // this field must not be modified other than with its property.

    public Difficaulty Difficaulty
    {
        get => _level + DefaultLevel;
        set => _level = value - DefaultLevel;
    }
}

Som jeg sa dette trikset fungerer kanskje ikke i alle tilfeller, selv om struct har bare verdifelt, bare du vet at hvis det fungerer i ditt tilfelle eller ikke. bare undersøke. men du får den generelle ideen.

Svarte 27/09/2017 kl. 17:31
kilden bruker

stemmer
1

Her er min løsning på ingen standardkonstruktør dilemma. Jeg vet dette er en sen løsning, men jeg tror det er verdt å merke seg er dette en løsning.

public struct Point2D {
    public static Point2D NULL = new Point2D(-1,-1);
    private int[] Data;

    public int X {
        get {
            return this.Data[ 0 ];
        }
        set {
            try {
                this.Data[ 0 ] = value;
            } catch( Exception ) {
                this.Data = new int[ 2 ];
            } finally {
                this.Data[ 0 ] = value;
            }
        }
    }

    public int Z {
        get {
            return this.Data[ 1 ];
        }
        set {
            try {
                this.Data[ 1 ] = value;
            } catch( Exception ) {
                this.Data = new int[ 2 ];
            } finally {
                this.Data[ 1 ] = value;
            }
        }
    }

    public Point2D( int x , int z ) {
        this.Data = new int[ 2 ] { x , z };
    }

    public static Point2D operator +( Point2D A , Point2D B ) {
        return new Point2D( A.X + B.X , A.Z + B.Z );
    }

    public static Point2D operator -( Point2D A , Point2D B ) {
        return new Point2D( A.X - B.X , A.Z - B.Z );
    }

    public static Point2D operator *( Point2D A , int B ) {
        return new Point2D( B * A.X , B * A.Z );
    }

    public static Point2D operator *( int A , Point2D B ) {
        return new Point2D( A * B.Z , A * B.Z );
    }

    public override string ToString() {
        return string.Format( "({0},{1})" , this.X , this.Z );
    }
}

ignorerer det faktum at jeg har en statisk struct kalt null, (Merk: Dette er for alle positive kvadranten bare), ved hjelp får; sett; i C #, kan du ha en prøve / fangst / slutt, for å håndtere de feilene hvor en bestemt datatype ikke er initialisert av standardkonstruktør Point2D (). Jeg antar at dette er unnvikende som en løsning på noen folk på dette svaret. Dvs stort sett derfor jeg legger meg. Bruke getter og setter funksjonalitet i C # vil tillate deg å omgå denne standardkonstruktøren ikke-følelse og sette en prøve fangst rundt hva du ikke har initialisert. For meg er dette fungerer fint for noen andre kan det være lurt å legge til noen hvis uttalelser. Så, i tilfelle hvor du ønsker en teller / nevner oppsett, kan denne koden hjelpe. Jeg ønsker bare å gjenta at denne løsningen ser ikke fint, sannsynligvis fungerer enda verre fra en effektivitet ståsted, men for noen som kommer fra en eldre versjon av C #, ved hjelp av tabelldatatyper gir deg denne funksjonaliteten. Hvis du bare vil ha noe som fungerer, prøv dette:

public struct Rational {
    private long[] Data;

    public long Numerator {
        get {
            try {
                return this.Data[ 0 ];
            } catch( Exception ) {
                this.Data = new long[ 2 ] { 0 , 1 };
                return this.Data[ 0 ];
            }
        }
        set {
            try {
                this.Data[ 0 ] = value;
            } catch( Exception ) {
                this.Data = new long[ 2 ] { 0 , 1 };
                this.Data[ 0 ] = value;
            }
        }
    }

    public long Denominator {
        get {
            try {
                return this.Data[ 1 ];
            } catch( Exception ) {
                this.Data = new long[ 2 ] { 0 , 1 };
                return this.Data[ 1 ];
            }
        }
        set {
            try {
                this.Data[ 1 ] = value;
            } catch( Exception ) {
                this.Data = new long[ 2 ] { 0 , 1 };
                this.Data[ 1 ] = value;
            }
        }
    }

    public Rational( long num , long denom ) {
        this.Data = new long[ 2 ] { num , denom };
        /* Todo: Find GCD etc. */
    }

    public Rational( long num ) {
        this.Data = new long[ 2 ] { num , 1 };
        this.Numerator = num;
        this.Denominator = 1;
    }
}
Svarte 13/11/2016 kl. 19:20
kilden bruker

stemmer
1

Du kan ikke definere en standard konstruktør fordi du bruker C #.

Structs kan ha standard konstruktører i .NET, men jeg vet ikke om noen bestemt språk som støtter det.

Svarte 03/12/2008 kl. 19:53
kilden bruker

stemmer
0
public struct Rational 
{
    private long numerator;
    private long denominator;

    public Rational(long num = 0, long denom = 1)   // This is allowed!!!
    {
        numerator   = num;
        denominator = denom;
    }
}
Svarte 17/08/2018 kl. 18:23
kilden bruker

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