1628 Stimmen

Wann sollte ich in C# eine Struktur anstelle einer Klasse verwenden?

Wann sollten Sie in C# struct und nicht class verwenden? Mein konzeptionelles Modell besagt, dass structs verwendet werden, wenn das Element nur eine Sammlung von Wertetypen ist. Eine Möglichkeit, sie alle logisch zu einem zusammenhängenden Ganzen zusammenzufassen.

Ich bin auf diese Regeln hier gestoßen:

  • Ein struct sollte einen einzigen Wert repräsentieren.
  • Ein struct sollte einen Speicherbedarf von weniger als 16 Bytes haben.
  • Ein struct sollte nach der Erstellung nicht verändert werden.

Funktionieren diese Regeln? Was bedeutet ein struct semantisch?

0 Stimmen

Ich kann nur dem ersten Punkt zustimmen, Strukturen werden zum Beispiel sehr häufig in der Spieleprogrammierung verwendet.

305 Stimmen

System.Drawing.Rectangle verletzt alle drei dieser Regeln.

3 Stimmen

Ja, nun gut, zumindest Teile davon. Ich weiß, dass es für Teile von Spielen verwendet wird, wie NWN2 World Creator. C ist in der Regel immer noch das Kernelement (Engine). XNA Game Studio, Google es :)

46voto

Luke Baughan Punkte 4548

Vom C# Sprachspezifikation:

1.7 Strukturen

Ähnlich wie Klassen sind Strukturen Datenstrukturen, die Datenmember und Funktionen enthalten können, aber im Gegensatz zu Klassen sind Strukturen Werttypen und erfordern keine Speicherplatzzuweisung im Heap. Eine Variable eines Strukturtyps speichert die Daten der Struktur direkt, während eine Variable eines Klasstyps eine Referenz auf ein dynamisch allokiertes Objekt speichert. Strukturtypen unterstützen keine benutzerdefinierte Vererbung und alle Strukturtypen erben implizit vom Typ "object".

Strukturen sind besonders nützlich für kleine Datenstrukturen mit Wertsemantik. Komplexe Zahlen, Punkte in einem Koordinatensystem oder Schlüssel-Wert-Paare in einem Wörterbuch sind alles gute Beispiele für Strukturen. Die Verwendung von Strukturen anstelle von Klassen für kleine Datenstrukturen kann einen großen Unterschied in der Anzahl der Speicherplatzzuweisungen ausmachen, die eine Anwendung durchführt. Zum Beispiel erstellt und initialisiert das folgende Programm ein Array von 100 Punkten. Mit Point implementiert als Klasse werden 101 separate Objekte instanziiert – eins für das Array und jeweils eins für die 100 Elemente.

class Point
{
   public int x, y;

   public Point(int x, int y) {
      this.x = x;
      this.y = y;
   }
}

class Test
{
   static void Main() {
      Point[] points = new Point[100];
      for (int i = 0; i < 100; i++) points[i] = new Point(i, i);
   }
}

Ein alternativer Ansatz ist es, Point zu einer Struktur zu machen.

struct Point
{
   public int x, y;

   public Point(int x, int y) {
      this.x = x;
      this.y = y;
   }
}

Jetzt wird nur ein Objekt instanziiert – das für das Array – und die Point-Instanzen werden inline im Array gespeichert.

Strukturkonstruktoren werden mit dem new-Operator aufgerufen, was jedoch nicht bedeutet, dass Speicherplatz zugeordnet wird. Anstatt ein Objekt dynamisch zuzuweisen und eine Referenz darauf zurückzugeben, gibt ein Strukturkonstruktor einfach den Strukturwert selbst zurück (normalerweise an einem temporären Ort auf dem Stapel), und dieser Wert wird bei Bedarf kopiert.

Bei Klassen ist es möglich, dass zwei Variablen auf dasselbe Objekt verweisen und somit Operationen auf einer Variablen das von der anderen Variablen referenzierte Objekt beeinflussen können. Bei Strukturen haben die Variablen jeweils ihre eigene Kopie der Daten, und es ist nicht möglich, dass Operationen auf eine die andere beeinflussen. Zum Beispiel hängt die Ausgabe des folgenden Codefragments davon ab, ob Point eine Klasse oder eine Struktur ist.

Point a = new Point(10, 10);
Point b = a;
a.x = 20;
Console.WriteLine(b.x);

Wenn Point eine Klasse ist, ist die Ausgabe 20, weil a und b auf dasselbe Objekt verweisen. Wenn Point eine Struktur ist, ist die Ausgabe 10, weil die Zuweisung von a an b eine Kopie des Werts erstellt und diese Kopie durch die nachfolgende Zuweisung an a.x nicht beeinflusst wird.

Das vorherige Beispiel verdeutlicht zwei der Einschränkungen von Strukturen. Erstens ist das Kopieren einer kompletten Struktur in der Regel weniger effizient als das Kopieren einer Objektreferenz, sodass Zuweisungen und Wertparameterübergaben bei Strukturen teurer sein können als bei Verweistypen. Zweitens ist es abgesehen von ref- und out-Parametern nicht möglich, Verweise auf Strukturen zu erstellen, was ihren Einsatz in einer Reihe von Situationen ausschließt.

6 Stimmen

Während die Tatsache, dass Verweise auf Strukturen nicht gespeichert werden können, manchmal eine Einschränkung darstellt, ist es auch eine sehr nützliche Eigenschaft. Eines der Hauptprobleme von .NET ist, dass es keinen ordentlichen Weg gibt, außerhalb des Codes einen Verweis auf ein veränderliches Objekt zu übergeben, ohne die Kontrolle darüber für immer zu verlieren. Im Gegensatz dazu kann man einem externen Methode sicher einen ref auf eine veränderliche Struktur übergeben und sicher sein, dass alle Mutationen, die diese externe Methode daran vornehmen wird, vor ihrer Rückkehr durchgeführt werden. Es ist schade, dass .NET kein Konzept von flüchtigen Parametern und Funktionsrückgabewerten hat, da...

5 Stimmen

... die es ermöglichen würden, die vorteilhaften Semantiken von Strukturen, die mit ref übergeben werden, mit Klassenobjekten zu erreichen. Im Wesentlichen könnten lokale Variablen, Parameter und Funktionsrückgabewerte beständig (standardmäßig), rückgabefähig oder vergänglich sein. Code wäre davon abgehalten, vergängliche Dinge zu kopieren, die über den aktuellen Bereich hinaus existieren würden. Rückgabefähige Dinge wären wie vergängliche Dinge, außer dass sie von einer Funktion zurückgegeben werden könnten. Der Rückgabewert einer Funktion wäre durch die strengsten Beschränkungen gebunden, die für einen ihrer "rückgabefähigen" Parameter gelten.

40voto

Usman Zafar Punkte 1749

Hier ist eine grundlegende Regel.

  • Wenn alle Memberfelder Werttypen sind, erstellen Sie eine struct.

  • Wenn ein beliebiges Memberfeld ein Verweistyp ist, erstellen Sie eine class. Dies liegt daran, dass das Verweistypfeld sowieso eine heap-Allokation benötigt.

Beispiele

public struct MyPoint 
{
    public int X; // Werttyp
    public int Y; // Werttyp
}

public class MyPointWithName 
{
    public int X; // Werttyp
    public int Y; // Werttyp
    public string Name; // Verweistyp
}

8 Stimmen

Unveränderliche Referenztypen wie string sind semantisch äquivalent zu Werten, und das Speichern einer Referenz auf ein unveränderliches Objekt in ein Feld erfordert keinen Heap-Allokation. Der Unterschied zwischen einer Struktur mit öffentlichen Feldern und einem Klassenobjekt mit öffentlichen Feldern besteht darin, dass bei der Code-Sequenz var q=p; p.X=4; q.X=5; der Wert von p.X 4 sein wird, wenn a ein Strukturtyp ist, und 5, wenn es sich um einen Klassentyp handelt. Wenn man die Mitglieder des Typs bequem ändern möchte, sollte man 'class' oder 'struct' basierend darauf wählen, ob Änderungen an q p beeinflussen sollen.

0 Stimmen

Ja, ich stimme zu, dass die Referenzvariable auf dem Stack liegen wird, aber das Objekt, auf das sie verweist, existiert im Heap. Obwohl sich Strukturen und Klassen unterschiedlich verhalten, wenn sie einer anderen Variablen zugewiesen werden, glaube ich nicht, dass dies ein ausschlaggebender Faktor ist.

0 Stimmen

Veränderbare Strukturen und veränderbare Klassen verhalten sich völlig unterschiedlich; wenn das eine richtig ist, wird das andere höchstwahrscheinlich falsch sein. Ich bin mir nicht sicher, wie das Verhalten kein entscheidender Faktor sein würde, um zu bestimmen, ob man eine Struktur oder eine Klasse verwenden soll.

38voto

Franci Penov Punkte 73239

Strukturen sind gut für die atomare Darstellung von Daten, bei der die genannten Daten vom Code mehrmals kopiert werden können. Das Klonen eines Objekts ist im Allgemeinen teurer als das Kopieren einer Struktur, da es die Speicherzuweisung, das Ausführen des Konstruktors und das Deallocieren/Abfallbereinigung beim Beenden umfasst.

4 Stimmen

Ja, aber große Strukturen können teurer sein als Klassenverweise (wenn sie in Methoden übergeben werden).

20voto

BC. Punkte 23018

Erstens: Interop-Szenarien oder wenn Sie das Speicherlayout angeben müssen

Zweitens: Wenn die Daten sowieso fast die gleiche Größe wie ein Referenzzeiger haben.

19voto

Roman Pokrovskij Punkte 8762

Ich habe einen kleinen Benchmark mit BenchmarkDotNet gemacht, um ein besseres Verständnis des Vorteils von "struct" in Zahlen zu bekommen. Ich teste das Durchlaufen von Arrays (oder Listen) von structs (oder Klassen). Das Erstellen dieser Arrays oder Listen liegt außerhalb des Umfangs des Benchmarks - es ist klar, dass eine "Klasse" mehr Gewicht hat, mehr Speicher verwendet und die Garbage Collection involviert.

Also die Schlussfolgerung lautet: Seien Sie vorsichtig mit LINQ und dem versteckten Boxing/Unboxing von structs und verwenden Sie structs für Mikro-Optimierungen streng auf Arrays beschränkt.

P.S. Ein weiterer Benchmark zum Übergeben von struct/class durch den Aufrufstapel befindet sich hier https://stackoverflow.com/a/47864451/506147

BenchmarkDotNet=v0.10.8, OS=Windows 10 Redstone 2 (10.0.15063)
Processor=Intel Core i5-2500K CPU 3.30GHz (Sandy Bridge), ProcessorCount=4
Frequency=3233542 Hz, Resolution=309.2584 ns, Timer=TSC
  [Host] : Clr 4.0.30319.42000, 64bit RyuJIT-v4.7.2101.1
  Clr    : Clr 4.0.30319.42000, 64bit RyuJIT-v4.7.2101.1
  Core   : .NET Core 4.6.25211.01, 64bit RyuJIT

          Method |  Job | Runtime |      Mean |     Error |    StdDev |       Min |       Max |    Median | Rank |  Gen 0 | Allocated |
---------------- |----- |-------- |----------:|----------:|----------:|----------:|----------:|----------:|-----:|-------:|----------:|
   TestListClass |  Clr |     Clr |  5.599 us | 0.0408 us | 0.0382 us |  5.561 us |  5.689 us |  5.583 us |    3 |      - |       0 B |
  TestArrayClass |  Clr |     Clr |  2.024 us | 0.0102 us | 0.0096 us |  2.011 us |  2.043 us |  2.022 us |    2 |      - |       0 B |
  TestListStruct |  Clr |     Clr |  8.427 us | 0.1983 us | 0.2204 us |  8.101 us |  9.007 us |  8.374 us |    5 |      - |       0 B |
 TestArrayStruct |  Clr |     Clr |  1.539 us | 0.0295 us | 0.0276 us |  1.502 us |  1.577 us |  1.537 us |    1 |      - |       0 B |
   TestLinqClass |  Clr |     Clr | 13.117 us | 0.1007 us | 0.0892 us | 13.007 us | 13.301 us | 13.089 us |    7 | 0.0153 |      80 B |
  TestLinqStruct |  Clr |     Clr | 28.676 us | 0.1837 us | 0.1534 us | 28.441 us | 28.957 us | 28.660 us |    9 |      - |      96 B |
   TestListClass | Core |    Core |  5.747 us | 0.1147 us | 0.1275 us |  5.567 us |  5.945 us |  5.756 us |    4 |      - |       0 B |
  TestArrayClass | Core |    Core |  2.023 us | 0.0299 us | 0.0279 us |  1.990 us |  2.069 us |  2.013 us |    2 |      - |       0 B |
  TestListStruct | Core |    Core |  8.753 us | 0.1659 us | 0.1910 us |  8.498 us |  9.110 us |  8.670 us |    6 |      - |       0 B |
 TestArrayStruct | Core |    Core |  1.552 us | 0.0307 us | 0.0377 us |  1.496 us |  1.618 us |  1.552 us |    1 |      - |       0 B |
   TestLinqClass | Core |    Core | 14.286 us | 0.2430 us | 0.2273 us | 13.956 us | 14.678 us | 14.313 us |    8 | 0.0153 |      72 B |
  TestLinqStruct | Core |    Core | 30.121 us | 0.5941 us | 0.5835 us | 28.928 us | 30.909 us | 30.153 us |   10 |      - |      88 B |

Code:

[RankColumn, MinColumn, MaxColumn, StdDevColumn, MedianColumn]
    [ClrJob, CoreJob]
    [HtmlExporter, MarkdownExporter]
    [MemoryDiagnoser]
    public class BenchmarkRef
    {
        public class C1
        {
            public string Text1;
            public string Text2;
            public string Text3;
        }

        public struct S1
        {
            public string Text1;
            public string Text2;
            public string Text3;
        }

        List testListClass = new List();
        List testListStruct = new List();
        C1[] testArrayClass;
        S1[] testArrayStruct;
        public BenchmarkRef()
        {
            for(int i=0;i<1000;i++)
            {
                testListClass.Add(new C1  { Text1= i.ToString(), Text2=null, Text3= i.ToString() });
                testListStruct.Add(new S1 { Text1 = i.ToString(), Text2 = null, Text3 = i.ToString() });
            }
            testArrayClass = testListClass.ToArray();
            testArrayStruct = testListStruct.ToArray();
        }

        [Benchmark]
        public int TestListClass()
        {
            var x = 0;
            foreach(var i in testListClass)
            {
                x += i.Text1.Length + i.Text3.Length;
            }
            return x;
        }

        [Benchmark]
        public int TestArrayClass()
        {
            var x = 0;
            foreach (var i in testArrayClass)
            {
                x += i.Text1.Length + i.Text3.Length;
            }
            return x;
        }

        [Benchmark]
        public int TestListStruct()
        {
            var x = 0;
            foreach (var i in testListStruct)
            {
                x += i.Text1.Length + i.Text3.Length;
            }
            return x;
        }

        [Benchmark]
        public int TestArrayStruct()
        {
            var x = 0;
            foreach (var i in testArrayStruct)
            {
                x += i.Text1.Length + i.Text3.Length;
            }
            return x;
        }

        [Benchmark]
        public int TestLinqClass()
        {
            var x = testListClass.Select(i=> i.Text1.Length + i.Text3.Length).Sum();
            return x;
        }

        [Benchmark]
        public int TestLinqStruct()
        {
            var x = testListStruct.Select(i => i.Text1.Length + i.Text3.Length).Sum();
            return x;
        }
    }

0 Stimmen

Hast du herausgefunden, warum Strukturen so viel langsamer sind, wenn sie in Listen und ähnlichem verwendet werden? Liegt es an dem versteckten Boxing und Unboxing, von dem du gesprochen hast? Wenn ja, warum passiert das?

0 Stimmen

Der Zugriff auf ein Strukt in einem Array sollte schneller sein, einfach weil keine zusätzlichen Verweise erforderlich sind. Boxen/Unboxen ist ein Fall für Linq.

CodeJaeger.com

CodeJaeger ist eine Gemeinschaft für Programmierer, die täglich Hilfe erhalten..
Wir haben viele Inhalte, und Sie können auch Ihre eigenen Fragen stellen oder die Fragen anderer Leute lösen.

Powered by:

X