21

When implementing the ISerializable interface in C#, we provide a constructor which takes a SerializationInfo object, and then queries it with various GetInt32, GetObject etc. methods in order to fill the fields of the object which we are trying to deserialize.

One major reason to implement this interface, rather than just using the [Serializable] attribute, is for backwards compatibility: if we have added new fields to the class at some point, we can catch the SerializationException thrown by a serialized, older version of the class, and handle them in an appropriate manner.

My question is the following: why do we have to use these exceptions for what is, essentially, control flow? If I am deserializing a large number of classes which were saved some time ago, potentially each missing field in each class will throw an exception, causing really bad performance.

Why does the SerializationInfo class not provide TryGetValue methods which would simply return false if the name string were not present?

Joel in Gö
  • 7,460
  • 9
  • 47
  • 77

3 Answers3

30

You can iterate over the available fields and use switch, though...

foreach(SerializationEntry entry in info) {
    switch(entry.Name) {
        ...
    }
}

Or you could use protobuf-net ;-p

spottedmahn
  • 14,823
  • 13
  • 108
  • 178
Marc Gravell
  • 1,026,079
  • 266
  • 2,566
  • 2,900
  • 2
    cool; this doesn't seem to be documented in the VS2008 documentation. – Joel in Gö Nov 04 '09 at 11:48
  • 1
    Agreed. This doesn't appear to be documented anywhere. Nothing seems to say that SerializationInfo is enumerable. But, it worked for me. – Michael Levy Jan 31 '11 at 23:15
  • 2
    @Michael except [here](http://msdn.microsoft.com/en-us/library/system.runtime.serialization.serializationinfo.getenumerator.aspx) of course, or (@Joel) going back to 1.1, [here](http://msdn.microsoft.com/en-us/library/system.runtime.serialization.serializationinfo.getenumerator(v=VS.71).aspx) – Marc Gravell Feb 01 '11 at 06:25
  • 1
    And this, friends, is one of the very few valid uses of the for/switch antipattern. – Ben Burns Aug 07 '14 at 04:54
  • 1
    @BenBurns the "for/switch" antipattern concerns a particular poor coding standard as described in its Wiki. Other than that bad example, there are a **ton** of, not a few, valid use cases of a switch inside a for. – Mr. TA Dec 05 '17 at 01:20
3

Well no one answered 'why', but I'm guessing that's addressed to MS..

My implementation for anyone in need:

public static class SerializationInfoExtensions
{
    public static bool TryGetValue<T>(this SerializationInfo serializationInfo, string name, out T value)
    {
        try
        {
            value = (T) serializationInfo.GetValue(name, typeof(T));
            return true;
        }
        catch (SerializationException)
        {
            value = default(T);
            return false;
        }
    }

    public static T GetValueOrDefault<T>(this SerializationInfo serializationInfo, string name, Lazy<T> defaultValue)
    {
        try
        {
            return (T) serializationInfo.GetValue(name, typeof(T));
        }
        catch (SerializationException)
        {
            return defaultValue.Value;
        }
    }
}
Mugen
  • 8,301
  • 10
  • 62
  • 140
  • 3
    Whilst this may be useful syntactic sugar in many cases, it doesn't address the underlying problem of avoiding an exception being thrown and caught for every missing field. Indeed, in some cases (where a group of fields that would always be serialized together had a single try/catch block around them), it may make performance worse, as an exception would be generated for every missing field, rather than once for the entire group. – Steve Dec 02 '20 at 12:15
1

In addition to the enumerating solution, there is an internal method SerializationInfo.GetValueNoThrow() according to the Reference Source.

You can e.g. make an extension method like this and avoid the exception overhead:

static class SerializationInfoExtensions
{
    private static MethodInfo _GetValueNoThrow =
        typeof(SerializationInfo).GetMethod("GetValueNoThrow",
                                            BindingFlags.Instance | BindingFlags.NonPublic);

    public static Object GetValueNoThrow(this SerializationInfo info, String name, Type type)
    {
        return _GetValueNoThrow.Invoke(info, new object[] { name, type });
    }
}

This solution still results in some reflection overhead compared to a plain method call, but it is an order of magnitude smaller – a crude benchmark at .NET Fiddle:

Method Mean Error StdDev
ExceptionOverhead 72.840 ms 2.0690 ms 2.2997 ms
ReflectionOverhead 2.657 ms 0.0192 ms 0.0205 ms

However, it should be noted that the whole SerializationInfo type is going to be deprecated in a future version of .NET, as discussed in Add SerializationInfo.TryGetValue API #42460.

Yirkha
  • 12,737
  • 5
  • 38
  • 53
  • ...of course now you have to deal with the _reflection overhead_ instead of the exception overhead. – Dai Feb 12 '22 at 19:12
  • That's a fair point, thank you. I have added some benchmark to the answer to give readers an idea whether the extra hassle is worth it. – Yirkha Feb 19 '22 at 18:35
  • 2
    To be fair, you can convert a `MethodInfo` to a `Func<>` call with some `System.Reflection.Emit` and the runtime cost overhead essentially disappears. – Dai Feb 19 '22 at 18:36