Benutzer mit den meisten Antworten
Zeitproblem beim Deserialisieren

Frage
-
Hallo,
ich habe ein Problem.
Das Deserialisieren benötigt zu viel Zeit. Ich weiß nicht warum. Sieht jemand etwas?
Mit using geht es etwas schneller, warum auch immer.
Lösung 1: Langsam
string Result = null; XmlSerializer Serializer = new XmlSerializer(typeof(T), string.Empty); MemoryStream MemoryStream = new MemoryStream(); MyXmlTextWriter TextWriter = new MyXmlTextWriter(MemoryStream, Encoding.ASCII); TextWriter.Indentation = 4; TextWriter.IndentChar = ' '; TextWriter.Formatting = Formatting.Indented; XmlSerializerNamespaces XmlNamespace = new XmlSerializerNamespaces(); XmlNamespace.Add(string.Empty, string.Empty); Serializer.Serialize(TextWriter, dataObject, XmlNamespace); Result = Encoding.UTF8.GetString(MemoryStream.ToArray()); TextWriter.Close(); MemoryStream.Close(); MemoryStream.Dispose(); return Result;
Lösung 2: Vermutlich schneller, aber da kommt folgende MeldungDie XML-Eingabe kann nicht angezeigt werden, wenn Stylesheet XSL verwendet wird. Beheben Sie den Fehler und klicken Sie dann auf Aktualisieren, oder wiederholen Sie den Vorgang späterXmlSerializerNamespaces namespaces = new XmlSerializerNamespaces(); namespaces.Add(string.Empty, string.Empty); string result = null; using (var ms = new MemoryStream()) { using (var writer = XmlWriter.Create(ms, WriterSettings)) { var serializer = new XmlSerializer(typeof(T)); serializer.Serialize(writer, dataObject, namespaces); result = Encoding.UTF8.GetString(ms.ToArray()); } } return result;
Das erste Zeichen ist da falsch. Das habe ich herausgefunden. Warum auch immer.
Viele Grüße Sandra
Antworten
-
Hi Sandra,
komisch, dass ein Beitrag von mir fehlt.Zu Deserialisieren brauchst Du:
1. einen Stream (Text), für dessen Beschaffung Zeit benötigt wird;
2. eine Instanz der XmlReader Klasse, wobei Du für die Instanziierung den Typ und Zeit benötigst;
3. Zeit für die eigentliche Deserialisierung, die bei mir bei 10000 Obejketn unter 1 ms liegt.
Bei mir braucht der zweite Punkte beträchtlich viel Zeit, weshalb mein Vorschlag war, die Instanz des XmlReaders vorfristig zu erstellen und wenn mehrfach deserialisiert werden soll, dann die gleiche Instanz wieder nutzen. Wenn es verschiedene Typen der zu deserialisierenden Objekte gibt, dann für jeden Zieltyp eine eigene Instanz erstellen und die Verweise in einem Dictionary ablegen mit Key=Zieltyp.
--
Viele Grüsse
Peter Fleischer (MVP, Partner)
Meine Homepage mit Tipps und Tricks
Kommas richtig setzen!
Schüler sagen, Lehrer haben es gut.
Schüler, sagen Lehrer, haben es gut- Als Antwort markiert Sandra Bauer Mittwoch, 27. April 2016 16:43
Alle Antworten
-
Hallo Sandra,
was heißt "benötigt zu viel Zeit" in deinem Fall? Wie lange dauert es denn? Wie komplex ist die zu serialisierende Objektstruktur? Wie viele Elemente werden serialisiert?
Arbeitest Du im Release- oder im Debugmodus? Letzteres bringt ggfs. schon einen Performanceeinbruch um den Faktor 5, 10, 20, ... je nach genauer Ausgestaltung.
Was ist zudem "MyXmlTextWriter"? Ich nehme an, ein von dir geschriebener bzw. abgeleiteter XmlTextWriter?
Ich würde mal alles mittracen und mir für die einzelnen Abschnitte die Laufzeiten ermitteln und dann schauen, welche Stelle so langsam ist.
Wo soll die genannte Fehlermeldung kommen? Beim Durchlaufen dieses Codes ja eher nicht, oder?
Gruß, Stefan
Microsoft MVP - Visual Developer ASP/ASP.NET
http://www.asp-solutions.de/ - Consulting, Development
http://www.aspnetzone.de/ - ASP.NET Zone, die ASP.NET Community
- Bearbeitet Stefan FalzModerator Montag, 18. April 2016 17:07
-
was heißt "benötigt zu viel Zeit" in deinem Fall? Wie lange dauert es denn? Wie komplex ist die zu serialisierende Objektstruktur? Wie viele Elemente werden serialisiert?
Arbeitest Du im Release- oder im Debugmodus? Letzteres bringt ggfs. schon einen Performanceeinbruch um den Faktor 5, 10, 20, ... je nach genauer Ausgestaltung.
Was ist zudem "MyXmlTextWriter"? Ich nehme an, ein von dir geschriebener bzw. abgeleiteter XmlTextWriter?
Hallo Stefan,
Das Deserialisieren benötigt 70 bis 100 ms. Das ist zuviel. _Die Struktur ist nicht so komplex. 4 Klassen und eine Liste mit 10 Einträge, also recht klein.
Im Release auch, Debug ist in der Tat noch langsamer.
MyXmlTextWriter ist abgeleitet, ja.
Hast Du eine Idee. Das ursprünglich war auslesen, parsen mit C++, ging schneller.
Jetzt deserialisiere ich es, wegen den Objekten, einfacher, aber zeitintensiv.
Wie würdest Du es denn machen. Lt. Internet mit using sei es schneller.
Viele Grüße Sandra
-
Hallo Sandra,
führ bitte eine Performancemessung durch und schau, wo genau das Zeitproblem auftritt. XML Serialisierung gehört nicht zu den schnellsten Sachen aber 10 Objekte in einer Liste sollten etwas schneller gehen. Ich würde allerdings eher auf deine eigene XmlTextWriter Klasse tippen.
Falls klar ist, wo genau das Performanceproblem herkommt, poste bitte die Details und den entsprechenden Code hier.
Gruß, Stefan
Microsoft MVP - Visual Developer ASP/ASP.NET
http://www.asp-solutions.de/ - Consulting, Development
http://www.aspnetzone.de/ - ASP.NET Zone, die ASP.NET Community -
Hi Sandra,
das Serialisieren von 10000 Objekten dauern in meiner Umgebung ca. 20 ms, das Desrialisieren dauert unter 1 ms. Hier mal meine Demo:using System; using System.Collections.Generic; using System.Diagnostics; using System.IO; using System.Text; using System.Xml.Serialization; namespace ConsoleApplication1CS { public class Program18 { static void Main(string[] args) { Stopwatch sw = new Stopwatch(); Demo18 d = new Demo18(); sw.Start(); d.Ser(); Console.WriteLine($"Zeit Serialisierung: {sw.ElapsedMilliseconds} ms"); sw.Reset(); d.Deser(); Console.WriteLine($"Zeit Deserialisierung: {sw.ElapsedMilliseconds} ms"); Console.ReadKey(); } public class Demo18 { XmlSerializer ser = new XmlSerializer(typeof(List<B>)); StringBuilder sb = new StringBuilder(); public List<B> Liste { get; set; } = new List<B>(); public Demo18() { // Objekt erzeugen for (int i = 1; i < 100; i++) { B b1 = new B() { ID = i }; Liste.Add(b1); for (int k = 0; k < 100; k++) b1.Liste.Add(new C() { Info = $"Zeile {i} {k}" }); } } public void Ser() { using (StringWriter wrt = new StringWriter(sb)) { ser.Serialize(wrt, Liste); } } public void Deser() { using (StringReader rdr = new StringReader(sb.ToString())) { var res = ser.Deserialize(rdr); } } } public class B { public int ID { get; set; } public List<C> Liste { get; set; } = new List<C>(); } public class C { public string Info { get; set; } } } }
--
Viele Grüsse
Peter Fleischer (MVP, Partner)
Meine Homepage mit Tipps und Tricks
Kommas richtig setzen!
Schüler sagen, Lehrer haben es gut.
Schüler, sagen Lehrer, haben es gut -
Hallo,
@Peter,
evtl. liegt es dann an XML.
Kann mir jemand sagen, warum beim Deserialisieren, das erste Zeichen nicht korrekt ist?
Liegt es an UTF8. Ich habe schon alle Varianten getestet. Geht alles nicht.
UTF8 scheint noch am besten zu sein.
3 Probleme
A) Erstes Zeichen
B) File deserialisieren, Zeit ok, Umsetzung korrekt, mit der Bitte um Bestätigung, wie Ihr das macht.
C) string deserialisieren benötigt Zeit, ich sehe es nicht.
vielleicht geht es systembedingt auch nicht.
Viele Grüße Sandra und Danke für Eure Unterstützung
Anlage Code, hoffe nicht zu unübersichtlich ;-)public static string SerializeObject<T>(T dataObject) { string result = null; //this avoids xml document declaration XmlWriterSettings settings = new XmlWriterSettings() { Indent = true, OmitXmlDeclaration = true }; var stream = new MemoryStream(); using (XmlWriter xw = XmlWriter.Create(stream, settings)) { //this avoids xml namespace declaration XmlSerializerNamespaces ns = new XmlSerializerNamespaces( new[] { XmlQualifiedName.Empty }); XmlSerializer x = new XmlSerializer(typeof(T), ""); x.Serialize(xw, dataObject, ns); } result = Encoding.UTF8.GetString(stream.ToArray()); char[] forTest = result.ToCharArray(); if (forTest[0] != ('<')) // Notlösung, denke ja nicht ganz korrekt. result = result.Substring(1, result.Length - 1); return result; // ---------------------------------- // TextReader benötigt mehr Zeit. // Ein File zu deserialisieren geht mit using relativ schnell, akzeptabel. object locking = new object(); lock (locking) { Logging logger = new Logging(); logger.Folder = "C:\\Log\\"; logger.WriteLogTest("Start " + FileName); T result = default(T); XmlSerializer Serializer = new XmlSerializer(typeof(T), string.Empty); //FileStream FileStream = new FileStream(FileName, FileMode.Open, FileAccess.Read); //, FileShare.None); //XmlTextReader TextReader = new XmlTextReader(FileStream); //result = (T)Serializer.Deserialize(TextReader); //TextReader.Close(); //FileStream.Close(); //FileStream.Dispose(); XDocument xDoc = new XDocument(); xDoc = XDocument.Load(FileName); using (var reader = xDoc.CreateReader()) { if (Serializer.CanDeserialize(reader)) { result = (T)Serializer.Deserialize(reader); } } logger.WriteLogTest("End " + FileName); return result; } // ---------------------------------- // Ein string zu deserialisieren benötigt viel Zeit, bis zu 100ms public static T DeserializeString<T>(string XmlContent) { T result = default(T); string XmlContentToParse = null; if (XmlContent.StartsWith("?")) { XmlContentToParse = XmlContent.Substring(1); } else { XmlContentToParse = XmlContent; } Logging logger = new Logging(); logger.Folder = "C:\\Log\\"; byte[] bufXML = ASCIIEncoding.UTF8.GetBytes(XmlContentToParse); XmlSerializer serializer = new XmlSerializer(typeof(T), string.Empty); // ** Problem, ist die Zeit using (var ms = new MemoryStream(bufXML)) { //xDoc = XDocument.Load(ms); //xDoc = XDocument.Parse(XmlContentToParse); //using (XmlReader reader = new XmlTextReader(ms)) //{ logger.WriteLogTest("StartDes " + XmlContent); result = (T)serializer.Deserialize(ms); // } //using (var reader = xDoc.CreateReader()) //{ // if (serializer.CanDeserialize(reader)) // { // result = (T)serializer.Deserialize(reader); // } //} } logger.WriteLogTest("EndDes " + XmlContent); //XmlSerializer Serializer = new XmlSerializer(typeof(T)); //StringReader StringReader = new StringReader(XmlContentToParse); //Result = (T)Serializer.Deserialize(StringReader); //StringReader.Close(); //StringReader.Dispose(); return result; } // ################################################################################### // JETZT MIT Logging <!-- 19-04-2016 10:50:51:693 --> Start MemoryStream <!-- 19-04-2016 10:50:51:695 --> Start <!-- 19-04-2016 10:50:51:733 --> End public static T DeserializeString<T>(string XmlContent) { T result = default(T); string XmlContentToParse = null; if (XmlContent.StartsWith("?")) { XmlContentToParse = XmlContent.Substring(1); } else { XmlContentToParse = XmlContent; } Logging logger = new Logging(); logger.Folder = "C:\\Log\\"; byte[] bufXML = ASCIIEncoding.UTF8.GetBytes(XmlContentToParse); XmlSerializer serializer = new XmlSerializer(typeof(T), string.Empty); logger.WriteLogTest("Start MemoryStream "); // ** Problem, ist die Zeit using (var ms = new MemoryStream(bufXML)) { XDocument xDoc = new XDocument(); xDoc = XDocument.Load(ms); //xDoc = XDocument.Parse(XmlContentToParse); logger.WriteLogTest("Start "); // result = (T)serializer.Deserialize(ms); // würde evtl. auch ohne XDocument reichen 19.04.2016 using (var reader = xDoc.CreateReader()) { if (serializer.CanDeserialize(reader)) { result = (T)serializer.Deserialize(reader); } } } logger.WriteLogTest("End"); //XmlSerializer Serializer = new XmlSerializer(typeof(T)); //StringReader StringReader = new StringReader(XmlContentToParse); //Result = (T)Serializer.Deserialize(StringReader); //StringReader.Close(); //StringReader.Dispose(); return result; }
-
Hi Sandra,
beim Deserialisieren bekommst Du eine Zeichenkette. Schau mal mit einem Hex-Editor, was da als erstes Zeichen kommt. Vielleicht ist es der BOM, der nicht richtig behandelt wurde.Wie ich das Deserialisieren umsetze, habe ich im Beispiel gezeigt. Es dauert bei mir für 10000 Objekte weniger als eine Millisekunde. Zeitaufwändig sind die Instanziierungen der dafür benötigten Objekte. Deshalb ist es sinnvoll, dieses Instanziierungen bereits vor der Deserialisierung durchzuführen (z.B. im Konstruktor) und dann erst am Ende wieder zu entsorgen (z.B. im Dispose).
--
Viele Grüsse
Peter Fleischer (MVP, Partner)
Meine Homepage mit Tipps und Tricks
Kommas richtig setzen!
Schüler sagen, Lehrer haben es gut.
Schüler, sagen Lehrer, haben es gut -
Hallo Peter,
ja nett ;-) BOM war ein gutes Stichwort.
Wußte ich nicht.
So wie ich es sehe nichts Schlimmes, einfach weglassen.
Byte Order Marks (BOM)var serializer = new XmlSerializer(typeof(Xsd.MESSAGE)); Encoding utf8EncodingWithNoByteOrderMark = new UTF8Encoding(false); XmlTextWriter xtw = new XmlTextWriter(stream, utf8EncodingWithNoByteOrderMark); serializer.Serialize(xtw, response); string xml = Encoding.UTF8.GetString(stream.ToArray());
Muss man auch wieder wissen. ------------------------------- Wie meinst Du das konkret. using (var reader = xDoc.CreateReader()) { if (serializer.CanDeserialize(reader)) { result = (T)serializer.Deserialize(reader); } }
Die zu instanziierende Klasse vorher mit new anzulegen bringt ja nichts,
weil der Deserialize eh alles wieder neu macht.
Keine Frage, was auffällt sind sicherlich, ich habe eine Klasse, diese Unterklassen beinhaltet.
Evtl. geht da die Zeit verloren.Grüße Sandra- Bearbeitet Sandra Bauer Mittwoch, 20. April 2016 19:11
-
Hi Sandra,
warum Deserialize alles neu macht, ist unklar. Wenn Du verschiedene "T" nutzt, dann nimm ein Dictionary und speichere dort die Instanzen für die unterschiedlichen Typen ab. Bei Aufruf wählst Du dann die Instanz des gewünschten Typs. Das Dictionary kannst Du ja bei der ersten Anforderung füllen.--
Viele Grüsse
Peter Fleischer (MVP, Partner)
Meine Homepage mit Tipps und Tricks
Kommas richtig setzen!
Schüler sagen, Lehrer haben es gut.
Schüler, sagen Lehrer, haben es gut -
Wenn Du verschiedene "T" nutzt, dann nimm ein Dictionary und speichere dort die Instanzen für die unterschiedlichen Typen ab. Bei Aufruf wählst Du dann die Instanz des gewünschten Typs. Das Dictionary kannst Du ja bei der ersten Anforderung füllen.
--
Hallo Peter,
>warum Deserialize alles neu macht, ist unklar.
wie meinst Du das? Mir oder Dir oder überhaupt? ;-)
Mir ist es unklar, ja. Wie meinst Du das mit dem Dictionary genau. Ja ich habe mir eine Hilfsklasse (Vorlage Stefan mit T erstellt)
Das müsstest genauer beschreiben. Danke jetzt schon.
Grüße Sandra
-
HI Sandra,
Du hast geschrieben: "Die zu instanziierende Klasse vorher mit new anzulegen bringt ja nichts, weil der Deserialize eh alles wieder neu macht.". Unklar ist mir, was Du damit aussagen willst. Instanzen zu unterschiedlichen Typen kann man in einem Dictionary sammeln und damit auf eine erneute Initiierung im Wiederholungsfalle verzichten, um damit Zeit zu sparen.--
Viele Grüsse
Peter Fleischer (MVP, Partner)
Meine Homepage mit Tipps und Tricks
Kommas richtig setzen!
Schüler sagen, Lehrer haben es gut.
Schüler, sagen Lehrer, haben es gut -
Hallo Peter,das Deserialisieren funktioniert mit new als auch ohne new im Konstruktor.
ReqPa = new PanReq.Root();
Das vorherige Anlegen bringt nichts, eher doppelt und der GC muss es aufräumen.ReqPa = HelperTel.DeserializeString<PanReq.Root>(TelegramFromScada); [Serializable] public class Root { // ATTRIBUTES [XmlAttribute("info")] public string info { get; set; } // ELEMENTS [XmlElement("PA_REQ")] public PA_REQ PaReq { get; set; } [XmlElement("CUSTOM")] public CUSTOM Custom { get; set; } // CONSTRUCTOR public Root() { PaReq = new PA_REQ(); Custom = new CUSTOM(); } ~Root() { } }
Ich denke jetzt reden wir wieder vom gleichen.
Vielleicht könntest Du die Dipose Variante aufzeigen, wie Du das konkret meinst.
Wenn alle Stricke nicht gehen, denke ich muss ich evtl. mit XDocument (LinQ) die XML
Struktur parsen. Fehlt mir auch die Erfahrung, ob das wirklich schneller ist.
Weiß Du es?Anlage Ansatz Dispose ? wie macht man es korrekt?
Viele Grüße Sandra
public class MySerial : IDisposable /// <summary> /// Finalizer /// </summary> ~MySerial() { Dispose(); } /// <summary> /// Dispose this instance. /// </summary> public void Dispose() { // Full dispose Dispose(true); // Suppress finalize GC.SuppressFinalize(this); }
-
Hi Sandra,
mit der Instanziierung (new beim Erzeugen der Klasse mit dem XmlReader) soll die Instanziierung des Readers ausgeführt werden, was einige Zeit in Anspruch nehmen kann. Der eigentliche Deserialisierungs-Prozess dauert bei mir für 10000 Objekte weniger als eine Millisekunde.Zum Dispose-Pattern kann man das Internet konsultieren, z.B. hier.
--
Viele Grüsse
Peter Fleischer (MVP, Partner)
Meine Homepage mit Tipps und Tricks
Kommas richtig setzen!
Schüler sagen, Lehrer haben es gut.
Schüler, sagen Lehrer, haben es gut -
Hi Sandra,
ich habe etwas den Überblick verloren. Zum Deserialisieren benötigst Du mehrere Objekte, deren Initiierung Zeit benötigt. Wo hats Du da noch Probleme?--
Viele Grüsse
Peter Fleischer (MVP, Partner)
Meine Homepage mit Tipps und Tricks
Kommas richtig setzen!
Schüler sagen, Lehrer haben es gut.
Schüler, sagen Lehrer, haben es gut -
Hi Sandra,
die Alternative ist, zeitintensive Arbeiten wie beispielsweise Instanziierung so auszulagern, dass sie den eigentlichen Ablauf nicht behindern. So etwas kann man im Konstruktor machen und dann im Destruktor wieder ordnungsgemäß abschließen.--
Viele Grüsse
Peter Fleischer (MVP, Partner)
Meine Homepage mit Tipps und Tricks
Kommas richtig setzen!
Schüler sagen, Lehrer haben es gut.
Schüler, sagen Lehrer, haben es gut -
die Alternative ist, zeitintensive Arbeiten wie beispielsweise Instanziierung so auszulagern, dass sie den eigentlichen Ablauf nicht behindern. So etwas kann man im Konstruktor machen und dann im Destruktor wieder ordnungsgemäß abschließen.
Hallo Peter,
und wie genau. Sonst aber mal beantwortet.
Grüße Sandra
-
Hi Sandra,
hast Du mein Bespiel vom Dienstag, 19. April 2016 10:41 nicht verstanden?--
Viele Grüsse
Peter Fleischer (MVP, Partner)
Meine Homepage mit Tipps und Tricks
Kommas richtig setzen!
Schüler sagen, Lehrer haben es gut.
Schüler, sagen Lehrer, haben es gut -
Hi Sandra,
der XmlSerializer wird mit dem zu serialisierenden Typ instanziiert. Wenn da unterschiedliche Typen im Programm zu serialisieren bzw. zu deserialisieren sind, kann man die einzelnen Instanzen in einer Liste puffern. Als Liste kann zweckmäßigerweise ein Dictionary genutzt werden, um im Programm schnell die gewünschte Instanz zu erhalten.--
Viele Grüsse
Peter Fleischer (MVP, Partner)
Meine Homepage mit Tipps und Tricks
Kommas richtig setzen!
Schüler sagen, Lehrer haben es gut.
Schüler, sagen Lehrer, haben es gut- Als Antwort markiert Sandra Bauer Montag, 25. April 2016 17:01
- Tag als Antwort aufgehoben Sandra Bauer Dienstag, 26. April 2016 16:40
-
Hallo Peter,Ich versteh's nicht. Wär's möglich aufzuzeigen, wie ich vorgehen müsste, dass beim Serialisieren
Deserialieren Objekte < 1ms liegen.Sind das gute Ansätze?
var MemStream = new MemoryStream(buffer)
Wäre das effektiver?
public static void Clear(this MemoryStream source) { byte[] buffer = source.GetBuffer(); Array.Clear(buffer, 0, buffer.Length); source.Position = 0; source.SetLength(0); }
Beim Programmstart mit new anlegen, nicht immer in der Funktion, was sicher 1ms beträgt und dann zugewiesen wird.
Ohne Liste, Dictionary.
Dann muss ich den Stream löschen, was auch zeit braucht, oder?public class Root { // ATTRIBUTES [XmlAttribute("info")] public string info { get; set; } // ELEMENTS [XmlElement("PA_REQ")] public PA_REQ PaReq { get; set; } [XmlElement("CUSTOM")] public CUSTOM Custom { get; set; } // CONSTRUCTOR public Root() { PaReq = new PA_REQ(); Custom = new CUSTOM(); }
Wenn ich diese Klasse deserialisiere werden die Klassen instanziiert.
Selbst wenn ich im Konstruktur das new der beinhaltenden Klassen weglasse, funktioniert es.
Jetzt sagst Du, ich solle das vorher in einer Liste, Dictionary machen. Da steige ich ohne konkretes Beispiel aus.
Die Zeit ist doch wichtig, deshalb frage ich nochmals nach. Sonst muss ich parsen und XDocument nehmen.Viele Grüße Sandra- Bearbeitet Sandra Bauer Dienstag, 26. April 2016 17:13 MemoryStream
-
Hi Sandra,
komisch, dass ein Beitrag von mir fehlt.Zu Deserialisieren brauchst Du:
1. einen Stream (Text), für dessen Beschaffung Zeit benötigt wird;
2. eine Instanz der XmlReader Klasse, wobei Du für die Instanziierung den Typ und Zeit benötigst;
3. Zeit für die eigentliche Deserialisierung, die bei mir bei 10000 Obejketn unter 1 ms liegt.
Bei mir braucht der zweite Punkte beträchtlich viel Zeit, weshalb mein Vorschlag war, die Instanz des XmlReaders vorfristig zu erstellen und wenn mehrfach deserialisiert werden soll, dann die gleiche Instanz wieder nutzen. Wenn es verschiedene Typen der zu deserialisierenden Objekte gibt, dann für jeden Zieltyp eine eigene Instanz erstellen und die Verweise in einem Dictionary ablegen mit Key=Zieltyp.
--
Viele Grüsse
Peter Fleischer (MVP, Partner)
Meine Homepage mit Tipps und Tricks
Kommas richtig setzen!
Schüler sagen, Lehrer haben es gut.
Schüler, sagen Lehrer, haben es gut- Als Antwort markiert Sandra Bauer Mittwoch, 27. April 2016 16:43