1

I have a class with a field that is a delegate that I would like to serialize and deserialize.

It looks like so:

public delegate bool DistanceEqualityStrategy (Distance distance1, Distance distance2);

[JsonObject(MemberSerialization.Fields)]
public partial class Distance
{

    private double _intrinsicValue;

    [JsonIgnore]
    private DistanceEqualityStrategy _strategy;

    public Distance(double passedInput, DistanceEqualityStrategy passedStrategy = null)
    {
        _intrinsicValue = passedInput;
        _equalityStrategy = _chooseDefaultOrPassedStrategy(passedStrategy);
    }

    public Distance(Distance passedDistance)
    {
        _intrinsicValue = passedDistance._intrinsicValue;
        _equalityStrategy = passedDistance._equalityStrategy;
    }

    private static DistanceEqualityStrategy _chooseDefaultOrPassedStrategy(DistanceEqualityStrategy passedStrategy)
    {
        if (passedStrategy == null)
        {
            return EqualityStrategyImplementations.DefaultConstantEquality;
        }
        else
        {
            return passedStrategy;
        }
    }
}

I am having trouble understanding what is really going on when deserializing this object. How does the object get rebuilt? Does it call a specific constructor of the class when it attempts to recreate the object? It serializes just fine, but when the object is rebuilt, it sets the delegate to be null.

This leads me to the conclusion that I do not understand how this deserialization works, as it does not seem to use a constructor.

Can someone explain to me how an object is created on deserialization without using the constructors?

jth41
  • 3,808
  • 9
  • 59
  • 109
  • wouldn't it be best to show your code and or structure etc.. so that others won't have to implement their JetEye mind powers..? – MethodMan Jun 17 '15 at 21:30
  • 3
    It isn't very possible to serialize a delegate. How would that work? – SLaks Jun 17 '15 at 21:31
  • 2
    What @SLaks said: _think_ about what it would mean to serialize a delegate. What would get sent across the wire? If the delegate was set to `null`, no problem. But what if the delegate was set to a method on a class instance in the serializing code? Is this instance also getting serialized? If not, what would it mean when the delegate was executed? Would the executing code reach across the wire into your process to execute the method on your instance? What if there is no wire? What if the delegate were serialized into a file? – John Saunders Jun 17 '15 at 21:38
  • Serializing a delegate is just as meaningful as serializing any other object graph : a delegate is just a pair of (message to send, identity of object it is sent to). – Ben Voigt Jun 17 '15 at 22:02
  • @BenVoigt: a delegate involves executable code. That's a _bit_ different. How would you transmit the code? IL? How much code would you transmit? What if the code called other code which called other code? – John Saunders Jun 17 '15 at 22:04
  • @JohnSaunders: Just no. You can transmit an object without transmitting the code for its methods. Delegates present no special problem here. A delegate should be transmitted as the two pieces of data I mentioned: identification of which method (message) is sent, and to what object. The first could be encoded as a numeric MethodToken, or a string representation of the method signature, or whatever. Anything that identifies which instance method on the target object should be invoked. – Ben Voigt Jun 17 '15 at 22:11
  • @BenVoigt: you are assuming a common platform, and not the general case? I think a PHP program will have little concept of what a MethodToken is. And also that can't work in the general case. It restricts the set of objects the delegate can refer to to be a set understood by the receiver. – John Saunders Jun 17 '15 at 22:17
  • @John again these problems aren't specific to delegates in any way. If you try to send an object the recipient doesn't understand then you have a problem. – Ben Voigt Jun 18 '15 at 00:42
  • @BenVoigt: the difference is that there is no common, platform-independent format in which a delegate can be sent. That's especially true because it refers to a specific object, and not to a serialized object that may be sent across the wire. On my machine, a call to the delegate will operate on the instance in my machine. On the destination machine, it would, at best, operate on an instance on the destination machine. That instance is unlikely to be a full representation of the source object. Show me an example of delegates being deserialized and I'll shut up. – John Saunders Jun 18 '15 at 00:58

2 Answers2

1

You asked, Can someone explain to me how an object is created on deserialization without using the constructors?

The short answer is, FormatterServices.GetUninitializedObject() can be used do do this.

The POCO type shown in the question has been marked with [JsonObject(MemberSerialization.Fields)]. Applying MemberSerialization.Fields to a POCO type that maps to a JSON object triggers special a special construction rule. The rules for how Json.NET decides how to construct an instance of such a type are as follows:

  1. If [JsonConstructor] is set on a constructor, use that constructor.

  2. Next, in full trust only, when MemberSerialization.Fields is applied, or [Serializable] is applied and DefaultContractResolver.IgnoreSerializableAttribute == false, the special method FormatterServices.GetUninitializedObject() is used to allocate the object. None of the type's constructors are called.

  3. Next, if there is a public parameterless constructor, use it.

  4. Next, if there is a non-public parameterless constructor and JsonSerializerSettings.ConstructorHandling == ConstructorHandling.AllowNonPublicDefaultConstructor, use it.

  5. Next, if there is a single public parameterized constructor, use that constructor, matching the JSON object properties to constructor parameters by name (modulo case) then deserializing the matched properties to the constructor parameter type. Default values are passed when there is no matching JSON object property.

  6. Failing all of the above, the object cannot be constructed and an exception will get thrown during deserialization.

See the reference source for the logic. Dictionaries, dynamic objects, types that implement ISerializable, collections (types that map to a JSON array) and types that map to JSON primitives may have somewhat different rules.

Thus FormatterServices.GetUninitializedObject() is called instead of the type's constructors, and so its _equalityStrategy is never initialized.

As a workaround, since you are not serializing _equalityStrategy anyway, you can add an OnDeserialized callback that initializes it:

[JsonObject(MemberSerialization.Fields)]
public partial class Distance
{
    [JsonIgnore]
    private DistanceEqualityStrategy _equalityStrategy;

    [OnDeserialized]
    void OnDeserialized(StreamingContext context)
    {
        this._equalityStrategy = _chooseDefaultOrPassedStrategy(this._equalityStrategy);
    }
}

This behavior of Json.NET is not clearly documented. The most relevant paragraphs are from the Serialization Guide:

Finally, types can be serialized using a fields mode. All fields, both public and private, are serialized and properties are ignored. This can be specified by setting MemberSerialization.Fields on a type with the JsonObjectAttribute or by using the .NET SerializableAttribute and setting IgnoreSerializableAttribute on DefaultContractResolver to false.

And from the Json.NET 4.5 Release 8 release notes:

Change - The serializer now creates objects using GetUninitializedObject when deserializing a Serializable type.

In fact, GetUninitializedObject() is used in "fields mode", likely in order to emulate the behavior of DataContractJsonSerializer. You could report a documentation issue if you like.

dbc
  • 104,963
  • 20
  • 228
  • 340
0

I don't think delegates have anything to do with your question, which appears to be:

Can I provide custom logic for setting fields marked with the JsonIgnore attribute? (since they aren't contained in the serialization stream)

Unfortunately the JsonIgnoreAttribute documentation is very scarce and doesn't explain this. Perhaps it is covered elsewhere.

As for your question about what constructor is used, it appears to be controlled by the ConstructorHandling property on the JsonSerializer instance.

The CustomCreationConverter class also sounds like it might be relevant, since it is described as "provides a way to customize how an object is created during JSON deserialization".

Ben Voigt
  • 277,958
  • 43
  • 419
  • 720
  • If the OP agrees with you that the question was not specific to delegates, then please work with the OP to edit the question (especially the title). – John Saunders Jun 17 '15 at 22:20
  • My real question is about how the deserialization works. It is the delegate that makes it clear that none of my constructors are being called – jth41 Jun 18 '15 at 13:20