Dlaczego klasa XML-Serializable potrzebuje konstruktora bez parametrów

173

Piszę kod do serializacji XML. Z poniższą funkcją.

public static string SerializeToXml(object obj)
{
    XmlSerializer serializer = new XmlSerializer(obj.GetType());
    using (StringWriter writer = new StringWriter())
    {
        serializer.Serialize(writer, obj);
        return writer.ToString();
    }
}

Jeśli argument jest wystąpieniem klasy bez konstruktora bez parametrów, zgłosi wyjątek.

Nieobsługiwany wyjątek: System.InvalidOperationException: nie można serializować CSharpConsole.Foo, ponieważ nie ma konstruktora bez parametrów. at System.Xml.Serialization.TypeDesc.CheckSupported () at System.Xml.Serialization.TypeScope.GetTypeDesc (Type type, MemberInfo sourc e, Boolean directReference, Boolean throwOnError) at System.Xml.Serialization.ModelScope.GetType (TypeModelScope.GetType Boolean direct Reference) w System.Xml.Serialization.XmlReflectionImporter.ImportTypeMapping (Type type, XmlRootAttribute root, String defaultNamespace) at System.Xml.Serialization.XmlSerializer..ctor (Type type, String defaultName space) at System.Xml.Serialization. XmlSerializer..ctor (typ typu)

Dlaczego musi istnieć konstruktor bez parametrów, aby umożliwić pomyślną serializację XML?

EDYCJA: dzięki za odpowiedź cfeduke. Konstruktor bez parametrów może być prywatny lub wewnętrzny.

Morgan Cheng
źródło
1
Jeśli jesteś zainteresowany, znalazłem, jak tworzyć obiekty bez potrzeby konstruktora (patrz aktualizacja) - ale to w ogóle nie pomoże XmlSerializer - nadal tego wymaga. Może przydaje się do niestandardowego kodu.
Marc Gravell
1
XmlSerializerwymaga domyślnego konstruktora bez parametrów do deserializacji.
Amit Kumar Ghosh

Odpowiedzi:

243

Podczas deserializacji obiektu klasa odpowiedzialna za desserializację obiektu tworzy wystąpienie serializowanej klasy, a następnie przechodzi do wypełniania serializowanych pól i właściwości dopiero po uzyskaniu wystąpienia do wypełnienia.

Możesz utworzyć konstruktora privatelub internaljeśli chcesz, o ile jest on bez parametrów.

cfeduke
źródło
1
Och, więc mogę ustawić bez parametrów ctor jako prywatny lub wewnętrzny, a serializacja nadal działa. Dziękuję za odpowiedź.
Morgan Cheng
2
Tak, robię to często, chociaż przyjąłem do wiadomości, że publiczne konstruktory bez parametrów są świetne, ponieważ pozwalają na używanie "new ()" z typami generycznymi i nową składnią inicjalizacji. W przypadku konstruktorów z parametrami użyj statycznych metod fabryki lub implementacji wzorca konstruktora.
cfeduke
14
Wskazówka dotycząca ułatwień dostępu jest dobra, ale wyjaśnienie nie ma sensu w przypadku serializacji. Obiekt należy utworzyć tylko w celu deserializacji. Zaryzykowałbym przypuszczenie, że kod sprawdzający typ jest wbudowany w konstruktor XmlSerializer, ponieważ jedno wystąpienie może być używane w obie strony.
Tomer Gabel
7
@jwg Jednym z przykładów jest wysyłanie XML do jakiejś usługi sieciowej i nie jesteś zainteresowany otrzymywaniem tych obiektów we własnym komponencie.
Tomer Gabel
5
Pamiętaj, że nawet jeśli utworzysz konstruktora bez parametrów privatelub internalwszystkie właściwości, których wartości zostały serializowane, muszą mieć publicmetody ustawiające.
chrnola
75

To jest ograniczenie XmlSerializer. Zauważ, że BinaryFormatteri DataContractSerializer nie wymagają tego - mogą stworzyć niezainicjowany obiekt z eteru i zainicjować go podczas deserializacji.

Ponieważ używasz XML, możesz rozważyć użycie DataContractSerializeri oznaczenie swojej klasy za pomocą [DataContract]/ [DataMember], ale zwróć uwagę, że zmienia to schemat (na przykład nie ma odpowiednika [XmlAttribute]- wszystko staje się elementami).

Aktualizacja: jeśli naprawdę chcesz wiedzieć, BinaryFormatteri inni używaj FormatterServices.GetUninitializedObject()do tworzenia obiektu bez wywoływania konstruktora. Prawdopodobnie niebezpieczne; Nie polecam używania go zbyt często ;-p Zobacz też uwagi na MSDN:

Ponieważ nowe wystąpienie obiektu jest inicjowane na zero i żadne konstruktory nie są uruchamiane, obiekt może nie reprezentować stanu, który jest uważany za prawidłowy przez ten obiekt. Bieżąca metoda powinna być używana tylko do deserializacji, gdy użytkownik zamierza natychmiast wypełnić wszystkie pola. Nie tworzy niezainicjowanego ciągu, ponieważ tworzenie pustej instancji niezmiennego typu nie ma sensu.

Mam własny silnik serializacji, ale nie zamierzam go używać FormatterServices; Lubię wiedzieć, że konstruktor ( dowolny konstruktor) faktycznie wykonał.

Marc Gravell
źródło
Dzięki za wskazówkę dotyczącą FormatterServices.GetUninitializedObject (Type). :)
Omer van Kloeten
6
Heh; okazuje się, że nie postępuję według własnych rad; protobuf-net ma (opcjonalnie) dozwolone FormatterServicesużywanie przez wieki
Marc Gravell
1
Ale nie rozumiem, jeśli nie określono konstruktora, kompilator tworzy publiczny konstruktor bez parametrów. Dlaczego więc nie jest to wystarczająco dobre dla silnika deserializacji XML?
toddmo
Jeśli chcę deserializować XML i zainicjować pewien obiekt za pomocą ich konstruktora (tak, aby elementy / atrybuty były dostarczane za pośrednictwem konstruktora), czy istnieje JAKIEKOLWIEK sposób, aby to osiągnąć? Czy nie ma sposobu na dostosowanie procesu serializacji tak, aby kompilował obiekty przy użyciu ich konstruktorów?
Shimmy Weitzhandler
1
@Shimmy nope; to nie jest obsługiwane. Tam jest IXmlSerializable , ale: co dzieje się po konstruktora i b: to jest bardzo brzydkie i trudne do uzyskania właściwej (zwłaszcza deserializacji) - zdecydowanie odradzam próby wykonania, ale: to nie będzie można korzystać z konstruktorów
Marc Gravell
4

Odpowiedź brzmi: bez żadnego powodu.

Wbrew swojej nazwie XmlSerializerklasa służy nie tylko do serializacji, ale także do deserializacji. Wykonuje pewne kontrole Twojej klasy, aby upewnić się, że zadziała, a niektóre z tych testów dotyczą tylko deserializacji, ale i tak je wszystkie, ponieważ nie wie, co zamierzasz zrobić później.

Sprawdzenie, którego klasa nie przejdzie, jest jednym z testów, które dotyczą tylko deserializacji. Oto co się dzieje:

  • Podczas deserializacji XmlSerializerklasa będzie musiała utworzyć wystąpienia Twojego typu.

  • Aby utworzyć instancję typu, należy wywołać konstruktor tego typu.

  • Jeśli nie zadeklarowałeś konstruktora, kompilator dostarczył już domyślny konstruktor bez parametrów, ale jeśli zadeklarowałeś konstruktora, jest to jedyny dostępny konstruktor.

  • Tak więc, jeśli zadeklarowany konstruktor akceptuje parametry, jedynym sposobem na utworzenie instancji klasy jest wywołanie konstruktora, który akceptuje parametry.

  • Jednak XmlSerializernie jest w stanie wywołać żadnego konstruktora z wyjątkiem konstruktora bez parametrów, ponieważ nie wie, jakie parametry przekazać do konstruktorów akceptujących parametry. Tak więc sprawdza, czy klasa ma konstruktora bez parametrów, a ponieważ go nie ma, kończy się niepowodzeniem.

Tak więc, gdyby XmlSerializerklasa została napisana w taki sposób, że wykonuje tylko kontrole związane z serializacją, to Twoja klasa przeszłaby, ponieważ nie ma absolutnie nic w serializacji, co wymagałoby posiadania konstruktora bez parametrów.

Jak inni już zauważyli, szybkim rozwiązaniem twojego problemu jest po prostu dodanie konstruktora bez parametrów. Niestety jest to również brudne rozwiązanie, ponieważ oznacza to, że nie można readonlyinicjować żadnych członków z parametrów konstruktora.

Oprócz tego XmlSerializerklasę można było napisać w taki sposób, aby umożliwić nawet deserializację klas bez konstruktorów bez parametrów. Wystarczyłoby skorzystać z „Wzorca projektowania metod fabrycznych” (Wikipedia) . Z wyglądu Microsoft uznał, że ten wzorzec projektowy jest zbyt zaawansowany dla programistów DotNet, których najwyraźniej nie należy niepotrzebnie mylić z takimi rzeczami. Dlatego programiści DotNet powinni według Microsoftu lepiej trzymać się konstruktorów bez parametrów.

Mike Nakis
źródło
Lol mówisz, For no good reason whatsoever,a następnie mówisz, XmlSerializer is not capable of invoking any constructor except a parameterless constructor, because it does not know what parameters to pass to constructors that accept parameters.jeśli nie wie, jakie parametry przekazać konstruktorowi, to skąd miałby wiedzieć, jakie parametry przekazać do fabryki? Albo jakiej fabryki użyć? Nie mogę sobie wyobrazić, że to narzędzie będzie prostsze w użyciu - chcesz, aby klasa została zdeserializowana, a następnie pozwól deserializatorowi utworzyć domyślną instancję, a następnie zapełnij każde oznaczone pole. Łatwo.
Chuck
0

Przede wszystkim to, co jest napisane w dokumentacji . Myślę, że jest to jedno z twoich pól klasy, a nie główne - i jak chcesz, aby deserialiser skonstruował go z powrotem bez konstrukcji bez parametrów?

Myślę, że istnieje obejście, aby uczynić konstruktora prywatnym.

Dmitrij Khalatov
źródło