Laden...

Verständnisfrage zu Structs mit Minimalbeispiel

Erstellt von Flyget vor einem Jahr Letzter Beitrag vor einem Jahr 396 Views
F
Flyget Themenstarter:in
2 Beiträge seit 2022
vor einem Jahr
Verständnisfrage zu Structs mit Minimalbeispiel

Hallo Zusammen,
ich bin relativ frisch was das c# Programme angeht, und habe ein Phänomen das mir nicht ganz klar ist.

Prinzipiell geht es darum eine Datei einzulesen und deren Inhalt in eine Struktur zu laden. Später soll der Inhalt dieser Struktur weiter verarbeitet werden. Mein Problem findet aber schon beim einlesen statt.

Beim Einlesen der Datei im Form Load Ereignis soll die Datei Zeile für Zeile in die Struktur "presets_collection_1" sortiert werden. Die Struktur besteht dabei aus 16 weiteren identischen Strukturen. Nun dachte ich, Strukturen wären Werte-Typen und damit sollten diese 16 einzelnen Strukturen unabhängig voneinander sein. Dem scheint aber zumindest in diesem Fall nicht so zu sein, bzw. ich habe irgendwo sonst einen Denkfehler.

Im Minimalbeispiel sollen zwei Presets aus der presets.txt eingelesen werden. Eigentlich erwarte ich dass anschließend folgendes in der Struktur preset_collection_1 steht:


preset_collection_1.Presets[0].Door_measurements[0] = 2
preset_collection_1.Presets[0].Door_measurements[1] = 1
preset_collection_1.Presets[0].Door_measurements[2] = 4
preset_collection_1.Presets[0].Belt_buckle_measurements[0] = 1
preset_collection_1.Presets[0].Belt_buckle_measurements[1] = 9
preset_collection_1.Presets[0].Belt_buckle_measurements[2] = 3

sowie


preset_collection_1.Presets[1].Door_measurements[0] = 3
preset_collection_1.Presets[1].Door_measurements[1] = 4
preset_collection_1.Presets[1].Door_measurements[2] = 5
preset_collection_1.Presets[1].Belt_buckle_measurements[0] = 2
preset_collection_1.Presets[1].Belt_buckle_measurements[1] = 3
preset_collection_1.Presets[1].Belt_buckle_measurements[2] = 1
preset_collection_1.Presets[1].Belt_buckle_measurements[2] = 7

jedoch sieht es so aus, dass in allen Presets preset_collection_1.Presets[0-15] der Inhalt aus dem zweiten Preset steht:


preset_collection_1.Presets[0-15].Door_measurements[0] = 3
preset_collection_1.Presets[0-15].Door_measurements[1] = 4
preset_collection_1.Presets[0-15].Door_measurements[2] = 5
preset_collection_1.Presets[0-15].Belt_buckle_measurements[0] = 2
preset_collection_1.Presets[0-15].Belt_buckle_measurements[1] = 3
preset_collection_1.Presets[0-15].Belt_buckle_measurements[2] = 1
preset_collection_1.Presets[0-15].Belt_buckle_measurements[2] = 7

Code:


namespace struct_test
{
	public partial class Form1 : Form
	{
		// Globale Variablen
		DialogResult result;

		public Form1()
		{
			InitializeComponent();
		}

		static T[] CreateArray<T>(int count, T value)
		{
			T[] ret = new T[count];
			for (int i = 0; i < count; i++)
			{
				ret[i] = value;
			}
			return ret;
		}

		public struct preset
		{
			public string Preset_name { get; set; }
			public string Test_number { get; set; }
			public string[] Door_measurements { get; set; }
			public string[] Belt_buckle_measurements { get; set; }

			public preset(string preset_name, string test_number, string[] door_measurements, string[] belt_buckle_measurements)
			{
				this.Preset_name = preset_name;
				this.Test_number = test_number;
				this.Door_measurements = door_measurements;
				this.Belt_buckle_measurements = belt_buckle_measurements;
			}
		}

		public struct preset_collection
		{
			public preset[] Presets { get; set; }

			public preset_collection(preset[] presets)
			{
				this.Presets = presets;
			}
		}

		preset_collection preset_collection_1 = new preset_collection
		{
			Presets = CreateArray<preset>(16, new preset { Preset_name = "", Test_number = "", Door_measurements = CreateArray<string>(9, ""), Belt_buckle_measurements = CreateArray<string>(9, "") })
		};


		private bool load_presets()
		{
			// Konfigurationsdatei laden oder durch Default-Datei ersetzen
			string current_config_file_path = Directory.GetCurrentDirectory() + "\\presets.txt";
			Encoding iso = Encoding.GetEncoding("ISO-8859-1");
			byte line_type = 0;
			byte preset_cnt = 0;
			byte measurement_cnt_door = 0;
			byte measurement_cnt_beltbuckle = 0;
			byte position = 0;

			if (File.Exists(current_config_file_path))
			{
				foreach (string line in File.ReadLines(current_config_file_path, iso))
				{
					if (line.StartsWith("#")) // Kommentarzeile -> ignorieren
					{

					}
					else
					{
						if (line.StartsWith("******")) // Neues Preset erstellen
						{
							line_type = 1;
							measurement_cnt_door = 0;
							measurement_cnt_beltbuckle = 0;
							preset_cnt += 1;

						}
						else if (line_type == 1) // Preset name
						{
							preset_collection_1.Presets[preset_cnt - 1].Preset_name = line;
							line_type = 2;
						}
						else if (line_type == 2) // Test number
						{
							preset_collection_1.Presets[preset_cnt - 1].Test_number = line;
							line_type = 3;
						}
						else if (line_type == 3) // Measurements
						{
							if (line.StartsWith("Door")) // Door measurements
							{
								if (Byte.TryParse(line.Substring(9, 1), out position))
								{
									preset_collection_1.Presets[preset_cnt - 1].Door_measurements[measurement_cnt_door] = position.ToString();
									measurement_cnt_door += 1;
								}
								else
									return false;
							}
							else if (line.StartsWith("Beltbuckle")) // Belt buckle measurements
							{
								if (Byte.TryParse(line.Substring(15, 1), out position))
								{
									preset_collection_1.Presets[preset_cnt - 1].Belt_buckle_measurements[measurement_cnt_beltbuckle] = position.ToString();
									measurement_cnt_beltbuckle += 1;
								}
								else
									return false;
							}
						}
					}
				}
			}
			return true;
		}

		private void Form1_Load(object sender, EventArgs e)
		{
			// Presets laden -> Falls Fehler, Program beenden oder ignorieren
			if (load_presets() == false)
			{
				result = MessageBox.Show("Fehler beim einlesen der presets", "Einlesefehler", MessageBoxButtons.YesNoCancel, MessageBoxIcon.Error);
				if (result == DialogResult.Yes) // Ignorieren von Fehler und nur korrekte Presets anzeigen
				{

				}
				else if ((result == DialogResult.No) | (result == DialogResult.Cancel) | (result == DialogResult.Abort)) // Program beenden
				{
					this.Close();
				}
			}
			else
			{
				// Combobox mit Presetnamen füllen
				for (byte i = 0; i < 16; i++)
				{
					if (!string.IsNullOrEmpty(preset_collection_1.Presets[i].Preset_name))
					{
						comboBox_attempt_presets.Items.Add(preset_collection_1.Presets[i].Preset_name);
					}
				}
			}
		}
	}
}


Wäre froh, wenn mich jemand etwas erleuchten könnte!
Vielen Dank,
Flyget

16.834 Beiträge seit 2008
vor einem Jahr

Die Struktur besteht dabei aus 16 weiteren identischen Strukturen.

Du hast kein struct mit 16 Stukuren, sondern ein struct, das ein Array beinhaltet.
Das Array hat eine Struktur als Typ, aber das Array selbst bleibt ein Referenztyp.

Dein CreateArray, das so in der Form kein Sinn macht, und eben für den Fehler verantwortlich ist, sorgt dafür, dass Du in 16 Presets Werte kopierst, aber das gleiche Array - weil die gleiche Referenz in alle Strukturen kopiert wird.
Alle Strukturen arbeiten damit also auf dem gleichen Array, Du überschreibst die Werte und daher kommt es zu dem Resultate.

Das struct als Container macht hier kein Sinn.
Auch mit 16 Werten in einer Struktur wäre das weit über das Ziel eines structs hinaus geschossen, gar kontraproduktiv, da dauernd immense Datenmengen hin und her kopiert werden würden.
Eine Klasse als Container wäre viel effizienter.

Und auch Deine Collection kannst komplett weglassen und mit nem Array oder einer Liste arbeiten.

PS: bitte keine CS Dateien anhängen, dafür gibts BBCodes. Hab den Inhalt eingefügt.
Das ist nicht der Sinn von Attachments.

PPS: es gibt Namensempfehlungen von C#, wie in jeder Sprache.
Du bist ganz weit weg davon 🙂
Richtlinien für die Benennung - Framework Design Guidelines

F
Flyget Themenstarter:in
2 Beiträge seit 2022
vor einem Jahr

Hallo Abt,
vielen Dank, das erleuchtet schonmal deutlich.

Wie würde man sowas denn möglichst strukturiert und lesbar als Klasse schreiben? Meine Idee mit dem Array war eben, dass ich da schön per Index zugreifen kann. Gibt objektorientiert aber sicher saubere Lösungen. Hier wäre ich noch über einen Tipp/Grundgerüst dankbar.

Viele Grüße,
Flyget

16.834 Beiträge seit 2008
vor einem Jahr

Ein struct sieht optisch nicht groß anders aus als eine Klasse, funktioniert aber eben anders.


public record class Preset(string Name, string TestNumber, string[] DoorMeasurements, string[] BeltBuckleMeasurements);

wäre Dein Preset als Klasse, geschrieben als Record.
C#-Tutorial: Verwenden von Datensatztypen
Ein Record erspart einem das erneute Schreiben von Eigenschaften.


public record class Preset(string Name)

ist das gleiche wie


public class Preset
{
   public string Name  {get;set;}
}

Deine Collection ist einfach


List<Preset> presets = new();

Und beim Parsen erzeugst Du eben neue Instanzen von Preset und fügst das der Liste hinzu.

Alle Objekte initial zu erzeugen und später die Werte zuweisen macht nur selten am Anfang sinn.
Bei structs wäre das in den meisten Fällen kontraproduktiv, weil das die Kopierlast steigert und damit langsamer macht.

structs sind in vielen Szenarien viel performanter als Klassen (Sustainable Code - Struct vs Class)
Aber wenn man structs falsch einsetzt und falsch programmiert, dann erreichst das Gegenteil.
Passender Tweet dazu von David Fowler heut:

When performance matters in languages that have a GC you have to:

  • Write efficient code to minimize allocations.
  • Learn how to tune the GC for your scenario.

Too many people blame GC pauses, GC's don't make the garbage[/url]

709 Beiträge seit 2008
vor einem Jahr
  
public record class Preset(string Name)  
  

ist das gleiche wie

  
public class Preset  
{  
   public string Name  {get;set;}  
}  
  

Ist das nicht eher das gleiche wie der folgende Code?


public class Preset
{
    public Preset (string name)
    {
        Name = name;
    }

    public string Name { get; }
}

Ich meine, dass in dem Fall die Properties readonly sind.

A
764 Beiträge seit 2007
vor einem Jahr

Genau genommen so:


public class Preset
{
   public string Name  { get; init; }
}

Es wird das neue init-Keyword verwendet.

https://docs.microsoft.com/en-us/dotnet/csharp/language-reference/builtin-types/record

4.939 Beiträge seit 2008
vor einem Jahr

Ja, bei einem (class) record-Typ werden automatisch ein passender Konstruktor sowie readonly-Eigenschaften erzeugt, nur bei record struct (ab C#10) sind diese ersteinmal auch veränderbar, solange man nicht explizit readonly record struct benutzt, s.a. records (C# reference) (gleich die obersten Beispiele).

PS: Ich habe extra den englischen Originalartikel (und nicht den deutschen) verlinkt, da im deutschen Datensätze (C#-Referenz) so tolle Übersetzungen wie stummgeschaltet (für mutable) enthalten sind (statt veränderbar).

A
764 Beiträge seit 2007
vor einem Jahr

PS: Ich habe extra den englischen Originalartikel (und nicht den deutschen) verlinkt, da im deutschen
>
so tolle Übersetzungen wie stummgeschaltet (für mutable) enthalten sind (statt veränderbar).

Ich habe bei mir ein Browser-Plugin eingerichtet, dass "de-DE" durch "en-US" ersetzt. xD