Laden...

GetProperties / Serialisierung ändert die Reihenfolge der Properties

Erstellt von trib vor 5 Jahren Letzter Beitrag vor 5 Jahren 1.269 Views
T
trib Themenstarter:in
708 Beiträge seit 2008
vor 5 Jahren
GetProperties / Serialisierung ändert die Reihenfolge der Properties

Hallo zusammen,

ich habe gerade ein Phänomen, welches ich nicht so recht nachvollziehen kann.

Eine Klasse erbt von einer Anderen und wird serialisiert.
Beim ersten Durchlauf werden zuerst die Eigenschaften der Basisklasse und dann die der abgeleiteten Klasse verarbeitet.
Ab dem folgenden Durchlauf genau andersherum.

Dass obj.GetType().GetProperties() weder Alphabetisch, noch in der Reihenfolge der Initialisierung durchlaufen wird, sagt ja schon die MSDN.
Dennoch bin ich verwundert, dass es unterschiedlich passiert.

Hintergrund ist, dass leider die Seite, die mein serialisiertes Json entgegen nimmt, auf das allererste Element angewiesen ist.

Der Code stark vereinfacht:


using System;
using System.Reflection;
using System.Xml.Serialization;

namespace TestConsole
{
    class Program
    {
        static void Main(string[] args)
        {
            var t = new Test();
            Console.ReadKey();
        }
    }

    public class Test
    {
        private DerivedClass derivedClass;
        public Test()
        {
            RunFirstTest();
            RunSecondTest();
            RunThirdTest();
            RunFourthTest();
        }

        public void RunFirstTest()
        {
            derivedClass = new DerivedClass();
            derivedClass.TestValue = "Test1";
            var baseClass = new BaseClass(derivedClass);
            baseClass.Id = "1";
            Console.WriteLine(Json.Serialize(baseClass));
        }

        public void RunSecondTest()
        {
            derivedClass.TestValue = "Test2";
            var baseClass = new BaseClass(derivedClass);
            baseClass.Id = "2";
            Console.WriteLine(Json.Serialize(baseClass));
        }

        public void RunThirdTest()
        {
            derivedClass = new DerivedClass();
            derivedClass.TestValue = "Test3";
            var baseClass = new BaseClass(derivedClass);
            baseClass.Id = "3";
            Console.WriteLine(Json.Serialize(baseClass));
        }

        public void RunFourthTest()
        {
            var derivedClass2 = new DerivedClass();
            derivedClass2.TestValue = "Test3";
            var baseClass = new BaseClass(derivedClass2);
            baseClass.Id = "3";
            Console.WriteLine(Json.Serialize(baseClass));
        }
    }

    public class DerivedClass
    {
        private string _testValue = string.Empty;
        public string TestValue
        {
            get { return _testValue; }
            set { _testValue = value; }
        }
    }

    public class BaseClass : DerivedClass
    {
        public BaseClass()
        {
        }

        public BaseClass(DerivedClass derivedClass)
        {
            foreach (PropertyInfo sourcePropertyInfo in derivedClass.GetType().GetProperties())
            {
                var destPropertyInfo = GetType().GetProperty(sourcePropertyInfo.Name);
                if (destPropertyInfo != null && destPropertyInfo.CanWrite)
                    destPropertyInfo.SetValue(
                        this,
                        sourcePropertyInfo.GetValue(derivedClass, null),
                        null);

            }
        }
        private string _id = string.Empty;
        public string Id
        {
            get { return _id; }
            set { _id = value; }
        }
    }

    public static class Json
    {
        public static string Serialize(object obj)
        {
            //var jObject = new JObject();
            var json = string.Empty;
            foreach (var propertyInfo in obj.GetType().GetProperties())
            {
                if (propertyInfo.IsDefined(typeof(XmlIgnoreAttribute), false))
                    continue;

                var value = propertyInfo.GetValue(obj);
                var name = propertyInfo.Name;
                //jObject.Add(name, JToken.FromObject(value));
                json += $"{name}\t{value}" + Environment.NewLine;
            }

            //var json = jObject.ToString();
            return json;
        }
    }
}

Ausgabe:
Test 1: ID, TestValue
Test 2: TestValue, ID
Test 3: TestValue, ID
Test 4: TestValue, ID

Dass ich die "DerivedClass" global vorhalte, könnte das erklären. Dann hätte aber Test4 wieder identisch zu Test1 laufen müssen?!?

Bonus Info: Unter Xamarin Android startet die Ausgabe immer mit ID, in einer Console oder Xamarin UWP kommt es zu o.g. Verhalten.

Über die Implementierung der Gegenseite müssen wir nicht sprechen. Da habe ich leider keinen Einfluss drauf 😦

P
441 Beiträge seit 2014
vor 5 Jahren

Hi,

wenn du nur die Reihenfolge der Properties beeinflussen musst, warum erstellst du dir da nicht einen eigenen Converter für JSON.NET ?

https://www.newtonsoft.com/json/help/html/CustomJsonConverter.htm
Das wäre jetzt meine erste Idee.

Wenn du unbedingt mit Reflection arbeiten willst, dann kannst du die Properties doch einfach sortieren, bevor du den Json String zusammenbaust (Abgesehen davon, dass strings mit += zusammensetzen gerade in schleifen problematisch ist).

6.911 Beiträge seit 2009
vor 5 Jahren

Hallo trib,

in Member Order Returned by GetFields, GetMethods … findest du die Hintergrundinfo dazu. Kurz: es geht darum wie das Reflektion-System die Daten intern cached.

mfG Gü

Stellt fachliche Fragen bitte im Forum, damit von den Antworten alle profitieren. Daher beantworte ich solche Fragen nicht per PM.

"Alle sagten, das geht nicht! Dann kam einer, der wusste das nicht - und hat's gemacht!"

3.003 Beiträge seit 2006
vor 5 Jahren

wenn du nur die Reihenfolge der Properties beeinflussen musst, warum erstellst du dir da nicht einen eigenen Converter für JSON.NET ?

Nicht einmal so weit müsste er gehen. JSON.NET kennt natürlich Reihenfolgen. Und wenn er JSON.NET nicht benutzen will, müsste er halt ein eigenes Attribut verewenden und dann bei dem Reflection-Zirkus benutzen, um die reflektierten Eigenschaften zu sortieren.

Das macht das Beharren darauf, eine etablierte (und performante), komplette JSON-Serialisierung unbedingt ignorieren zu wollen, nicht unbedingt nachvollziehbarer.

LaTino

"Furlow, is it always about money?"
"Is there anything else? I mean, how much sex can you have?"
"Don't know. I haven't maxed out yet."
(Furlow & Crichton, Farscape)

T
trib Themenstarter:in
708 Beiträge seit 2008
vor 5 Jahren

Guten Morgen,
danke für Eure Antworten.

warum erstellst du dir da nicht einen eigenen Converter für JSON.NET ?

Da ich erstens verstehen möchte weshalb der selbe Code nacheinander ein anderes Verhalten zeigt und zweitens wollte ich gerne vermeiden, dass ich die Properties der Klasse kennen muss. Sprich das Feld "Id".
Am Converter wird aber letztendlich wohl wenig vorbeiführen.

Wenn du unbedingt mit Reflection arbeiten willst, dann kannst du die Properties doch einfach sortieren, bevor du den Json String zusammenbaust (Abgesehen davon, dass strings mit += zusammensetzen gerade in schleifen problematisch ist).

Wo kommt diese These her? Dachte eigentlich die Stelle "Der Code stark vereinfacht:" und die auskommentierten Zeilen, die auf JSON.Net hinweisen, reichen aus um darzustellen, dass es hierbei nur um das Prinzip, um Beispielcode geht.

Aber auch LaTinos Aussage lässt darauf schließen, dass dem offenbar nicht so ist.

@gfoidl: Danke, dass sind exakt die Info´s die ich gesucht habe, bzw. die aus der MSDN für GetProperties() nicht hervorgegangen sind.

3.003 Beiträge seit 2006
vor 5 Jahren

Nunja - mir fällt auf die Schnelle nur ein Grund ein, eine JSON-Serialisierung selbst zu machen: zum Lernen. Dafür fallen mir aber sofort etwa ein Dutzend falsche Gründe ein, es selbst zu machen. Daher meine Skepsis, ob es sich bei deiner Frage nicht um ein Problem handelt, dass tiefer liegt als die ursprüngliche Frage.

Und wie gesagt, du kannst Serialisierung-Metainformationen mit Hilfe von Attributen an die Klasse hängen, so wie das bei jeder Serialisierung möglich ist. Das erspart dir die Mühe eines eigenen Converteres und nebenbei sorgt es auch dafür, dass du (i.e. der Serialisierer) die Properties "kennen" muss.

Tatsächlich rührt mein Misstrauen gegenüber deinem Code aber hiervon:


public BaseClass(DerivedClass derivedClass)
        {
            foreach (PropertyInfo sourcePropertyInfo in derivedClass.GetType().GetProperties())
            {
                var destPropertyInfo = GetType().GetProperty(sourcePropertyInfo.Name);
                if (destPropertyInfo != null && destPropertyInfo.CanWrite)
                    destPropertyInfo.SetValue(
                        this,
                        sourcePropertyInfo.GetValue(derivedClass, null),
                        null);

            }
        }

Das ist das Gruseligste, was ich seit einiger Zeit gesehen habe. Zum einen erbt BaseClass sowieso von DerivedClass (btw: Base wäre eigentlich die Basis, und Derived die geerbte, also alles genau umgedreht wie bei dir), d.h. du kannst dir den ganzen Zauber sparen, weil die Properties sowieso vorhanden sind und du an der Stelle die konkrete Klasse gerade in der Hand hältst. Das sieht für mich nach einem Konstrukt aus, das Wissenslücken in der OOP kompensieren soll. Kann man machen, dann muss man sich aber auch Kritik gefallen lassen.

LaTino

"Furlow, is it always about money?"
"Is there anything else? I mean, how much sex can you have?"
"Don't know. I haven't maxed out yet."
(Furlow & Crichton, Farscape)