Autor Beitrag
Dr.Prof.Evil
Hält's aus hier
Beiträge: 9



BeitragVerfasst: Do 15.01.15 17:29 
Vorweg, ich weiß dieses Thema gab es schon mehr als einmal, aber ich möchte hier ein wenig anders auf diese eingehen

Hallo erstmal,

ich brauche für mein Programm(e) eine Liste, die unterschiedliche Typen besitzt.
Ich weiß, mit einer einfachen Liste ist dies nicht möglich. Ich habe auch nach langem Belesen keine Möglichkeit gefunden.
Ich habe mich also selber mal rangesetzt.

Es ist sicherlich naiv zu glauben, dass ich eine Lösung finde, ohne diese schon einmal gelesen zu haben

Ich habe also eine Klasse erstellt.

ausblenden C#-Quelltext
1:
2:
3:
4:
5:
    [Serializable]
    public class SpecialList
    {

    }


Diese Klasse, erbt von einer Liste die aus einer anderen Klasse besteht.
ausblenden C#-Quelltext
1:
2:
3:
4:
5:
    [Serializable]
    public class SpecificType : Evil.Microsoft.BindableBase
    {

    }


"BindableBase" wissen sicherlich manche, was das ist.

Die Klasse SpecificType, braucht nun eine Variable, die den Typen unterbringt. Ich habe dann dieses Problem so gelöst.
ausblenden volle Höhe C#-Quelltext
1:
2:
3:
4:
5:
6:
7:
8:
9:
10:
11:
12:
13:
14:
15:
16:
17:
18:
19:
20:
21:
22:
23:
24:
25:
26:
27:
28:
29:
30:
31:
32:
33:
34:
35:
36:
37:
38:
39:
/// <summary>
    /// Diese Klasse kann einen unterschiedlich beliebigen Typ als Object in der Variable Type besitzen!
    /// </summary>
    [Serializable]
    public class SpecificType : Evil.Microsoft.BindableBase
    {
        /// <summary>
        /// Defailt Konstruktor
        /// </summary>
        public SpecificType () : base () { }

        private Object type;

        /// <summary>
        /// Gibt den Typen an!
        /// </summary>
        public Object Type
        {
            get
            {
                return this.type;
            }
            set
            {
                this.SetProperty ( ref this.type, value );
            }
        }

        /// <summary>
        /// Setzt das Objekt und übergibt seinen Wert.
        /// </summary>
        /// <typeparam name="Type">Gibt den Typen des zusetzenden Wert an.</typeparam>
        /// <param name="_type">Gibt den zusetzenden Wert an.</param>
        public void SetType<Type> ( Type _type )
        {
            this.Type = _type;
        }

    }


Die Andere Klasse sieht nun wie folgt aus.

ausblenden C#-Quelltext
1:
2:
3:
4:
5:
6:
7:
8:
9:
10:
11:
/// <summary>
    /// Eine Klasse, die eine Liste beinhaltet, die verschiedene Typen in einer Liste, von der die Klasse erbt, besitzen kann. Diese Typen werden in der Klasse SpecificType gelagert.
    /// </summary>
    [Serializable]
    public class SpecialList : List<SpecificType>
    {
        /// <summary>
        /// Default Konstruktor.
        /// </summary>
        public SpecialList () : base () { }
    }


Nun, teste ich das Ganze.
ausblenden C#-Quelltext
1:
2:
3:
4:
5:
6:
7:
8:
9:
10:
11:
12:
13:
14:
15:
16:
17:
 Mensch mensch = new Mensch ();

            String[] fileNames  = { "pipette.cur""Kontakt.cs" };

            SpecialList spec = new SpecialList ();

            spec.Add ( new SpecificType () );
            spec.Add ( new SpecificType () );
            spec.Add ( new SpecificType () );
            spec.Add ( new SpecificType () );

            spec[ 0 ].SetType<String> ( "Hundekuchen" );
            spec[ 1 ].SetType<Boolean> ( true );
            spec[ 2 ].SetType<Mensch> ( mensch );
            spec[ 3 ].SetType<String[]> ( fileNames );

            Evil.FileManager.SaveAList<SpecificType> ( spec, "saves.xml"true );


Mensch ist eine einfache Klasse mit einem Property:
ausblenden volle Höhe C#-Quelltext
1:
2:
3:
4:
5:
6:
7:
8:
9:
10:
11:
12:
13:
14:
15:
16:
17:
18:
19:
20:
21:
22:
23:
24:
25:
26:
27:
28:
29:
30:
31:
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using System.Xml.Serialization;

namespace PlayGrees
{
    [Serializable]   
    public class Mensch
    {
        public Mensch () : base () 
        {
            name = "Tom";
        }

        public String name;
        public String Name
        {
            get
            {
                return this.name;
            }
            set
            {
                this.name = value;
            }
        }
    }
}


Evil.FileManager.SaveAList<SpecificType> ( spec, "saves.xml", true ); ist eine Methode aus einer von mir geschrieben Klasse:

ausblenden C#-Quelltext
1:
2:
3:
4:
5:
6:
7:
8:
9:
10:
11:
12:
13:
14:
15:
16:
/// <summary>
        /// Speichert eine beliebige Liste ab!
        /// </summary>
        /// <typeparam name="ListType">Gibt den Typen der Liste an!</typeparam>
        /// <param name="source">Gibt die zu speichernde Liste an!</param>
        /// <param name="path">Gibt den Pfad an, wohin die Datei gespeichert werden soll!</param>
        /// <param name="overwriteAccess">Gibt an, ob die Datei, wenn sie bereits vorhanden ist, überschrieben werden soll, oder nicht.</param>
        /// <param name="doCryp">Gibt an, ob die Datei verschlpsselt werden soll, oder nicht.</param>
        public static void SaveAList<ListType> ( List<ListType> source,  //     Gibt die zu speichernde Liste an!
                                                String path,             //     Gibt den Pfad an, wohin die Datei gespeichert werden soll!
                                                Boolean overwriteAccess, //     Gibt an, ob die Datei, wenn sie bereits vorhanden ist, überschrieben werden soll, oder nicht.
                                                Boolean doCryp = false )  //    Gibt an, ob die Datei verschlpsselt werden soll, oder nicht.
        {
            Evil.Hilfsklassen.ListSave<ListType> listSave = new ListSave<ListType> ( source );
            listSave.Save ( path, overwriteAccess, doCryp );
        }


Evil.Hilfsklassen.ListSave<ListType> listSave = new ListSave<ListType> ( source );
listSave.Save ( path, overwriteAccess, doCryp );
Kommt von:
ausblenden volle Höhe C#-Quelltext
1:
2:
3:
4:
5:
6:
7:
8:
9:
10:
11:
12:
13:
14:
15:
16:
17:
18:
19:
20:
21:
22:
23:
24:
25:
26:
27:
28:
29:
30:
31:
32:
33:
34:
35:
36:
37:
38:
39:
40:
41:
42:
43:
44:
45:
46:
47:
48:
49:
50:
51:
52:
53:
54:
55:
56:
57:
58:
59:
60:
61:
62:
63:
64:
65:
66:
67:
68:
69:
70:
71:
72:
73:
74:
75:
76:
77:
78:
79:
80:
81:
82:
83:
84:
85:
86:
87:
88:
89:
90:
91:
92:
93:
94:
95:
96:
97:
98:
99:
100:
101:
102:
103:
104:
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;

namespace Evil.Hilfsklassen
{

    /// <summary>
    /// Dient zur serialisierung/speicherung von beliebigen Listen!
    /// </summary>
    /// <typeparam name="ListType">Gibt den Typen der Liste an!</typeparam>
    [Serializable]
    public class ListSave<ListType> : Evil.Microsoft.BindableBase
    {
        /// <summary>
        /// Default Konstruktor
        /// </summary>
        public ListSave () : base () { }

        /// <summary>
        /// Übergibt der Klasse die zu speichernde List!
        /// </summary>
        /// <param name="source">DIe zu speichernde Liste!</param>
        public ListSave(List<ListType> source)
        {
            this.ToSave = source;
        }

        /// <summary>
        /// Wandelt das übergebende Array in eine Liste um!
        /// </summary>
        /// <param name="sourceArray">Die zu speichernde Liste in Array Form!</param>
        public ListSave(ListType[] sourceArray)
        {
            this.ToSave = new List<ListType> ();
            foreach ( ListType l in sourceArray ) this.ToSave.Add ( l );
        }

        private List<ListType> toSave;
        /// <summary>
        /// Gibt die zu speichernde Liste an!
        /// </summary>
        public List<ListType> ToSave
        {
            get
            {
                return this.toSave;
            }
            set
            {
                this.SetProperty ( ref this.toSave, value );
            }
        }

        /// <summary>
        /// Speichert die Liste in einer Datei ab!
        /// </summary>
        /// <param name="path">Gibt den Pfad an, wohin die Datei abgelegt werden soll!</param>
        /// <param name="overwriteAccess">Gibt an, ob die Datei, wenn diese bereits vorhanden ist, überschrieben werden soll.</param>
        /// <param name="doCryp">Gibt an, ob die Datei verschlüsselt werden soll!</param>
        public void Save(   String path,
                            Boolean overwriteAccess,
                            Boolean doCryp = false)
        {
            try
            {
                if ( doCryp == true )   Evil.FileManager.EncryptAndSerializeObject<ListSave<ListType>> ( path, this, overwriteAccess );
                else                    Evil.FileManager.SerializeObject<ListSave<ListType>> ( path, this, overwriteAccess );
            }
            catch (Exception e)
            {
                Console.WriteLine ( "Exception in ListSave.Save(String, bool, bool)" );
                Console.WriteLine ( e.Source );
                Console.WriteLine ( e.Message );
            }
        }

        /// <summary>
        /// Liest die Liste aus der Datei aus.
        /// </summary>
        /// <param name="path">Gibt den Pfad zur Datei an.</param>
        /// <param name="didCryped">Gibt an, ob die Datei vorher entschlüsselt werden muss. Dieses nur angeben, wenn die Datei beim speicher verschlüsselt wurde!</param>
        /// <returns>Die ausgelesene Liste!</returns>
        public static ListSave<ListType> Load(  String path,
                                                Boolean didCryped = false)
        {
            try
            {
                if ( didCryped == true ) return Evil.FileManager.DecryptAndDeserializeObject<ListSave<ListType>> ( path );
                else                     return Evil.FileManager.DeserializeObject<ListSave<ListType>> ( path );
            }
            catch (Exception e)
            {
                Console.WriteLine ( "Exception in ListSave.Load(String, bool)" );
                Console.WriteLine ( e.Source );
                Console.WriteLine ( e.Message );
            }

            return null;
        }
    }
}


Dort drin befinden sich nun wieder Methoden, die aus einer anderen Klasse kommen:
ausblenden volle Höhe C#-Quelltext
1:
2:
3:
4:
5:
6:
7:
8:
9:
10:
11:
12:
13:
14:
15:
16:
17:
18:
19:
20:
21:
22:
23:
24:
25:
26:
27:
28:
29:
30:
31:
32:
33:
34:
35:
36:
37:
38:
39:
40:
41:
42:
43:
44:
45:
46:
47:
48:
49:
50:
51:
52:
53:
54:
55:
/// <summary>
        /// Entschlüsselt und deserialisiert ein beliebiges Objekt aus einer beliebigen Klasse.
        /// </summary>
        /// <typeparam name="MyObjectClass">Gibt die Klasse an, aus der das zu deserialisierende Objekt stammt.</typeparam>
        /// <param name="path">Gibt den Pfad der Datei an, aus der deserialisiert werden soll.</param>
        /// <param name="key">Gibt einen Schlüssel an, der benötigt wird, wenn die zu entschlüsselde Datei mit einem Schlüssel verschlüsselt wurde. Der Schlüssel muss in beiden fällen der Selbe sein.</param>
        /// <returns>Das entschlüsselte und deserialisierende Objekt.</returns>
        public static MyObjectClass DecryptAndDeserializeObject<MyObjectClass> ( String path, byte[] key = null )
        {
            /*  Diese Methode Deserialisiert eine klasse und entschlüsselt sie.
             *  Wenn die Datei mit einem bestimmten Schlüssel (key) verschlüsselt wurde, und der hier angegebene Schlüssel
             *  nicht mit dem übereinstimmt, dann wird ein Fehler kommen und die es wird null zurückgegeben!
             */

            DESCryptoServiceProvider _key = new DESCryptoServiceProvider ();

            //  Setzt den Key, wenn dieser übergeben wurde
            if(key != null)
            {
                _key.Key = key;
                _key.IV  = key;
            }

            if ( File.Exists ( path ) == true )
            {
                using ( FileStream fStream = new FileStream ( path, FileMode.Open, FileAccess.Read ) )
                {
                    if ( key == null )
                    {
                        using ( CryptoStream cStream = new CryptoStream ( fStream, _key.CreateDecryptor ( Encoding.ASCII.GetBytes ( "64BitPas" ), Encoding.ASCII.GetBytes ( "InitVector" ) ), CryptoStreamMode.Read ) )
                        {
                            XmlSerializer s = new XmlSerializer ( typeof ( MyObjectClass ) );
                            return (MyObjectClass) s.Deserialize ( cStream );
                        }
                    }
                    else
                    {
                        using ( CryptoStream cStream = new CryptoStream ( fStream, _key.CreateDecryptor (), CryptoStreamMode.Read ) ) 
                        {
                            XmlSerializer s = new XmlSerializer ( typeof ( MyObjectClass ) );
                            try
                            {
                               return (MyObjectClass) s.Deserialize ( cStream );
                            }
                            catch (Exception e)
                            {
                                Console.WriteLine ( e.Message );
                            }
                           
                        }
                    }
                }
            }

            return default ( MyObjectClass );
        }

ausblenden volle Höhe C#-Quelltext
1:
2:
3:
4:
5:
6:
7:
8:
9:
10:
11:
12:
13:
14:
15:
16:
17:
18:
19:
20:
21:
22:
23:
24:
25:
26:
27:
28:
29:
30:
31:
32:
33:
34:
35:
36:
37:
38:
39:
40:
41:
42:
43:
44:
45:
ublic static void EncryptAndSerializeObject<MyObjectClass> ( String path,                //  Der Pfad zur Datei
                                                                        MyObjectClass obj,        //  Das zu serialisierende Objekt
                                                                        Boolean overwrite = false,  //  Gibt an, ob die Datei überschrieben werden soll, wenn sie bereits vorhanden ist
                                                                        byte[] key = null )         //  Der Schlüssel zum verschlüsseln des Inhalts der Datei
        {
            /* Serialisiert ein beliebiges Objekt und verschlüsselt den Inhalt der Datei
             *
             * Serialisiert ein ganz normal ein Objekt, verschlüsselt die Datei zusätzlich.
             * Sollte ein Schlüssel angegeben werden, dann kann man diese Datei auch nur mit dem Selben wieder einlesen
             * 
             */

            DESCryptoServiceProvider _key = new DESCryptoServiceProvider ();

            //  Wenn ein Schlüssel angegeben wurde, dann übergebe ihn an den CryptoServiceProvider
            if(key != null)
            {
                _key.Key = key;
                _key.IV = key;
            }

            //  Wennn die Datei überschrieben werden, soll dann löscht er diese.
            //  Man muss nicht vorher überprüfen ob sie überhaupt existiert, weil wenn sie nicht existieren sollte, dann kommt keine Fehlermeldung 
            if ( overwrite == true ) FileManager.DeleteFile ( path, true );
          
            //Serialisiert das Objekt mit dem Xmlserializer aus dem CryptoStream einmal, wenn ein Key angegeben wurde, und einmal wenn nicht.
            using ( FileStream fStream = new FileStream ( path, FileMode.Create, FileAccess.Write ) )
            {
                if ( key == null )
                {
                    using ( CryptoStream cStream = new CryptoStream ( fStream, _key.CreateEncryptor ( Encoding.ASCII.GetBytes ( "64BitPas" ), Encoding.ASCII.GetBytes ( "InitVector" ) ), CryptoStreamMode.Write ) )
                    {
                        XmlSerializer s = new XmlSerializer ( typeof ( MyObjectClass ) );
                        s.Serialize ( cStream, obj ); 
                    }
                }
                else
                {
                    using ( CryptoStream cStream = new CryptoStream ( fStream, _key.CreateEncryptor (), CryptoStreamMode.Write ) )
                    {
                        XmlSerializer s = new XmlSerializer ( typeof ( MyObjectClass ) );
                        s.Serialize ( cStream, obj );
                    }
                }
            }
        }


Mein Problem ist nun, dass wenn ich einen String, Boolean oder Int32 speichere, funktionierts. Sobald ich aber ein Array oder Mensch speichern möchte, kommt ein Fehler:
"Beim Generieren des XML-Dokuments ist ein Fehler aufgetreten."
{System.InvalidOperationException: Der Typ PlayGrees.Mensch wurde nicht erwartet. Verwenden Sie das XmlInclude- oder das SoapInclude-Attribut, um Typen anzugeben, die nicht statisch sind.
bei System.Xml.Serialization.XmlSerializationWriter.WriteTypedPrimitive(String name, String ns, Object o, Boolean xsiType)
bei Microsoft.Xml.Serialization.GeneratedAssembly.XmlSerializationWriterListSave1.Write1_Object(String n, String ns, Object o, Boolean isNullable, Boolean needType)
bei Microsoft.Xml.Serialization.GeneratedAssembly.XmlSerializationWriterListSave1.Write3_SpecificType(String n, String ns, SpecificType o, Boolean isNullable, Boolean needType)
bei Microsoft.Xml.Serialization.GeneratedAssembly.XmlSerializationWriterListSave1.Write4_ListSaveOfSpecificType(String n, String ns, ListSave`1 o, Boolean isNullable, Boolean needType)
bei Microsoft.Xml.Serialization.GeneratedAssembly.XmlSerializationWriterListSave1.Write5_ListSaveOfSpecificType(Object o)}

Ich habe ehrlich gesagt keine Ahnung, was ich zu tun habe.

M.f.G.
Dr. Prof. Evil | Tom

Moderiert von user profile iconTh69: Titel geändert: "Hilfe" entfernt
Ralf Jansen
ontopic starontopic starontopic starontopic starontopic starontopic starontopic starhalf ontopic star
Beiträge: 4708
Erhaltene Danke: 991


VS2010 Pro, VS2012 Pro, VS2013 Pro, VS2015 Pro, Delphi 7 Pro
BeitragVerfasst: Do 15.01.15 17:46 
Der XmlSerialiser kann nur bekannte Typen serialisieren/deserialisieren. In deinem Serializer Code ist dem XmlSerializer NUR MyObjectClass bekannt.
Nur die kann er Serialisieren keine Ableitungen davon (ich spar mir gerade mal die ganzen Details des warum).

Wenn du Ableitungen von MyObjectClass serialisieren können willst mußt du die ALLE benennen (die müssen zur Compilezeit dem Serializer bekannt sein). Eine Möglichkeit, die auch die Exception benennt, ist alle Ableitungen von MyObjectClass die du serialisieren können möchtest an MyObjectClass bekannt zu machen in dem du eben der Klasse MyObjectClass ein XmlInclude Attribut verpasst und alle aufzählst.

ausblenden C#-Quelltext
1:
2:
3:
4:
5:
 [XmlInclude(typeof(Mensch)), XmlInclude(typeof(Tier))]
 public MyObjectClass : WhatSoEverClass
 {
    ...
 }


Edit: Es ist nett wenn man nicht immer alle Details erfragen muß und schon potentiell relevantes mitliefert was für die Beantwortung wichtig sein könnte. Man kann aber auch übertreiben ;)

Edit2: Falls MyObjectClass ein generischer Platzhalter für einen konkreten Typ ist und gar keine eigene Klasse gilt oben gesagtes natürlich für die konkret benutzte Klasse. Und falls dem so ist würde es helfen für generische Platzhalter ein spezielles Naming zu verwenden. Es hat sich üblicherwese eingebürgert den Namen mit einem T zu präfixen. Wennn man sich daran hält funktionieren übrigens auch die Reflexe der hier anwesenden potentiellen Helfer besser ;)
Dr.Prof.Evil Threadstarter
Hält's aus hier
Beiträge: 9



BeitragVerfasst: Fr 16.01.15 15:25 
user profile iconRalf Jansen hat folgendes geschrieben Zum zitierten Posting springen:

Edit2: Falls MyObjectClass ein generischer Platzhalter für einen konkreten Typ ist und gar keine eigene Klasse gilt oben gesagtes natürlich für die konkret benutzte Klasse. Und falls dem so ist würde es helfen für generische Platzhalter ein spezielles Naming zu verwenden. Es hat sich üblicherwese eingebürgert den Namen mit einem T zu präfixen. Wennn man sich daran hält funktionieren übrigens auch die Reflexe der hier anwesenden potentiellen Helfer besser ;)


Danke erstmal für dein Antworten.



ausblenden C#-Quelltext
1:
2:
3:
4:
5:
6:
7:
8:
9:
10:
11:
12:
13:
14:
15:
16:
17:
18:
19:
20:
21:
public class MyClass : Irgendwas
{
  public static void Methode<T>(T obj)
  {
    
  }
}

public class MyMainClass
{
  static void Main(String[] args]
  {
    //Kann ich den XmlInclude auch im Aufruf erst erwähnen. Mein Code ist ja aus einer .dll . Und diese soll ja für sämtliche Projekte sein, dass heißt, 
    //dass ich in .dll Projekt ja nicht angeben kann,welche Klassen alle er kennen muss, da die in diesem Projekt ja nie existieren werden.
    [XmlInclude typeof(IrgendeineKlasse)]  // ???????
    MyClass.Methode<IrgendeineGANZandereKlasse>(obj);

    // Die Klasse "IrgendeineKlasse" wird in der Methode vewendet. Exakt so, wie in meiner Liste oben z.B.. Also sollte der XmlSerializer diese nicht kennen.
    // Ich bin mir sicher, dann dies so nicht funktioniert, ist ja nicht mal alles vorhanden was ich angegeben habe.
  }
}


Fragen stehen im Quellcode.
Ralf Jansen
ontopic starontopic starontopic starontopic starontopic starontopic starontopic starhalf ontopic star
Beiträge: 4708
Erhaltene Danke: 991


VS2010 Pro, VS2012 Pro, VS2013 Pro, VS2015 Pro, Delphi 7 Pro
BeitragVerfasst: Fr 16.01.15 16:19 
Zitat:
Kann ich den XmlInclude auch im Aufruf erst erwähnen

Du kannst das an der Methode bei der Methodendefinition erwähnen aber natürlich nicht bei der Methodenverwendung(Dieses Attribut ist eine Metainfo für den Serializer zur Compilezeit nicht zur Laufzeit.) An der Methode ist aber noch weniger sinnvoll als an der Klasse. Du kannst ja nicht mehr auf den Ändern des Typs T reagieren.

Zitat:
dass ich in .dll Projekt ja nicht angeben kann,welche Klassen alle er kennen muss, da die in diesem Projekt ja nie existieren werden.


Alternativ kannst du dem XmlSerializer über den Konstruktor eine Array aller möglicherweise auftretenden Klassen mitgeben. Aber zum Zeitpunkt des Erzeugens des XmlSerialzers müssen die ALLE bekannt sein und du mußt die benennen können egal ob über ein Attribut oder diese Typ Liste.
Palladin007
ontopic starontopic starontopic starontopic starontopic starontopic starontopic starofftopic star
Beiträge: 1282
Erhaltene Danke: 182

Windows 11 x64 Pro
C# (Visual Studio Preview)
BeitragVerfasst: Fr 16.01.15 17:18 
Eine Option ist vielleicht, die verwendeten Typen auch zu speichern? Der vollständige Name des Typs reicht aus, dann kann .NET den Typ wieder finden, solange die verwendeten Assemblys weiterhin erreichbar sind.
Du müsstest dann allerdings vorher diese Liste als extra Datei daneben legen (ist am einfachsten), oder in die XML, allerdings müsstest du das dann vorher raus lesen, was sich aber mit LINQtoXML gut lösen lässt. Den Haupt-Aufwand der Deserialisierung übernimmt dann trotzdem der XmlSerializer.

Alternativ kannst du ganz LINQtoXML verwenden, flexibler geht nicht, allerdings musst du dann alles selber machen.
Oder du implementierst für jeden verwendeten Typ das IXmlSerializable-Interface und schreibst für jeden Listen-Inhalt noch zusätzlich den Typ dazu. Wenn du den verwendeten Typ hast, kannst du den dann nehmen und dort die ReadXml-Methode von IXmlSerializable aufrufen. Die macht dann genau das gleiche auch für die unbekannten Objekte, die in ihrem XML-Abschnitt vorhanden sind.


Oder du verwendest Soap, das ist nicht mehr so schön zu lesen wie XML, kann aber mit deiner Problemstellung umgehen, ohne dass du noch etwas dazu machen musst.
Dr.Prof.Evil Threadstarter
Hält's aus hier
Beiträge: 9



BeitragVerfasst: Fr 16.01.15 19:53 
user profile iconPalladin007 hat folgendes geschrieben Zum zitierten Posting springen:
Eine Option ist vielleicht, die verwendeten Typen auch zu speichern? Der vollständige Name des Typs reicht aus, dann kann .NET den Typ wieder finden, solange die verwendeten Assemblys weiterhin erreichbar sind.
Du müsstest dann allerdings vorher diese Liste als extra Datei daneben legen (ist am einfachsten), oder in die XML, allerdings müsstest du das dann vorher raus lesen, was sich aber mit LINQtoXML gut lösen lässt. Den Haupt-Aufwand der Deserialisierung übernimmt dann trotzdem der XmlSerializer.


Ich habe schon begonnen an der Idee zu arbeiten. Habe nun 2 Methoden. Einmal die, die die Objekte alle einserialisiert. (kompliziertes Wort) Und einmal die, die die ausliest.

Erst einmal, was habe ich bis jetzt getan:

___________________________________________________________________________________________________________________________________________________________________________________________________________
Um wieder zu übertreiben, dafür aber wenig Fragen aufkommen:

Ich habe erstmal eine neue Klasse erstellt.

ausblenden C#-Quelltext
1:
2:
3:
4:
5:
6:
 
  [Serializable]
  public partial class SpecialSerialize : Evil.Microsoft.BindableBase
  {
    
  }


Diese klasse ist partial, weil sie über eine andere Klasse aufrufbar sein soll.

Nun habe ich wieder Properties erstellt. Dieses Mal eine Liste mit Objects (Meine Typen), und einmal eine Liste<String>, die später alle Namen von den Listen aller Klassen von den Typen in der
richtigen Reihenfolge beherbergt. (Achtung, es besteht durch den komplizierten Satz hoffentlich keine Missverständnisgefahr)

ausblenden volle Höhe C#-Quelltext
1:
2:
3:
4:
5:
6:
7:
8:
9:
10:
11:
12:
13:
14:
15:
16:
17:
18:
19:
20:
21:
22:
23:
24:
25:
26:
27:
28:
29:
30:
31:
32:
33:
34:
35:
36:
37:
38:
39:
40:
41:
42:
43:
44:
45:
46:
47:
48:
           [Serializable]
            public partial class SpecialSerialize : Evil.Microsoft.BindableBase
            {
                public SpecialSerialize () : base ()
                {
                    this.Type = new List<Object> ();
                }

                private List<Object> type;
                [XmlIgnore]
                private List<Object> Type
                {
                    get
                    {
                        return this.type;
                    }
                    set
                    {
                        this.SetProperty ( ref this.type, value );
                    }
                }

                private List<String> nameOfAllType;
                public List<String> NameOfAllTypes
                {
                    get
                    {
                        return this.nameOfAllType;
                    }
                    set
                    {
                        this.SetProperty ( ref this.nameOfAllType, value );
                    }
                }

                #region " Add und Get: AddType<T> | GetObject

                public void AddType<T> ( T type )
                {
                    this.Type.Add ( type );
                }

                public Object GetObject ( Int32 _index )
                {
                    return this.Type[ _index ];
                }

                #endregion


Zu beachten ist, dass die Liste mit Objekten ignoriert werden soll, vom Serializer.

___________________________________________________________________________________________________________________________________________

Nun kommen die möglicherweise komplizierten Methoden.
Bitte kein Gemecker an manchen Stellen. :(

ausblenden volle Höhe C#-Quelltext
1:
2:
3:
4:
5:
6:
7:
8:
9:
10:
11:
12:
13:
14:
15:
16:
17:
18:
19:
20:
21:
22:
23:
24:
25:
26:
27:
28:
29:
30:
31:
32:
33:
34:
35:
36:
37:
38:
39:
40:
41:
42:
43:
44:
45:
46:
47:
48:
49:
50:
51:
52:
53:
54:
55:
56:
57:
58:
59:
60:
61:
62:
63:
64:
65:
66:
67:
68:
69:
70:
71:
72:
73:
                public void Save ( String mainPath,
                                    Boolean overwriteAccess = false )
                {
                    /*  In dieser Methode, möchte ich alle Typen speichern. Es ist nicht möglich, einfach die gesamte Klasse zu serialisieren. Deswegen tuhe ich folgendes:
                     *  Ich serialisere jeden Typen extra, und füge dann sämtlichen Inhalt zusammen und packe diesen in eine Datei. Theoretisch möglich. 
                     */


                    //  Diese Pfade werden für die Anzahl der Typen benötigt, um zwischen zu serialisieren.
                    List<String> tempPaths = new List<String> ();
                    for ( int i = 0 ; i < this.Type.Count ; i++ ) tempPaths.Add ( "tempSave" + i + ".xml" );

                    //  Hier werden die Zeilen die am Ende entgültig zu schreiben sind, aufgelistet.
                    List<String> toWrite = new List<String> ();

                    //  Überprüfung, ob die Datei bereits vorhanden ist.
                    if ( overwriteAccess == true )
                    {
                        try
                        {
                            FileManager.DeleteFile ( mainPath, true );
                        }
                        catch ( Exception e )
                        {
                            Console.WriteLine ( e.Message );
                        }
                    }

                    Int32 counter = 0;  //  Dieser counter zählt bei jedem Durclauf der folgenden Schleife 1 hoch
                    foreach ( String path in tempPaths )
                    {
                        //  Serialiseren des Objektes
                        using ( FileStream stream = new FileStream ( path, FileMode.Create, FileAccess.Write ) )
                        {
                            try
                            {
                                //  Hier wird als Type, der type des zu serialisierenden Objektes genutzt
                                XmlSerializer s = new XmlSerializer ( this.Type[ counter ].GetType () );
                                s.Serialize ( stream, this.Type[ counter ] );
                            }
                            catch ( Exception e )
                            {
                                Console.WriteLine ( e.Message );
                            }
                        }

                        //  Im folgenden Code, werden aus der serialisierten Datei, alle Zeilen ausgelesen und in eine Liste gebracht.
                        String[] linesInFile = Evil.FileManager.ReadLinesInFile ( path );   
                        foreach ( String s in linesInFile )
                        {
                            toWrite.Add ( s );
                        }
                        counter++;
                    }

                    //  Das Array hat den selben Inhalt wie toWriteListe, aber ich brauche dieses für die Methode zum schreiben der Datei
                    String[] toWriteAsArray = new String[ toWrite.Count ];
                    counter = 0;    //  Der counter zälht wieder/Durchlauf + 1
                    foreach ( String s in toWrite )
                    {
                        toWriteAsArray[ counter ] = s;
                        counter++;
                    }

                    //  Hier wird nun alles eingeschrieben
                    Evil.FileManager.WriteLinesInFile ( toWriteAsArray, mainPath, overwriteAccess );

                    //  Nun speichere ich die ganze Klasse ab, worin sich alle Namen der Klassen der serialisierten Typen befindet. 
                    this.NameOfAllTypes = new List<String> ();
                    for ( int i = 0 ; i < this.Type.Count ; i++ ) this.NameOfAllTypes.Add ( this.Type[ i ].GetType ().Name );

                    Evil.FileManager.SerializeObject<SpecialSerialize> ( "_SpecialSerialize_NamesOfAllTypes.xml"thistrue );
                    //Evil.FileManager.SetFileAttribute("_SpecialSerialize_NamesOfAllTypes.xml", FileAttributes.Hidden, true);
                }


Hier wird nun jeder Type serialisiert. Der Inhalt der Datein, wird erfolgreich zusammengeführt.
Zusätzlich, werden die Namen jeder Klassen jedes Types aufgelistet und ebenfalls über die eigene Klasse selbst, serialisiert.

________________________________________________________________________________________________________________________________________

Nun kommt das Laden.

ausblenden volle Höhe C#-Quelltext
1:
2:
3:
4:
5:
6:
7:
8:
9:
10:
11:
12:
13:
14:
15:
16:
17:
18:
19:
20:
21:
22:
23:
24:
25:
26:
27:
28:
29:
30:
31:
32:
33:
34:
35:
36:
37:
38:
39:
40:
41:
42:
43:
44:
45:
46:
47:
48:
49:
50:
51:
52:
53:
54:
55:
56:
57:
58:
59:
60:
61:
62:
63:
64:
65:
66:
67:
68:
69:
70:
71:
72:
73:
74:
75:
76:
77:
78:
79:
                public static SpecialSerialize Load ( String mainPath )
                {
                    /*  Im folgenden Code, tuhe ich beinahe komplett das Selbe wie bei .Save(); Nur andersherum.
                     *  Erstmal wird die MainDatei Stück für Stück ausgelesen. (Type für Type)
                     */


                    //  Erstmal wird ein SpecialSerialize erstellt, und geladen. -> Liste mit Namen aller Klassen der seialisierten Typen wird ausgelesen
                    SpecialSerialize specialSerialize = Evil.FileManager.DeserializeObject<SpecialSerialize> ( "_SpecialSerialize_NamesOfAllTypes.xml" );

                    //  Diese Pfade werden für die Anzahl der Typen benötigt, um zwischen zu speichern um darauf folgend auszulesen
                    List<String> tempPaths = new List<String> ();
                    for ( int i = 0 ; i < specialSerialize.NameOfAllTypes.Count ; i++ ) tempPaths.Add ( "tempSave" + i + ".xml" );

                    //  Liest nun die komplette MainDatei aus
                    String[] tempLinesInFile = Evil.FileManager.ReadLinesInFile ( mainPath );   
                    List<String> linesInFile = new List<String> (); //  Beherbergt exakt den selben Inhalt wie tempLinesInFile

                    Int32 counterFirstForEach = 0;  //  Zählt für jeden Durchlauf der folgenden Schleife + 1
                    foreach ( String className in specialSerialize.NameOfAllTypes )
                    {
                        foreach ( String str in tempLinesInFile ) linesInFile.Add ( str );  //  (Komplizierte) Umwandlung von String[] zu List<String>

                        //  In diese Liste kommen alle Zeilen/tempDatei bzw. Type. Diese wird am Ende der Schleife eingeschrieben
                        List<String> toWriteInTemp = new List<String> ();

                        foreach ( String line in linesInFile )
                        {
                            /*  Hier wird nun jede Zeile einer Klasse in der MainDatei ausgelesen. Und zu toWriteInTemp hinzugefügt, bis die Klasse in der Datei zuende definiert ist.
                             *  (</ClassName>)
                             */

                            String toCheck = "</" + className + ">";
                            if ( line != toCheck ) toWriteInTemp.Add ( line );
                            if ( line == toCheck )
                            {
                                toWriteInTemp.Add ( line );
                                break;
                            }
                        }

                        //  Die ausgelesenen Zeilen müssen nun entfernt werden:
                        for ( int i = 0 ; i < toWriteInTemp.Count ; i++ ) linesInFile.Remove ( linesInFile[ 0 ] );

                        //  Die folgenden Zeilen wandeln wieder in ein String[] um
                        String[] toWriteAsArray = new String[ toWriteInTemp.Count ];
                        Int32 counter = 0;
                        foreach ( String str in toWriteInTemp )
                        {
                            toWriteAsArray[ counter ] = str;
                            counter++;
                        }

                        //  Hier wird nun eingeschrieben
                        Evil.FileManager.WriteLinesInFile ( toWriteAsArray, tempPaths[ counterFirstForEach ], true );


                        counterFirstForEach++;
                    }

                    /*  Hier hänge nun. Ich besitze Name der Klasse um muss beim deserialiseren auf jede einzelne Klasse der Typen (casten).
                     *  Doch geht das ja nicht, weil in dem .dll Projekt diese Klassen nicht bekannt sind.
                     */

                    foreach ( String path in tempPaths )
                    {
                        if ( File.Exists ( path ) == true )
                        {
                            using ( FileStream stream = new FileStream ( path, FileMode.Open, FileAccess.Read ) )
                            {
                                XmlSerializer s = new XmlSerializer ( typeof ( Object ) );
                                specialSerialize.Type.Add ( s.Deserialize ( stream ) );
                            }
                        }
                        else
                        {

                        }
                    }

                    return specialSerialize;
                }


Hier wird eine Menge getan. Genau genommen, alles andersherum. Ich teile die MainDatei und schreibe alles getrennt in mehrere Datein, die ich dann jeweils Typ für Typ in der richtigen Reihenfolge,
deserialisieren könnte doch:


Du sagtest, dass es reicht, den Namen des Typs zu wissen. Ich habe ja alle ausgelesen und besitze auch alle. Wie kann ich das nun verwenden?

M.f.G. Dr. Prof. Evil | Tom

P.S. Bei Fragen, die sich hier sicherlich nicht vermeiden lassen, einfach stellen. :)
Palladin007
ontopic starontopic starontopic starontopic starontopic starontopic starontopic starofftopic star
Beiträge: 1282
Erhaltene Danke: 182

Windows 11 x64 Pro
C# (Visual Studio Preview)
BeitragVerfasst: Fr 16.01.15 21:49 
Versuch mal das.
Den Link habe ich aber innerhalb zwei Sekunden mit "c# get type of full name" gefunden, gleich der zweite Streffer ;)

Allgemein würde ich dir noch empfehlen, deine Methoden etwas besser zu strukturieren und auch auf eine einzige Aufgabe zu reduzieren.
Es ist mir noch bei jeder Methode gelungen, sie in Teilaufgaben zu splitten, kombiniert mit einer geschickten Benennung der Methode und der Parameter, kann allein der Aufruf den ganzen Inhalt der Methode verraten und so den ganzen Code viel übersichtlicher machen.
Ich muss gestehen, dass ich mir den Code nicht angeschaut habe, ich beobachte das auch bei meinem eigenen Code, wenn ich Abneigung spüre, mir den Code einer Methode anzuschauen, ist sie definitiv zu lang bzw. zu unsortiert/unordentlich.

Was den ganzen Beitrag angeht:
Der Wunsch, keine Fragen offen zu lassen, ist zwar positiv und kann es durchaus einfacher machen, zu helfen, zu viel Text kann aber auch stark demotivieren, überhaupt zu helfen. Die Kunst ist wie so oft, den Mittelweg zu finden. Möglichst wenig schreiben, aber möglichst viele ungestellte Fragen beantworten und gleichzeitig das Problem möglichst genau beschreiben. Bei dir läuft ja alles auf eine Frage hinaus, der Rest darüber ist doch eigentlich gar nicht notwendig, oder?


Aber zurück zum Thema:
Ich habe ja oben einen Link geschickt, wie du ein Type-Objekt von dem Name des Typs erhältst. Wenn du den Namen von der FullName-Property erhältst, sollte das kein Problem darstellen.
Instanziieren kannst du den Typ mit der Activator-Klasse und einer der CreateInstance-Überladungen. Die Methoden rufen intern einen Konstruktor auf, welcher das ist, musst du ihr mit teilen, sonst sucht sie einen parameterlosen Konstruktor, der vielleicht nicht da ist.
Wenn die Klassen IXmlSerializable implementiert haben, dann kannst du dahin casten und dann die Methoden nutzen, wenn nicht, musst du die Eigenschaften selber schreiben, was dann schon aufwendiger wird.
Ralf Jansen
ontopic starontopic starontopic starontopic starontopic starontopic starontopic starhalf ontopic star
Beiträge: 4708
Erhaltene Danke: 991


VS2010 Pro, VS2012 Pro, VS2013 Pro, VS2015 Pro, Delphi 7 Pro
BeitragVerfasst: Fr 16.01.15 22:22 
Mir ist das auch zuviel Code der vermutlich rein gar nix mit dem Problem zu tun hat was tatsächlich gelöst werden soll. Mich beschleicht sogar das Gefühl das da gar kein Problem existiert sondern du irgendwo falsch abgebogen bist und dich an irgendwas eigentlich unnötigem abarbeitest. Vielleicht drehen wir das Rad darum gerade nochmal zurück und du erzählst was das initiale Problem ist und wir versuchen nicht das Problem innerhalb Problemlösung zu beseitigen solange wir das eigentliche Problem nicht kennen.
Also was ist das Problem?
z.B.
- Persistieren eines Programmzustands?
- Transportieren von Daten?

Was sind dabei die Randbedingungen?
z.B.
- Datenformat der persistierten Daten
- Wie generisch muss das sein (aka zu welchem Zeitpunkt ist bekannt was für Typen serialisiert werden müssen)
- muss das auch wieder eingelesen werden können? und wenn ja von wem (dem eigenen Programm (versionsunabhängig) oder auch von anderen Programmen)
Dr.Prof.Evil Threadstarter
Hält's aus hier
Beiträge: 9



BeitragVerfasst: Fr 16.01.15 23:02 
Okay. Zurückspulen ist eine gute Idee.

Also: Ich habe mich an eine Klasse herangewagt, die eine Liste aus verschiedenen Typen beinhalteten soll. Gelöst habe ich das Problem, wie nach dem Absatz beschrieben.
Herausbekommen haben wir schon einmal, dass der Typ/die Typen für den XmlSerializer beim Serialisieren unbekannt ist/sind, da beim serialisieren als Type die Klasse mit der
Liste der unterschiedlichen Typen angegeben wird, und nicht jeder einzelne Typ in der Liste.

ausblenden volle Höhe C#-Quelltext
1:
2:
3:
4:
5:
6:
7:
8:
9:
10:
11:
12:
13:
14:
15:
16:
17:
18:
19:
20:
21:
22:
23:
24:
25:
26:
27:
28:
29:
30:
31:
32:
33:
34:
35:
36:
    [Serializable]
    public class SpecificType : Evil.Microsoft.BindableBase
    {
        /// <summary>
        /// Defailt Konstruktor
        /// </summary>
        public SpecificType () : base () { }

        private Object type;

        /// <summary>
        /// Gibt den Typen an!
        /// </summary>
        public Object Type
        {
            get
            {
                return this.type;
            }
            set
            {
                this.SetProperty ( ref this.type, value );
            }
        }

        /// <summary>
        /// Setzt das Objekt und übergibt seinen Wert.
        /// </summary>
        /// <typeparam name="Type">Gibt den Typen des zusetzenden Wert an.</typeparam>
        /// <param name="_type">Gibt den zusetzenden Wert an.</param>
        public void SetType<Type> ( Type _type )
        {
            this.Type = _type;
        }

    }

ausblenden C#-Quelltext
1:
2:
3:
4:
5:
6:
7:
8:
    [Serializable]
    public class SpecialList : List<SpecificType>
    {
        /// <summary>
        /// Default Konstruktor.
        /// </summary>
        public SpecialList () : base () { }
    }

Existieren, tut die Klasse/Klassen in einer .dll Datei. Das heißt, dass wir im .dll Projekt selber die zu serialisierenden Typen verlangen müssen.
Das selbe natürlich auch, fürs Auslesen. Dabei muss auch jeder Typ wieder in die Liste eingelesen werden.

Ich hatte nun einmal die Idee, alle Typen einzeln zu serialisieren, und den Inhalt aller Datein zusammen in eine zu stopfen.
Beim auslesen genau anders herum. Doch muss man dem XmlSerializer sagen, welchen Typ er deserialisieren soll, doch können wir ihm das im .dll Projekt selber nicht sagen.

M.f.G.
Dr. Prof. Evil | Tom

Moderiert von user profile iconTh69: Vollzitat entfernt.
Palladin007
ontopic starontopic starontopic starontopic starontopic starontopic starontopic starofftopic star
Beiträge: 1282
Erhaltene Danke: 182

Windows 11 x64 Pro
C# (Visual Studio Preview)
BeitragVerfasst: Fr 16.01.15 23:26 
Ich glaube, du hast Ralf falsch verstanden ;)
Er meint den Grund, warum du die Daten überhaupt serialisieren willst, was ist das Ziel und warum unbedingt XML? Was sind die Rahmenbedingungen, wäre ein anderer Weg vielleicht auch möglich oder besser?