C# Read/Write Methode Object (XML Serialization)

Fonce

Captain
Registriert
Feb. 2006
Beiträge
3.403
Hi,
ich bin derzeit dabei mich mal wieder ein wenig mit C# zu befassen. Genau genommen mit der Serialization von Objecten.
Da bin ich auf ein kleines problem gestoßen bei dem ich nicht weiß wieso.

Zum einen habe ich mir diese Klasse geschrieben um mir später schreibarbeit zu ersparen.
Code:
public class ConfigSerializer<T>
        {
            public ConfigSerializer()
            {                
            }

            public static bool Serialize(T config, string path)
            {
                try
                {
                    FileStream stream = new FileStream(@path, FileMode.Create);
                    XmlSerializer serializer = new XmlSerializer(typeof(T));
                    serializer.Serialize(stream, config);
                    stream.Close();
                    return true;
                }
                catch (Exception e)
                {
                    return false;
                }
            }

            public static bool Deserialize(T config, string path)
            {
                try
                {
                    FileStream stream = new FileStream(@path, FileMode.Open);
                    XmlSerializer serializer = new XmlSerializer(typeof(T));
                    config = (T)serializer.Deserialize(stream);
                    stream.Close();
                    return true;
                }catch(Exception e){
                    return false;
                }
            }
        }
Diese funktioniert auch wunderbar.

Dann habe ich hier diese Klasse in der Profile gespeichert werden sollen.(Ist noch im Testzustand also nicht wundern :p )
Code:
public class ConvertProfile
        {
            public string InFileExt { get; set; }
            public string OutFileExt { get; set; }
            public string ProfileName { get; set; }
            public List<string> test { get; set; }

            public ConvertProfile(string ProfileName)
            {
                test = new List<string>();
                this.ProfileName = ProfileName;
            }

            public ConvertProfile(ConvertProfile convertProfile)
            {
                test = new List<string>(convertProfile.test);
                this.ProfileName = convertProfile.ProfileName;
                this.InFileExt = convertProfile.InFileExt;
                this.OutFileExt = convertProfile.OutFileExt;
            }

            public ConvertProfile(){
                test = new List<string>();
            }

            public bool Write(string path)
            {
                return ConfigSerializer<ConvertProfile>.Serialize(this, path);
            }

            public bool Read(string path)
            {
               return ConfigSerializer<ConvertProfile>.Deserialize(this, path);
            }
        }

Das Problem habe ich nun mit der Methode Read(Die Write Methode funktioniert so wie gewünscht :) ). Diese funktioniert einfach nicht, die Member der Klasse bleiben hier einfach NULL.

Hat zufällig jemand eine Idee?
 
Der Grund ist einfach...
Zeile 29 im oberen Code: der Deserialisierer liefert eine neue Instanz der Klasse zurück. Innerhalb der Methode verlierst du damit den Zugriff auf das Objekt, das du ändern möchtest, den du biegst da den Wert der Variable config um. Diese enthält ab dort eine neue Instanz und nicht mehr das Objekt, das du eigentlich mit Werten befüllen willst.
Du willst aber die bereits existierende Instanz ändern -> geht nicht auf diese Weise.
Du könntest allerdings die Datei deserialisieren in einem temporären Objekt und dann alle Werte auf config setzen... damit verlierst du allerdings den generischen Ansatz... ergo mach die Serialsisierungsfunktion (zumindest zum Teil) direkt in die Klasse rein, indem z.B. read von ConfigSerializer die gelesene Instanz zurückliefert.

Btw: Den Erfolg/das Scheitern einer Method als Boolean-Rückgabewert zu machen ist nicht unbedingt guter Stil, Exception-Handling wurde nicht grundlos eingeführt.
 
Zuletzt bearbeitet:
Ja stimmt, total übersehen. ;)

Mit dem Rückgabewert hast natürlich recht, war jetzt auch nur der erste Entwurf bei dem dann natürlich noch Exception-Handling implementiert werden muss. Werde hierfür aber noch eigene Exceptions schreiben, deswegen hatte ich das erstal weggelassen. ;)
 
Btw: Ich kann nur dringend davon abraten Serialisierung zu benutzen, um Daten in Dateien zu speichern.
 
Man könnte das Template doch auch als Referenz übergeben. Dann wäre das Problem gelöst. Ob C# da Probs macht, keine Ahnung. Nur so ne Idee.
 
@Sub-N-Casbar
Und was sollte ich stattdessen benutzen?

Ich hab wie gesagt Objekte(Bestehend aus string, int, double und List mehr nicht) in der Profile gespeichert sind. Diese sollen im XML Format gespeichert werden und dann später wieder ausgelesen.

@Hellsfoul
War dann auch mein gedanke, aber da in C# this nicht als referenz übergeben werden kann...
 
"Normale" Speicher- und Lade-Funktionen z.B. mit dem Xml-DOM. Wenn Du Serialisierung benutzt, kannst Du die Daten nur lesen, wenn Du die passende Klasse dazu hast. Das kann fies werden, wenn sich die Datenablage mal ändert.
 
Also was ich bzgl. Serialisierung empfehlen würde, wäre auf jeden Fall wenigstens eine separate Klasse zu machen, die sich nur darum kümmert. So wie es für mich aussieht, sind diese Profile allerdings schon sowas.

Natürlich gibt es bei Serialisierung Probleme, wenn sich die serialisierten Klassen ändern. Aber je nach Anwendungszweck kann es auch komplett egal sein...

@Hellsfoul: Das Template als Referenz übergeben? oO
Du meinst T? T ist kein Template. Grundsätzlich kann man in C# Referenz-Parameter machen, aber schau dir doch mal den Code an.
Wie willst du this als Referenz-Parameter übergeben? Um zu erkennen, dass das nicht geht, muss ich nicht mal Visual Studio starten... Unabhängig von der Sprache macht das wohl kaum Sinn...
 
Zuletzt bearbeitet:
@Sub-N-Casbar
Die Dateien werden nur gelesen, verarbeitet und einmal zurückgeschrieben.

@ 1668mib
In Java wäre das z.B. kein Problem, da auch this nur eine Referenz ist ;)
Aber wie gesagt, in C# kann this nicht als Referenz übergeben werden.
 
In Java kein Problem? Im Gegenteil.
In Java gibt es gar keine Referenz-Parameter. Jeder Parameter wird Call-By-Value übergeben. Bei Objekten sind das eben die Adressen, die kopiert werden. Und schon hast du genau das Verhalten von deinem read oben. Du weist config einen Wert zu, der Aufrufer bekommt aber nichts mit.
 
Nein, bei Java wird, außer bei den primitiven Typen wie int, double, usw.(die sind Call-By-Value), immer Call-By-Reference übergeben.
Nimm dir doch mal zwei Threads denen du beiden das gleiche Objekt übergibst und änder dann was an dem Objekt, du wirst feststellen, das sie die Änderung in dem einen Thread auch auf den anderen Thread auswirkt, den beide arbeiten mit dem gleichen Objekt!
 
Call-By-Value bei Referenztypen (in C# und Java) sieht so aus, dass die Referenz bei der Übergabe kopiert wird (hat @1668mib auch schon erklärt). Du hast dann also zwei Referenzen, die auf das selbe Objekt zeigen. Deshalb wirken sich zwar Änderungen am Objekt selbst auf den Aufrufer aus, eine Änderung der Referenz aber nicht, weil diese eben By-Value übergeben wurde. In Java könntest du das also überhaupt nicht realisieren.

Die einzig wahre Lösung ist auch meiner Meinung nach das deserialisierte Objekt als Return-Wert zu benutzen.
 
Fonce, es ist eben genau nicht Call-by-reference, sondern wie 1668mib sagte, call-by-value.

Kopiert wird die Referenz, nicht das Objekt. Nach der Übergabe an eine Methode hast du also plötzlich zwei Referenzen auf das selbe Objekt, da fand die Kopie statt.

Davon abgesehen, dass Threads mit der Thematik nichts zu tun haben (ich denke, das hast du nur exemplarisch angeführt): setze doch mal an eine Methode übergebene Referenz (umgangssprachlich: Objekt) auf null. Ist dadurch das Objekt auch für die original-Referenz plötzlich null? Nein. Das Objekt ist außerhalb der Methode nach wie vor referenziert wie davor auch.

Du kannst innerhalb der Methode über die Referenz zwar auf dem Objekt arbeiten, aber du kannst die Referenz selbst nicht so umbiegen, dass das außerhalb der Methode etwas verändern würde, weil es eben nicht die original-Referenz ist.
 
Zuletzt bearbeitet:
@Fonce
Da wirfst du aber was durcheinander!

Sowohl in Java, als auch in C# gibts Primitive Datentypen/Value Types (int, float, double usw.) und Komplexe Datentypen/Reference Types (erben von Object).
Was diese Datentypen unterscheidet, solltest du wissen denke ich.

Mit der Parameterübergabe hat das aber nichts zu tun.
Denn Parameter werden sowohl in Java, als auch in C# standardmäßig per call by value übergeben!

In C# ist auch zusätzlich call by reference möglich. Dazu gibts die beiden Keywords ref und out. In Java geht das nicht.


Kleines Beispiel in Java:
Code:
public class Bar
{
	public static class Foo
	{
		public int number;

		public Foo(int number)
		{
			this.number = number;
		}

		public void load()
		{
			uselessMethod(this);
		}
	}

	private static void uselessMethod(Foo f)
	{
		f = new Foo(12345);
	}
	
	public static void main(String args[]){
		Foo bar = new Foo(1);		
		bar.load();
		
		System.out.println(bar.number);
	}
}
Das (etwas hässliche) Beispiel macht so ziemlich den gleichen Fehler, den du auch in deinem C# Code gemacht hast.
Und natürlich funktioniert das auch in Java nicht.

Denn genauso wie in C# überschreibt hier in Java die uselessMethod die Kopie der Referenz auf das Bar-Objekt das ihr übergeben wird.

Kleine Erläuterung was im Beispiel passiert:
Foo ist ein reference type.
Wenn also die Variable bar deklariert und initialisiert wird, dann wird kein Objekt darin gespeichert, sondern nur eine Referenz, die auf das Objekt verweist.

Wenn du nun bar an die Methode uselessMethod übergibst, dann wird eine Kopie von bar erstellt (call by value).
Innerhalb der Methode kannst du also nur auf eine Kopie der Referenz auf das Foo-Objekt zugreifen.

Im Endeffekt hast du im Speicher also folgendes:
Das Objekt vom Typ Foo.
Eine Referenz auf das Foo-Objekt (bar-Variable)
Noch eine Referenz auf das Foo-Objekt (f-Parameter)

Da beide Referenzen auf das gleiche Objekt verweisen, wird natürlich beim Zugriff sowohl über bar, als auch über f das gleiche Objekt manipuliert.
Wenn du allerdings f etwas anderes zuweist (eine Referenz auf ein neues Objekt), dann juckt das bar überhaupt nicht und vice versa.


Ich hoffe das hilft dir etwas, deinen Denkfehler zu verstehen :)

Edit:
Da war ich ja ziemlich langsam mit meiner Antwort...
 
Zuletzt bearbeitet:
Das Call-By-Value-Verhalten zeigt ja schon dieses einfache Beispiel:

Code:
public class Main {
	
	public static void changeString(String s)
	{
		System.out.println("changeString #1: " + s);
		s = "Neuer Text";
		System.out.println("changeString #2: " + s);
	}

	public static void main(String[] args) {
		String s = "Alter Text";
		changeString(s);
		System.out.println(s);
	}

}

Auch wenn man sich von Zeigerarithmetik (z.B. aus C) in heutigen Sprachen wegbewegt, hilft das Wissen dahinter dennoch ein wenig, um Themen wie Parameterübergabe zu verstehen. In Sprachen, in denen es nur noch dynamische Objekte gibt, vergisst oder übersieht man halt all zu leicht, dass die Variable eigentlich nichts anderes ist als ein Zeiger...
 
Bei referenzübergabe kann man die referenz bearbeiten.
Machst du allerdings soetwas: referenz = new ComplexDataType();
Verweißt du auf ein neues Object => du bearbeitest nichtmehr auf dem gleichen object wie das welches du dachtest dass du übergibst.

Willst du das machen musst du das keyword ref hinzufügen.

Hier als Beispiel:

Code:
    public class Dummy
    {
        public string text;
    }

    public class CallByReference
    {
        public static void Test()
        {
            Dummy d2 = new Dummy();
            d2.text = "haha";
            Change(d2);
            Debug.WriteLine(d2.text);

            Dummy d = new Dummy();
            d.text = "haha";
            ChangeByRef(ref d);
            Debug.Write(d.text);
        }

        private void Change(Dummy d)
        {
            d = new Dummy();
            d.text = "nichts zu lachen"; 
        }

        private void ChangeByRef(ref Dummy t)
        {
            t = new Dummy();
            t.text = "Geändert";
        }
}

Ausgabe:
haha
Geändert
 
Zuletzt bearbeitet von einem Moderator:
Zurück
Oben