Laden...

NativeAot Object zu Byte Array und Byte Array zu Object

Letzter Beitrag vor einem Jahr 14 Posts 756 Views
NativeAot Object zu Byte Array und Byte Array zu Object

Guten Tag zusammen,

ich benötige ein Object als Byte Array und muss dieses auch wieder von einem Byte Array zu einem Object zurückkonvertieren können. Das klingt erstmal nach keinem Problem, allerdings wird das Programm mit NativeAot kompiliert und dann funktioniert es nicht mehr. Hier mein Codeansatz:

BinarySerializer.cs

using System.Runtime.Serialization.Formatters.Binary;

public class BinarySerializer
{
    public static byte[] Serialize(object objectToSerialize)
    {
        var binaryFormatter = new BinaryFormatter();
        var memoryStream = new MemoryStream();
        binaryFormatter.Serialize(memoryStream, objectToSerialize);
        return memoryStream.ToArray();
    }

    public static object Deserialize(byte[] bytesToDeserialize)
    {
        var memoryStream = new MemoryStream();
        var binaryFormatter = new BinaryFormatter();
        memoryStream.Write(bytesToDeserialize, 0, bytesToDeserialize.Length);
        memoryStream.Position = 0;
        return binaryFormatter.Deserialize(memoryStream);
    }
}

Program.cs

[Serializable]
public class WeatherForecast
{
    public DateTimeOffset Date { get; set; }
    public int TemperatureCelsius { get; set; }
    public string? Summary { get; set; }
}

internal class Program
{
    static void Main(string[] args)
    {
        try
        {
            var weatherForecast = new WeatherForecast
            {
                Date = DateTime.Parse("2019-08-01"),
                TemperatureCelsius = 25,
                Summary = "Hot"
            };

            var byteArray = BinarySerializer.Serialize(weatherForecast);

            var restoredWeatherForecast = (WeatherForecast)BinarySerializer.Deserialize(byteArray);

            Console.WriteLine(restoredWeatherForecast.Summary);
            Console.WriteLine(restoredWeatherForecast.TemperatureCelsius);
        }
        catch (Exception e)
        {
            Console.WriteLine(e);
        }

        Console.ReadKey();
    }
}

.csproj

<Project Sdk="Microsoft.NET.Sdk">

  <PropertyGroup>
	  <PublishAot>true</PublishAot>
	  <EnableUnsafeBinaryFormatterSerialization>true</EnableUnsafeBinaryFormatterSerialization>
	  <OutputType>Exe</OutputType>
    <TargetFramework>net7.0</TargetFramework>
    <ImplicitUsings>enable</ImplicitUsings>
    <Nullable>enable</Nullable>
  </PropertyGroup>

</Project>

Wenn ich nun die IL exe starte. Ausgabe (wie gewünscht):

Hot
25

Wenn ich dann die native exe starte. Ausgabe:

System.TypeInitializationException: A type initializer threw an exception. To determine which type, inspect the InnerException's StackTrace property.
 ---> System.IO.FileNotFoundException: Cannot load assembly 'mscorlib, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089'. No metadata found for this assembly.
   at System.Reflection.Runtime.General.ReflectionCoreCallbacksImplementation.Load(AssemblyName, Boolean) + 0x7d
   at System.Runtime.Serialization.Formatters.Binary.Converter..cctor() + 0x2e3
   at System.Runtime.CompilerServices.ClassConstructorRunner.EnsureClassConstructorRun(StaticClassConstructionContext*) + 0xc6
   --- End of inner exception stack trace ---
   at System.Runtime.CompilerServices.ClassConstructorRunner.EnsureClassConstructorRun(StaticClassConstructionContext*) + 0x167
   at System.Runtime.CompilerServices.ClassConstructorRunner.CheckStaticClassConstructionReturnGCStaticBase(StaticClassConstructionContext*, Object) + 0xd
   at System.Runtime.Serialization.Formatters.Binary.ObjectWriter.GetAssemblyId(WriteObjectInfo) + 0x68
   at System.Runtime.Serialization.Formatters.Binary.ObjectWriter.Serialize(Object, BinaryFormatterWriter) + 0x1ed
   at System.Runtime.Serialization.Formatters.Binary.BinaryFormatter.Serialize(Stream, Object) + 0xef
   at BinarySerializer.Serialize(Object) + 0x64
   at Program.Main(String[]) + 0x88

Hoffe es gibt da eine Möglichkeit die die gewünschte Funktion mit NativeAot funktional macht. Welchen Klassen am Ende benutzt werden ist ja egal. Hauptsach es läuft. Aber nach Möglichkeit ohne Third Party Libraries. Bin auf eure Antworten gespannt.

Mit freundlichen Grüßen

Ich habe nochmal was anderes ausprobiert. Leider funktioniert es nur wieder ohne NativeAot.

using System.Text;
using System.Text.Json;

public class WeatherForecast
{
    public DateTimeOffset Date { get; set; }
    public int TemperatureCelsius { get; set; }
    public string? Summary { get; set; }
}

internal class Program
{
    static void Main(string[] args)
    {
        try
        {
            var weatherForecast = new WeatherForecast
            {
                Date = DateTime.Parse("2019-08-01"),
                TemperatureCelsius = 25,
                Summary = "Hot"
            };

            var jsonString = JsonSerializer.Serialize(weatherForecast);
            var byteArray = Encoding.UTF8.GetBytes(jsonString);

            var restoredJsonString = Encoding.UTF8.GetString(byteArray);
            var restoredWeatherForecast = JsonSerializer.Deserialize<WeatherForecast>(restoredJsonString);

            Console.WriteLine(restoredWeatherForecast.Summary);
            Console.WriteLine(restoredWeatherForecast.TemperatureCelsius);
        }
        catch (Exception e)
        {
            Console.WriteLine(e);
        }

        Console.ReadKey();
    }
}

Ausgabe IL exe:

Hot
25

Ausgabe native exe:

System.NotSupportedException: Deserialization of types without a parameterless constructor, a singular parameterized constructor, or a parameterized constructor annotated with 'JsonConstructorAttribute' is not supported. Type 'WeatherForecast'. Path: $ | LineNumber: 0 | BytePositionInLine: 1.
 ---> System.NotSupportedException: Deserialization of types without a parameterless constructor, a singular parameterized constructor, or a parameterized constructor annotated with 'JsonConstructorAttribute' is not supported. Type 'WeatherForecast'.
   --- End of inner exception stack trace ---
   at System.Text.Json.ThrowHelper.ThrowNotSupportedException(ReadStack&, Utf8JsonReader&, NotSupportedException) + 0x1fe
   at System.Text.Json.Serialization.Converters.ObjectDefaultConverter`1.OnTryRead(Utf8JsonReader&, Type, JsonSerializerOptions, ReadStack&, T&) + 0xb2
   at System.Text.Json.Serialization.JsonConverter`1.TryRead(Utf8JsonReader&, Type, JsonSerializerOptions, ReadStack&, T&) + 0x1ec
   at System.Text.Json.Serialization.JsonConverter`1.ReadCore(Utf8JsonReader&, JsonSerializerOptions, ReadStack&) + 0x34f
   at System.Text.Json.JsonSerializer.ReadFromSpan[TValue](ReadOnlySpan`1, JsonTypeInfo, Nullable`1) + 0x109
   at System.Text.Json.JsonSerializer.ReadFromSpan[TValue](ReadOnlySpan`1, JsonTypeInfo) + 0xf5
   at System.Text.Json.JsonSerializer.Deserialize[TValue](String, JsonSerializerOptions) + 0x58
   at Program.Main(String[]) + 0xe3

Binäre Serialisierung kann kein Native AOT.
Deshalb gibt's ja die EnableUnsafeBinaryFormatterSerialization Property, die ist standardmäßig false 😄

Aber warum überhaupt binär serialisieren?
Das gilt ja auch aus gutem Grund als obsolet.

Besser wäre, Du arbeitest mit JSON (System.Text.Json), dafür gibt's einen Sourcegenerator, mit dem dann gar kein Reflection mehr nötig ist, sodass es auch mit Native AOT funktioniert.
https://learn.microsoft.com/en-us/dotnet/standard/serialization/system-text-json/source-generation

Ich mein, am Ende ist JSON auch nur binär, bloß mit UTF8 kodiert.
Außerdem ist es leichter zu parsen und auch für Menschen leichter zu lesen.
Und wenn Du unbedingt ein unleserliches Format haben will, verschlüssle es, oder mach ein ZIP Archiv daraus.

NuGet Packages im Code auslesen
lock Alternative für async/await

Beim CleanCode zählen nicht die Regeln, sondern dass wir uns mit diesen Regeln befassen, selbst wenn wir sie nicht befolgen - hoffentlich nach reiflichen Überlegungen.

Und Fehlermeldungen vielleicht lesen. Da steht das Problem - und die Lösung - ja drin.

System.NotSupportedException: Deserialization of types without a parameterless constructor, a singular parameterized constructor, or a parameterized constructor

@Palladin007: Hmmm ja, das entspricht dann meinem zweiten Ansatz bzw. zweiten Beitrag. Leider funktioniert das auch nicht. Siehst du den Fehler, den ich gemacht habe?

@Abt: Also entweder verstehe ich die Fehlermeldung nicht richtig oder das betrifft quasi jede Art von Konstruktor. Ist also nicht umsetzbar.

Also irgendwie habe ich glaube ich gerade ein Brett vorm Kopf. Sollte folgendes nicht eigentlich funktionieren?

using System.Text;
using System.Text.Json;
using System.Text.Json.Serialization;

public class WeatherForecast
{
    [JsonConstructorAttribute]
    public WeatherForecast(DateTimeOffset Date, int TemperatureCelsius, string? Summary)
    {
        this.Date = Date;
        this.TemperatureCelsius = TemperatureCelsius;
        this.Summary = Summary;
    }

    public DateTimeOffset Date { get; set; }
    public int TemperatureCelsius { get; set; }
    public string? Summary { get; set; }
}

internal class Program
{
    static void Main(string[] args)
    {
        try
        {
            var weatherForecast = new WeatherForecast(DateTime.Parse("2019-08-01"), 25, "Hot");

            var jsonString = JsonSerializer.Serialize(weatherForecast);
            var byteArray = Encoding.UTF8.GetBytes(jsonString);

            var restoredJsonString = Encoding.UTF8.GetString(byteArray);
            var restoredWeatherForecast = JsonSerializer.Deserialize<WeatherForecast>(restoredJsonString);

            Console.WriteLine(restoredWeatherForecast.Summary);
            Console.WriteLine(restoredWeatherForecast.TemperatureCelsius);
        }
        catch (Exception e)
        {
            Console.WriteLine(e);
        }

        Console.ReadKey();
    }
}

Bekomme aber immer noch folgende Exception:

System.NotSupportedException: Deserialization of types without a parameterless constructor, a singular parameterized constructor, or a parameterized constructor annotated with 'JsonConstructorAttribute' is not supported. Type 'WeatherForecast'. Path: $ | LineNumber: 0 | BytePositionInLine: 1.
 ---> System.NotSupportedException: Deserialization of types without a parameterless constructor, a singular parameterized constructor, or a parameterized constructor annotated with 'JsonConstructorAttribute' is not supported. Type 'WeatherForecast'.
   --- End of inner exception stack trace ---
   at System.Text.Json.ThrowHelper.ThrowNotSupportedException(ReadStack&, Utf8JsonReader&, NotSupportedException) + 0x1fe
   at System.Text.Json.Serialization.Converters.ObjectDefaultConverter`1.OnTryRead(Utf8JsonReader&, Type, JsonSerializerOptions, ReadStack&, T&) + 0xb2
   at System.Text.Json.Serialization.JsonConverter`1.TryRead(Utf8JsonReader&, Type, JsonSerializerOptions, ReadStack&, T&) + 0x1ec
   at System.Text.Json.Serialization.JsonConverter`1.ReadCore(Utf8JsonReader&, JsonSerializerOptions, ReadStack&) + 0x34f
   at System.Text.Json.JsonSerializer.ReadFromSpan[TValue](ReadOnlySpan`1, JsonTypeInfo, Nullable`1) + 0x109
   at System.Text.Json.JsonSerializer.ReadFromSpan[TValue](ReadOnlySpan`1, JsonTypeInfo) + 0xf5
   at System.Text.Json.JsonSerializer.Deserialize[TValue](String, JsonSerializerOptions) + 0x58
   at Program.Main(String[]) + 0xe3

Funktioniert einwandfrei.

    public class WeatherForecast
    {
        public WeatherForecast() { }
        public WeatherForecast(DateTimeOffset date, int temperatureCelsius, string? summary)
        {
            Date = date;
            TemperatureCelsius = temperatureCelsius;
            Summary = summary;
        }

        public DateTimeOffset Date { get; set; }
        public int TemperatureCelsius { get; set; }
        public string? Summary { get; set; }
    }

    internal class Program
    {
        static void Main(string[] args)
        {
            WeatherForecast weatherForecast = new(DateTime.Parse("2019-08-01"), 25, "Hot");

            string jsonString = JsonSerializer.Serialize(weatherForecast);
            Console.WriteLine("Object as Json: " + jsonString);

            byte[] byteArray = Encoding.UTF8.GetBytes(jsonString);
            
            string restoredJsonString = Encoding.UTF8.GetString(byteArray);
            Console.WriteLine("Restored Json: " + restoredJsonString);

            WeatherForecast restoredWeatherForecast = JsonSerializer.Deserialize<WeatherForecast>(restoredJsonString);
            Console.WriteLine("From Deserialized " + restoredWeatherForecast.Summary);
        }
    }

Console Output:

Object as Json: {"Date":"2019-08-01T00:00:00+02:00","TemperatureCelsius":25,"Summary":"Hot"}
Restored Json: {"Date":"2019-08-01T00:00:00+02:00","TemperatureCelsius":25,"Summary":"Hot"}
From Deserialized Hot

C:\source\temp\ConsoleApp13\ConsoleApp13\bin\Debug\net8.0\ConsoleApp13.exe (process 126296) exited with code 0.

Ich glaube du hast vergessen in deiner .csproj <PublishAot>true</PublishAot> hinzuzufügen. Bei deinem Quellcode bekomme ich folgenden Output:

Object as Json: {}
Restored Json: {}
Unhandled Exception: System.NotSupportedException: Deserialization of types without a parameterless constructor, a singular parameterized constructor, or a parameterized constructor annotated with 'JsonConstructorAttribute' is not supported. Type 'WeatherForecast'. Path: $ | LineNumber: 0 | BytePositionInLine: 1.
 ---> System.NotSupportedException: Deserialization of types without a parameterless constructor, a singular parameterized constructor, or a parameterized constructor annotated with 'JsonConstructorAttribute' is not supported. Type 'WeatherForecast'.
   --- End of inner exception stack trace ---
   at System.Text.Json.ThrowHelper.ThrowNotSupportedException(ReadStack&, Utf8JsonReader&, NotSupportedException) + 0x33a
   at System.Text.Json.Serialization.Converters.ObjectDefaultConverter`1.OnTryRead(Utf8JsonReader&, Type, JsonSerializerOptions, ReadStack&, T&) + 0xd3
   at System.Text.Json.Serialization.JsonConverter`1.TryRead(Utf8JsonReader&, Type, JsonSerializerOptions, ReadStack&, T&, Boolean&) + 0x225
   at System.Text.Json.Serialization.JsonConverter`1.ReadCore(Utf8JsonReader&, JsonSerializerOptions, ReadStack&) + 0x350
   at System.Text.Json.JsonSerializer.ReadFromSpan[TValue](ReadOnlySpan`1, JsonTypeInfo`1, Nullable`1) + 0xfe
   at System.Text.Json.JsonSerializer.ReadFromSpan[TValue](ReadOnlySpan`1, JsonTypeInfo`1) + 0x108
   at System.Text.Json.JsonSerializer.Deserialize[TValue](String, JsonSerializerOptions) + 0x4c
   at Program.Main(String[] args) + 0x11c
   at JsonSerializerConsole!<BaseAddress>+0x20ea69

Ne, hab ich nicht. Ist drin.
Wüsste jetzt nicht, was ich sonst für ein Setting vergessen hätte.

Auch die Exe als Release-Exe ohne Debugging/VS funktioniert einwandfrei.

Mein Fehler - das kann so gar nicht klappen. Hab auch gesehen, dass mein Publish nicht korrekt war.
Daher gings bei mir.

Kurz nachgelesen: Du musst Deine Serialisierung als Source Code Generator wählen, ansonsten funktioniert NativeAoT nicht, weil sonst Reflection.
Siehe Docs dazu: Specify source generation mode 
Siehe GitHub dazu: [Native-AOT] Using Json Serialize and Deserialize with Native AOT

Welche .NET Version kommt denn zum Einsatz?

Wenn ich die Ausgabe von @Abt richtig sehe, kommt dort 8.0 zum Einsatz.

@Abt: Funktioniert. Perfekt. Besten Dank!

JsonContext.cs

using System.Text.Json.Serialization;

[JsonSourceGenerationOptions(WriteIndented = true)]
[JsonSerializable(typeof(WeatherForecast))]
internal partial class SourceGenerationContext : JsonSerializerContext
{
}

Program.cs

using System.Text;
using System.Text.Json;

public class WeatherForecast
{
    public DateTimeOffset Date { get; set; }
    public int TemperatureCelsius { get; set; }
    public string? Summary { get; set; }
}

internal class Program
{
    static void Main(string[] args)
    {
        try
        {
            var weatherForecast = new WeatherForecast()
            {
                Date = DateTime.Parse("2019-08-01"),
                TemperatureCelsius = 25,
                Summary = "Hot"
            };

            var jsonString = JsonSerializer.Serialize(weatherForecast!, SourceGenerationContext.Default.WeatherForecast);
            var byteArray = Encoding.UTF8.GetBytes(jsonString);

            var restoredJsonString = Encoding.UTF8.GetString(byteArray);
            var restoredWeatherForecast = JsonSerializer.Deserialize<WeatherForecast>(restoredJsonString, SourceGenerationContext.Default.WeatherForecast);

            Console.WriteLine(restoredWeatherForecast.Summary);
            Console.WriteLine(restoredWeatherForecast.TemperatureCelsius);
        }
        catch (Exception e)
        {
            Console.WriteLine(e);
        }

        Console.ReadKey();
    }
}

Eine Frage hab ich allerdings noch. Was bewirkt das Ausrufezeichen hinter weatherForecast?

var jsonString = JsonSerializer.Serialize(weatherForecast!, SourceGenerationContext.Default.WeatherForecast);

@david.m: Funktioniert auch mit .NET 7.

Siehe ! (null-forgiving) operator (C# reference)

Hilft Dir qualitativ besseren und stabileren Code mit einer OOP-Sprache zu schreiben, in dem Du Code so schreibst, dass Du es nicht brauchst 😃

Ah okay. Danke.