Datacontractserializer Doesn't Call My Constructor

DataContractSerializer doesn't call my constructor?

DataContractSerializer (like BinaryFormatter) doesn't use any constructor. It creates the object as empty memory.

For example:

    Type type = typeof(Customer);
object obj = System.Runtime.Serialization.
FormatterServices.GetUninitializedObject(type);

The assumption is that the deserialization process (or callbacks if necessary) will fully initialize it.

How does WCF deserialization instantiate objects without calling a constructor?

FormatterServices.GetUninitializedObject() will create an instance without calling a constructor. I found this class by using Reflector and digging through some of the core .Net serialization classes.

I tested it using the sample code below and it looks like it works great:

using System;
using System.Reflection;
using System.Runtime.Serialization;

namespace NoConstructorThingy
{
class Program
{
static void Main()
{
// does not call ctor
var myClass = (MyClass)FormatterServices.GetUninitializedObject(typeof(MyClass));

Console.WriteLine(myClass.One); // writes "0", constructor not called
Console.WriteLine(myClass.Two); // writes "0", field initializer not called
}
}

public class MyClass
{
public MyClass()
{
Console.WriteLine("MyClass ctor called.");
One = 1;
}

public int One { get; private set; }
public readonly int Two = 2;
}
}

http://d3j5vwomefv46c.cloudfront.net/photos/large/687556261.png

Is a blank constructor required for WCF [DataContract] classes? Why?

What exactly is that block of code I'm writing

Item item = new Item {description = "Some description"};

Is equal and gets compiled to:

Item item = new Item();
item.description = "Some description";

So it requires a parameterless constructor. If the class doesn't have one, but has a parameterized one, you must use that one:

Item item = new Item("Some description");

Using named parameters, it would look like this:

Item item = new Item(description: "Some description");

You can still combine that with the object initializer syntax:

var item = new Item("Some description")
{
Foo = "bar"
};

Is a blank constructor required for [DataContract] classes?

Yes. The default serializer, DataContractSerializer, doesn't use reflection to instantiate a new instance, but still requires a parameterless constructor.

If it can't find a parameterless constructor, it can't instantiate the object. Well, it can, but it doesn't. So if you were to actually use this Item class in a service operation:

public void SomeOperation(Item item)
{
}

Then WCF will throw an exception once you invoke this operation from a client, because the serializer can't find a parameterless constructor on Item.

Why is my abstract base class's constructor not called when an object is initialized by the WCF deserializer?

WCF (and DataContractSerializer in particular) doesn't use constructors. No, really (it uses FormatterServices.GetUninitializedObject to create raw objects).

It is expected that all data will be initialized either by the serializer, or for non-serialized fields - by serialization callbacks that you add (for example, via [OnDeserialized]).

Why does the DataContractSerializer not work?

First, you cannot have a type that is both [Serializable] and [DataContract]. This is not recommended and is meaningless. Just have [DataContract]. For more information on why, see this post on the data contract programming model.

Anyway, the problem here is that you're actually trying to serialize a RuntimeType, because MessageType is represented as a RuntimeType. RuntimeType is an internal class that's a child of Type and that is not public, so that you can't knowingly refer to it as a known type. See What's the difference between System.Type and System.RuntimeType in C#? for more information on what RuntimeType is and why it is the way it is.

So, you have two options here:

  • Consider adding a KnownTypes attribute that takes a static method name. From your static method, you can return the various types you really want, including potentially RuntimeType if you use reflection.

  • The option I'd recommend is to make MessageType a TypeHandle (a RuntimeTypeHandle.) The advantage of this is that you can actually make RuntimeTyepHandle a known type since it's public. It's also serializable and deserializable just like any other type. See this excellent blog post on what I mean.

Trouble with DataContractSerializer

The problem here is that your class is derived from a collection, and as such, the DataContractSerializer serializes only its items, but not any extra properties, as stated here: No properties when using CollectionDataContract.

A workaround would be using the original (inherited) collection as a property, rather than inheriting from it:

public class MyOwnObservableCollection<T> : IDisposable
where T : IObjectWithChangeTracker, INotifyPropertyChanged
{
readonly ObservableCollection<T> originalCollection = new ObservableCollection<T>();
protected List<T> removedItems = = new List<T>();

[DataMember]
public List<T> RemovedItems
{
get { return this.removedItems;}
set { this.removedItems = value;}
}

[DataMember]
public ObservableCollection<T> OriginalCollection
{
get { return this.originalCollection; }
}

// ...
}

Can a readonly field be initialized when using DataContractSerializer?

I'm not sure doing this is a good idea, but you can change the value of a readonly field outside the constructor or field initializer by using reflection.

Putting something like:

typeof(MyType).GetField("Field").SetValue(this, value);

in your deserialization callback should work.

C# DataContract and Serializing readonly bool field to always be 'true'

You would like the field private readonly bool isVisible; to be initialized to true during deserialization. Unfortunately, DataContractSerializer doesn't call any constructor of your class so there is no obvious, easy way to do this.

Your proposed solution -- of always emitting <isVisible>true</isVisible> during serialization and taking advantage of the data contract serializer's ability to deserialize read-only field values -- is less than ideal because a badly crafted XML file, e.g. one where <isVisible> is false or even missing, could introduce unexpected behavior into your application.

Instead, consider the following alternatives that do not require serializing the value of isVisible:

  1. Modify the name and semantics of the read-only field so that the default value is the correct, desired value. In this case you would need to replace the current isVisible with private readonly bool isNotVisible;:

    [DataContract]
    public class MapItem
    {
    // Do not mark with [DataContract] as deserialized instances should always have the default value
    private readonly bool isNotVisible;

    public MapItem(bool isVisible)
    {
    this.isNotVisible = !isVisible;
    }

    public bool IsVisible { get { return !isNotVisible; } }
    }
  2. Set the field value in an [OnDeserializing] callback using reflection:

    [DataContract]
    public class MapItem
    {
    public MapItem(bool isVisible)
    {
    this.isVisible = isVisible;
    }

    // Do not mark with [DataContract] as deserialized instances should always have the value
    // set in the OnDeserializing() callback.
    private readonly bool isVisible;

    public bool IsVisible { get { return isVisible; } }

    [OnDeserializing]
    void OnDeserializing(StreamingContext context)
    {
    typeof(MapItem)
    .GetField(nameof(isVisible), BindingFlags.Instance | BindingFlags.NonPublic)
    .SetValue(this, true);
    }
    }
  3. Finally, you could implement ISerializable as this is supported by the data contract serializer, and initialize isVisible to true in the streaming constructor:

    [Serializable]
    public class MapItem : ISerializable
    {
    public MapItem(bool isVisible)
    {
    this.isVisible = isVisible;
    }

    private readonly bool isVisible;

    public bool IsVisible { get { return isVisible; } }

    #region ISerializable Members

    public virtual void GetObjectData(SerializationInfo info, StreamingContext context)
    {
    }

    #endregion

    protected MapItem(SerializationInfo info, StreamingContext context)
    {
    this.isVisible = true;
    }
    }

    However this is not recommended as you now would need to serialize all of your class's members manually.

Datacontract exception. Cannot be serialized

Because you have provided one or more initializing constructors, you will also need to add a parameterless (default) constructor.

i.e. You need to add:

[DataContract]
public class Occupant
{
// *** Needed only for Serialization
public Occupant() {}
...

This is because the default constructor disappears when you add an explicit constructor.

[The issue isn't with the method returning List<Occupant>, since methods aren't serialized).]



Related Topics



Leave a reply



Submit