Udostępnij za pośrednictwem


Serializacja w Orleans

Istnieją zasadniczo dwa rodzaje serializacji używane w programie Orleans:

  • Serializacja wywołań ziarna: służy do serializacji obiektów przekazywanych do i z ziarna.
  • Serializacja magazynu ziarna: służy do serializacji obiektów do i z systemów magazynowania.

Większość tego artykułu koncentruje się na serializacji wywołań ziarna za pośrednictwem struktury serializacji zawartej w programie Orleans. W sekcji Serializatory magazynowania ziarna omówiono serializacji magazynowania ziarna.

Użyj Orleans serializacji

Orleanszawiera zaawansowaną i rozszerzalną strukturę serializacji, nazywaną .Orleans Serializacja. Struktura serializacji zawarta w programie Orleans została zaprojektowana w celu spełnienia następujących celów:

  • Wysoka wydajność: serializator został zaprojektowany i zoptymalizowany pod kątem wydajności. Więcej szczegółów można znaleźć w tej prezentacji.
  • Wysoka wierność odwzorowania: serializator wiernie reprezentuje większość systemu typów platformy .NET, obejmując obsługę typów ogólnych, polimorfizm, hierarchie dziedziczenia, identyfikację obiektów oraz cykliczne grafy. Wskaźniki nie są obsługiwane, ponieważ nie są przenośne między procesami.
  • Elastyczność: Można dostosować serializator do obsługi bibliotek innych firm, tworząc zastępcze lub delegując do zewnętrznych bibliotek serializacji, takich jak System.Text.Json, Newtonsoft.Json i Google.Protobuf.
  • Tolerancja wersji: serializator pozwala na rozwój typów aplikacji w czasie, obsługując:
    • Dodawanie i usuwanie członków
    • Podklasy
    • Rozszerzanie i zawężenie liczbowe (np. int do/z long, float do/z double)
    • Zmienianie nazw typów

Reprezentacja typów o dużej wierności jest dość rzadka dla serializatorów, więc niektóre punkty wymagają dalszego wyjaśnienia.

  1. Typy dynamiczne i dowolny polimorfizm: Orleans nie wymusza ograniczeń typów przekazywanych w wywołaniach ziarna i utrzymuje dynamiczny charakter rzeczywistego typu danych. Oznacza to na przykład, że jeśli metoda w interfejsie ziarna jest zadeklarowana do akceptowania IDictionary, ale w czasie wykonywania nadawca przekazuje SortedDictionary<TKey,TValue>, odbiorca rzeczywiście otrzymuje SortedDictionary (mimo że interfejs "kontrakt statyczny"/ziarno nie określił tego zachowania).

  2. Utrzymywanie tożsamości obiektu: jeśli ten sam obiekt jest przekazywany wiele razy w argumentach wywołania ziarna lub jest pośrednio wskazywany na więcej niż raz z argumentów, Orleans serializuje go tylko raz. Po stronie odbiorcy Orleans przywraca wszystkie odwołania poprawnie, tak aby dwa wskaźniki do tego samego obiektu nadal wskazywały ten sam obiekt po deserializacji. Zachowanie tożsamości obiektu jest ważne w scenariuszach, takich jak następujące: Wyobraź sobie, że ziarno A wysyła słownik ze 100 wpisami do ziarna B, a 10 kluczy w słowniku wskazuje ten sam obiekt, obj, po stronie A. Bez zachowania tożsamości obiektu B otrzyma słownik zawierający 100 wpisów z tymi 10 kluczami wskazującymi na 10 różnych klonów objobiektu . Po zachowaniu tożsamości obiektu słownik po stronie B wygląda dokładnie tak samo jak po stronie A, z tymi 10 kluczami wskazującymi pojedynczy obiekt obj. Należy pamiętać, że ponieważ domyślne implementacje kodu skrótu ciągu na platformie .NET są losowe dla każdego procesu, kolejność wartości w słownikach i zestawach skrótów (na przykład) może nie zostać zachowana.

Aby zapewnić odporność na wersje, serializator wymaga jawnego określenia, które typy i składowe są serializowane. Staraliśmy się, aby było to tak bezbolesne, jak to możliwe. Oznacz wszystkie typy możliwe do serializacji za pomocą Orleans.GenerateSerializerAttribute polecenia , aby Orleans wygenerować kod serializatora dla danego typu. Po wykonaniu tego możesz użyć dołączonej poprawki kodu, aby dodać wymagane Orleans.IdAttribute do serializowalnych elementów w twoich typach, jak pokazano tutaj.

Animowany obraz sugerowanej i zastosowanej poprawki kodu dla atrybutu GenerateSerializerAttribute, gdy typ zawierający nie ma atrybutów IdAttribute w swoich członkach.

Oto przykład typu możliwego do serializacji w Orleans, pokazujący sposób stosowania atrybutów.

[GenerateSerializer]
public class Employee
{
    [Id(0)]
    public string Name { get; set; }
}

Orleans obsługuje dziedziczenie i serializuje poszczególne warstwy w strukturze hierarchii osobno, umożliwiając im posiadanie unikatowych identyfikatorów członkowskich.

[GenerateSerializer]
public class Publication
{
    [Id(0)]
    public string Title { get; set; }
}

[GenerateSerializer]
public class Book : Publication
{
    [Id(0)]
    public string ISBN { get; set; }
}

W poprzednim kodzie należy pamiętać, że zarówno Publication, jak i Book mają elementy członkowskie z [Id(0)], chociaż Book dziedziczy z Publication. Jest to zalecana praktyka Orleans, ponieważ identyfikatory składowych są związane z poziomem dziedziczenia, a nie z typem jako całością. Można dodawać i usuwać elementy członkowskie z Publication i Book niezależnie, ale nie można wstawić nowej klasy bazowej do hierarchii po wdrożeniu aplikacji bez specjalnej uwagi.

Orleans Obsługuje również serializowanie typów z elementami internal, private i readonly, takimi jak w tym przykładzie:

[GenerateSerializer]
public struct MyCustomStruct
{
    public MyCustom(int intProperty, int intField)
    {
        IntProperty = intProperty;
        _intField = intField;
    }

    [Id(0)]
    public int IntProperty { get; }

    [Id(1)] private readonly int _intField;
    public int GetIntField() => _intField;

    public override string ToString() => $"{nameof(_intField)}: {_intField}, {nameof(IntProperty)}: {IntProperty}";
}

Domyślnie Orleans serializuje twój typ, kodując jego pełną nazwę. Możesz to zastąpić, dodając element Orleans.AliasAttribute. W ten sposób typ jest serializowany przy użyciu nazwy odpornej na zmianę nazwy bazowej klasy lub przenoszenia go między zestawami. Aliasy typów są globalnie ograniczone i nie można mieć dwóch aliasów o tej samej wartości w aplikacji. W przypadku typów ogólnych wartość aliasu musi zawierać liczbę parametrów ogólnych poprzedzonych znakiem odwrotnym; na przykład MyGenericType<T, U> może mieć alias [Alias("mytype`2")].

Serializacja record typów

Składowe zdefiniowane w konstruktorze podstawowym rekordu mają domyślnie niejawne identyfikatory. Innymi słowy, Orleans obsługuje serializowanie record typów. Oznacza to, że nie można zmienić kolejności parametrów dla już wdrożonego typu, ponieważ narusza to zgodność z poprzednimi wersjami aplikacji (w scenariuszu aktualizacji kroczącej) oraz z serializowanymi instancjami tego typu w pamięci i strumieniach. Elementy członkowskie zdefiniowane w treści typu rekordu nie dzielą tożsamości z głównymi parametrami konstruktora.

[GenerateSerializer]
public record MyRecord(string A, string B)
{
    // ID 0 won't clash with A in primary constructor as they don't share identities
    [Id(0)]
    public string C { get; init; }
}

Jeśli nie chcesz, aby podstawowe parametry konstruktora były automatycznie dołączane jako pola możliwe do serializacji, użyj polecenia [GenerateSerializer(IncludePrimaryConstructorParameters = false)].

Zastępcze do serializacji typów obcych

Czasami może być konieczne przekazanie typów między ziarnami, nad którymi nie masz pełnej kontroli. W takich przypadkach ręczna konwersja do i z niestandardowego typu w kodzie aplikacji może być niepraktyczna. Orleans oferuje rozwiązanie dla takich sytuacji: typy zastępcze. Typy zastępcze są serializowane zamiast ich typu docelowego i posiadają funkcjonalność, aby konwertować na typ docelowy i z niego. Rozważmy następujący przykład typu zagranicznego oraz odpowiedniego zastępnika i konwertera:

// This is the foreign type, which you do not have control over.
public struct MyForeignLibraryValueType
{
    public MyForeignLibraryValueType(int num, string str, DateTimeOffset dto)
    {
        Num = num;
        String = str;
        DateTimeOffset = dto;
    }

    public int Num { get; }
    public string String { get; }
    public DateTimeOffset DateTimeOffset { get; }
}

// This is the surrogate which will act as a stand-in for the foreign type.
// Surrogates should use plain fields instead of properties for better performance.
[GenerateSerializer]
public struct MyForeignLibraryValueTypeSurrogate
{
    [Id(0)]
    public int Num;

    [Id(1)]
    public string String;

    [Id(2)]
    public DateTimeOffset DateTimeOffset;
}

// This is a converter that converts between the surrogate and the foreign type.
[RegisterConverter]
public sealed class MyForeignLibraryValueTypeSurrogateConverter :
    IConverter<MyForeignLibraryValueType, MyForeignLibraryValueTypeSurrogate>
{
    public MyForeignLibraryValueType ConvertFromSurrogate(
        in MyForeignLibraryValueTypeSurrogate surrogate) =>
        new(surrogate.Num, surrogate.String, surrogate.DateTimeOffset);

    public MyForeignLibraryValueTypeSurrogate ConvertToSurrogate(
        in MyForeignLibraryValueType value) =>
        new()
        {
            Num = value.Num,
            String = value.String,
            DateTimeOffset = value.DateTimeOffset
        };
}

W poprzednim kodzie:

  • MyForeignLibraryValueType jest typem poza twoją kontrolą, zdefiniowanym w używanej bibliotece.
  • MyForeignLibraryValueTypeSurrogate stanowi mapowanie typu zastępczego na MyForeignLibraryValueType.
  • RegisterConverterAttribute określa, że MyForeignLibraryValueTypeSurrogateConverter działa jako konwerter do mapowania między dwoma typami. Klasa implementuje IConverter<TValue,TSurrogate> interfejs.

Orleans obsługuje serializacji typów w hierarchiach typów (typy pochodzące z innych typów). Jeśli typ obcy może pojawić się w hierarchii typów (np. jako klasa bazowa dla jednego z własnych typów), należy dodatkowo zaimplementować Orleans.IPopulator<TValue,TSurrogate> interfejs. Rozważmy następujący przykład:

// The foreign type is not sealed, allowing other types to inherit from it.
public class MyForeignLibraryType
{
    public MyForeignLibraryType() { }

    public MyForeignLibraryType(int num, string str, DateTimeOffset dto)
    {
        Num = num;
        String = str;
        DateTimeOffset = dto;
    }

    public int Num { get; set; }
    public string String { get; set; }
    public DateTimeOffset DateTimeOffset { get; set; }
}

// The surrogate is defined as it was in the previous example.
[GenerateSerializer]
public struct MyForeignLibraryTypeSurrogate
{
    [Id(0)]
    public int Num;

    [Id(1)]
    public string String;

    [Id(2)]
    public DateTimeOffset DateTimeOffset;
}

// Implement the IConverter and IPopulator interfaces on the converter.
[RegisterConverter]
public sealed class MyForeignLibraryTypeSurrogateConverter :
    IConverter<MyForeignLibraryType, MyForeignLibraryTypeSurrogate>,
    IPopulator<MyForeignLibraryType, MyForeignLibraryTypeSurrogate>
{
    public MyForeignLibraryType ConvertFromSurrogate(
        in MyForeignLibraryTypeSurrogate surrogate) =>
        new(surrogate.Num, surrogate.String, surrogate.DateTimeOffset);

    public MyForeignLibraryTypeSurrogate ConvertToSurrogate(
        in MyForeignLibraryType value) =>
        new()
    {
        Num = value.Num,
        String = value.String,
        DateTimeOffset = value.DateTimeOffset
    };

    public void Populate(
        in MyForeignLibraryTypeSurrogate surrogate, MyForeignLibraryType value)
    {
        value.Num = surrogate.Num;
        value.String = surrogate.String;
        value.DateTimeOffset = surrogate.DateTimeOffset;
    }
}

// Application types can inherit from the foreign type, assuming they're not sealed
// since Orleans knows how to serialize it.
[GenerateSerializer]
public sealed class DerivedFromMyForeignLibraryType : MyForeignLibraryType
{
    public DerivedFromMyForeignLibraryType() { }

    public DerivedFromMyForeignLibraryType(
        int intValue, int num, string str, DateTimeOffset dto) : base(num, str, dto)
    {
        IntValue = intValue;
    }

    [Id(0)]
    public int IntValue { get; set; }
}

Reguły przechowywania wersji

Tolerancja wersji jest obsługiwana, pod warunkiem przestrzegania zestawu reguł podczas modyfikowania typów. Jeśli znasz systemy takie jak Google Protocol Buffers (Protobuf), te reguły będą ci znane.

Typy złożone (class & struct)

  • Dziedziczenie jest obsługiwane, ale modyfikowanie hierarchii dziedziczenia obiektu nie jest obsługiwane. Nie można dodawać, zmieniać ani usuwać klasy bazowej klasy.
  • Z wyjątkiem niektórych typów liczbowych opisanych w poniższej sekcji Numeryczne nie można zmienić typów pól.
  • Możesz dodawać lub usuwać pola w dowolnym momencie w hierarchii dziedziczenia.
  • Nie można zmienić identyfikatorów pól.
  • Identyfikatory pól muszą być unikatowe dla każdego poziomu w hierarchii typów, ale mogą być używane ponownie między klasami podstawowymi i podklasami. Na przykład Base klasa może zadeklarować pole o identyfikatorze 0, a Sub : Base klasa może zadeklarować inne pole o tym samym identyfikatorze. 0

Liczby

  • Nie można zmienić znakowości pola liczbowego.
    • Konwersje między elementami int i uint są nieprawidłowe.
  • Możesz zmienić szerokość pola liczbowego.
    • Na przykład obsługiwane są konwersje z int do long lub ulong do ushort .
    • Konwersje zawężające szerokość zgłaszają wyjątek, jeśli wartość pola w środowisku uruchomieniowym powoduje przepełnienie.
    • Konwersja z ulong na ushort jest obsługiwana tylko wtedy, gdy wartość środowiska uruchomieniowego jest mniejsza niż ushort.MaxValue.
    • Konwersje z double do float są obsługiwane tylko wtedy, gdy wartość środowiska uruchomieniowego mieści się między float.MinValue i float.MaxValue.
    • Podobnie w przypadku elementu decimal, który ma węższy zakres niż double i float.

Kopiarki

Orleans domyślnie promuje bezpieczeństwo, w tym ochronę przed niektórymi typami błędów współbieżności. W szczególności Orleans natychmiast kopiuje obiekty przekazywane w wywołaniach ziarna domyślnie. Orleans. Serializacja ułatwia kopiowanie. Podczas gdy stosujesz Orleans.CodeGeneration.GenerateSerializerAttribute do typu, Orleans tworzy również kopiarki dla tego typu. Orleans unika kopiowania typów lub pojedynczych elementów członkowskich oznaczonych jako ImmutableAttribute. Aby uzyskać więcej informacji, zobacz Serializacja niezmiennych typów w programie Orleans.

Najlepsze rozwiązania dotyczące serializacji

  • Podaj aliasy typów przy użyciu atrybutu [Alias("my-type")] . Typy z aliasami można zmieniać bez naruszania zgodności.

  • Nie zmieniaj record obiektu na zwykły class lub odwrotnie. Rekordy i klasy nie są reprezentowane identycznie, ponieważ rekordy mają składowe głównego konstruktora oprócz zwykłych składowych; dlatego nie można ich stosować zamiennie.

  • Nie dodawaj nowych typów do istniejącej hierarchii typów dla typu możliwego do serializacji. Nie można dodać nowej klasy bazowej do istniejącego typu. Możesz bezpiecznie dodać nową podklasę do istniejącego typu.

  • Zastąp użyciaSerializableAttribute za pomocą GenerateSerializerAttribute i odpowiednich deklaracji IdAttribute.

  • Zacznij wszystkie identyfikatory członków od zera dla każdego typu. Identyfikatory w podklasie i jej klasie bazowej mogą bezpiecznie nakładać się na siebie. Obie właściwości w poniższym przykładzie mają identyfikatory równe 0.

    [GenerateSerializer]
    public sealed class MyBaseClass
    {
        [Id(0)]
        public int MyBaseInt { get; set; }
    }
    
    [GenerateSerializer]
    public sealed class MySubClass : MyBaseClass
    {
        [Id(0)]
        public int MyBaseInt { get; set; }
    }
    
  • Jeśli to konieczne, należy rozszerzyć typy składowych liczbowych. Możesz poszerzyć sbyte do short do int do long.

    • Można zawęzić typy składowych liczbowych, ale jeśli obserwowane wartości nie mogą być poprawnie reprezentowane przez zawężony typ, powoduje to wyjątek środowiska uruchomieniowego. Na przykład int.MaxValue nie można przedstawić jako pole short, więc zawężenie pola int do short może spowodować wyjątek środowiska uruchomieniowego, jeśli taka wartość zostanie napotkana.
  • Nie zmieniaj znaku dla członka typu liczbowego. Nie można zmienić typu członka z uint na int lub z int na uint.

Serializatory magazynu ziarna

Orleans zawiera model trwałości oparty na dostawcach dla ziarna, do którego można uzyskać dostęp poprzez właściwość State lub wstrzykując jedną lub więcej wartości IPersistentState<TState> do ziarna. Przed Orleans 7.0 każdy dostawca miał inny mechanizm konfigurowania serializacji. W wersji Orleans 7.0 pojawił się teraz interfejs serializatora stanu ziarna przeznaczenia ogólnego, IGrainStorageSerializer, który oferuje spójny sposób dostosowywania serializacji stanu dla każdego dostawcy. Obsługiwani dostawcy przechowywania implementują wzorzec polegający na ustawieniu właściwości IStorageProviderSerializerOptions.GrainStorageSerializer w klasie opcji danego dostawcy, na przykład:

Serializacja magazynu ziarna jest obecnie domyślnie ustawiona na Newtonsoft.Json serializacji stanu. Tę właściwość można zastąpić, modyfikując tę właściwość w czasie konfiguracji. W poniższym przykładzie pokazano to przy użyciu OptionsBuilder<TOptions>:

siloBuilder.AddAzureBlobGrainStorage(
    "MyGrainStorage",
    (OptionsBuilder<AzureBlobStorageOptions> optionsBuilder) =>
    {
        optionsBuilder.Configure<IMySerializer>(
            (options, serializer) => options.GrainStorageSerializer = serializer);
    });

Aby uzyskać więcej informacji, zobacz OptionsBuilder API (Interfejs API programu OptionsBuilder).

Orleans ma zaawansowaną i rozszerzalną strukturę serializacji. Orleans serializuje typy danych przekazywane w komunikatach żądania ziarna i odpowiedzi, a także obiekty stanu trwałego ziarna. W ramach tej struktury Orleans automatycznie generuje kod serializacji dla tych typów danych. Oprócz generowania bardziej wydajnej serializacji/deserializacji dla typów już serializowalnych przez .NET, Orleans także próbuje generować serializatory dla typów używanych w interfejsach granicznych, które nie są serializowalne przez .NET. Struktura zawiera również zestaw wydajnych wbudowanych serializatorów dla często używanych typów: list, słowników, ciągów, elementów pierwotnych, tablic itp.

Dwie ważne cechy Orleansserializatora odróżniają ją od wielu innych struktur serializacji innych firm: dynamiczne typy/dowolną polimorfizm i tożsamość obiektów.

  1. Typy dynamiczne i dowolny polimorfizm: Orleans nie wymusza ograniczeń typów przekazywanych w wywołaniach ziarna i utrzymuje dynamiczny charakter rzeczywistego typu danych. Oznacza to na przykład, że jeśli metoda w interfejsie ziarna jest zadeklarowana do akceptowania IDictionary, ale w czasie wykonywania nadawca przekazuje SortedDictionary<TKey,TValue>, odbiorca rzeczywiście otrzymuje SortedDictionary (mimo że interfejs "kontrakt statyczny"/ziarno nie określił tego zachowania).

  2. Utrzymywanie tożsamości obiektu: jeśli ten sam obiekt jest przekazywany wiele razy w argumentach wywołania ziarna lub jest pośrednio wskazywany na więcej niż raz z argumentów, Orleans serializuje go tylko raz. Po stronie odbiorcy Orleans przywraca wszystkie odwołania poprawnie, tak aby dwa wskaźniki do tego samego obiektu nadal wskazywały ten sam obiekt po deserializacji. Zachowanie tożsamości obiektu jest ważne w scenariuszach, takich jak następujące: Wyobraź sobie, że ziarno A wysyła słownik ze 100 wpisami do ziarna B, a 10 kluczy w słowniku wskazuje ten sam obiekt, obj, po stronie A. Bez zachowania tożsamości obiektu B otrzyma słownik zawierający 100 wpisów z tymi 10 kluczami wskazującymi na 10 różnych klonów objobiektu . Po zachowaniu tożsamości obiektu słownik po stronie B wygląda dokładnie tak samo jak po stronie A, z tymi 10 kluczami wskazującymi pojedynczy obiekt obj.

Standardowy serializator binarny platformy .NET zapewnia dwa powyższe zachowania, dlatego ważne było, abyśmy również obsługiwali ten standard oraz znane i standardowe zachowanie w Orleans.

Wygenerowane serializatory

Orleans używa następujących reguł, aby zdecydować, które serializatory generować:

  1. Skanuj wszystkie typy we wszystkich zestawach odwołujących się do biblioteki podstawowej Orleans.
  2. Z tych zestawów wygeneruj serializatory dla typów bezpośrednio przywołynych w sygnaturach metody ziarna lub sygnaturach klasy stanu lub dla dowolnego typu oznaczonego znakiem SerializableAttribute.
  3. Ponadto interfejs ziarna lub projekt implementacji może wskazywać dowolne typy dla generowania serializacji, dodając atrybuty KnownTypeAttribute lub KnownAssemblyAttribute na poziomie zestawu. Informują one generator kodu o generowaniu serializatorów dla określonych typów lub wszystkich kwalifikujących się typów w zestawie. Aby uzyskać więcej informacji na temat atrybutów na poziomie zestawu, zobacz Stosowanie atrybutów na poziomie zestawu.

Serializacja zapasowa

Orleans umożliwia przesył dowolnych typów w czasie wykonywania. W związku z tym wbudowany generator kodu nie może określić całego zestawu typów, które będą przesyłane z wyprzedzeniem. Ponadto pewne typy nie mogą mieć wygenerowanych serializatorów, ponieważ są niedostępne (np. private) lub mają niedostępne pola (np. readonly). W związku z tym istnieje potrzeba serializacji typów na zasadzie just-in-time, które były nieoczekiwane lub dla których nie można było wygenerować serializatorów z wyprzedzeniem. Serializator odpowiedzialny za te typy jest nazywany serializatorem rezerwowym. Orleans jest dostarczany z dwoma serializatorami zapasowymi.

Skonfiguruj serializator rezerwowy za pomocą właściwości FallbackSerializationProvider na obu ClientConfiguration (klienta) i GlobalConfiguration (silosów).

// Client configuration
var clientConfiguration = new ClientConfiguration();
clientConfiguration.FallbackSerializationProvider =
    typeof(FantasticSerializer).GetTypeInfo();

// Global configuration
var globalConfiguration = new GlobalConfiguration();
globalConfiguration.FallbackSerializationProvider =
    typeof(FantasticSerializer).GetTypeInfo();

Alternatywnie określ rezerwowego dostawcę serializacji w konfiguracji XML:

<Messaging>
    <FallbackSerializationProvider
        Type="GreatCompany.FantasticFallbackSerializer, GreatCompany.SerializerAssembly"/>
</Messaging>

BinaryFormatterSerializer to domyślny serializator zapasowy.

Ostrzeżenie

Serializacja binarna z BinaryFormatter może być niebezpieczna. Aby uzyskać więcej informacji, zobacz Przewodnik po zabezpieczeniach BinaryFormatter i Przewodnik migracji BinaryFormatter.

Serializacja wyjątków

Wyjątki są serializowane przy użyciu serializatora rezerwowego. Przy domyślnej konfiguracji, BinaryFormatter jest rezerwowym serializatorem. W związku z tym należy postępować zgodnie ze wzorcem ISerializable , aby zapewnić poprawną serializacji wszystkich właściwości w typie wyjątku.

Przykład typu wyjątku z poprawnie zaimplementowaną serializacją:

[Serializable]
public class MyCustomException : Exception
{
    public string MyProperty { get; }

    public MyCustomException(string myProperty, string message)
        : base(message)
    {
        MyProperty = myProperty;
    }

    public MyCustomException(string transactionId, string message, Exception innerException)
        : base(message, innerException)
    {
        MyProperty = transactionId;
    }

    // Note: This is the constructor called by BinaryFormatter during deserialization
    public MyCustomException(SerializationInfo info, StreamingContext context)
        : base(info, context)
    {
        MyProperty = info.GetString(nameof(MyProperty));
    }

    // Note: This method is called by BinaryFormatter during serialization
    public override void GetObjectData(SerializationInfo info, StreamingContext context)
    {
        base.GetObjectData(info, context);
        info.AddValue(nameof(MyProperty), MyProperty);
    }
}

Najlepsze rozwiązania dotyczące serializacji

Serializacja służy dwóm podstawowym celom w programie Orleans:

  1. Jako format komunikacji do przesyłania danych między modułami i klientami podczas pracy programu.
  2. Jako format magazynu do utrwalania długotrwałych danych na potrzeby późniejszego pobierania.

Serializatory generowane przez Orleans program są odpowiednie do pierwszego celu ze względu na ich elastyczność, wydajność i wszechstronność. Nie są one tak odpowiednie do drugiego celu, ponieważ nie są jawnie odporne na wersje. Zalecamy skonfigurowanie serializatora odpornego na wersje, takiego jak Protocol Buffers, do danych trwałych. Bufory protokołów są obsługiwane za pośrednictwem Orleans.Serialization.ProtobufSerializer pakietu NuGet Microsoft.Orleans.OrleansGoogleUtils. Postępuj zgodnie z najlepszymi rozwiązaniami dla wybranego serializatora, aby zapewnić tolerancję wersji. Skonfiguruj serializatory innych firm, używając właściwości konfiguracji SerializationProviders, jak opisano powyżej.