1

A bit of context: I'm implementing serialization in somewhat large-scale project that saves data to a file or multiple files. While I'm not alone in the project, I am the only one with some experience in serialization, though evidently not enough to figure this out on my own.

The goal is to create a class library so that we can implement MVVM and allow easy development with cross-plat in mind. This means all non-exposed classes must be internal. Further, some data is given protected or private setters, to prevent mutation where this shouldn't happen, even within the class library. Further complicating matters is some classes have base classes with non-default constructors, which initialize fields that are used and cannot be null or default (for non-nullable types). These are generally initializing readonly fields.

I need to be able to serialize the data selectively, for any accessibility level, and also call any base constructor and provide it the correct arguments.

I have tried: BinaryFormatter, DataContractSerializer, and XmlSerializer
I've have had the most luck with DataContractSerializer, as it allows properties with non-public get/set to serialize well, and is opt-in instead of opt-out like BinaryFormatter is. The problem is that each one of these provides an automatic method of serialization - without calling a constructor.
I am aware of the OnSerializing/OnDeserializing/OnDeserialized/OnSerialized attributes. unfortunately, they cannot set readonly properties or call a base constructor.

I've also previously implemented "Serializer" versions of my classes with empty constructors that I can then pass into the actual class constructor, and while it works, it's tedious and requires a lot of rewrite of identical code only to force a constructor, not to mention extra memory usage.

I've also tried using ISerializable, which allows me to force a constructor, which allows me to call a base constructor and provide the necessary values, if necessary. however, it doesn't use the default serialization that makes the Serialize/DataMember attributes actually useful.

I thought I had a clever workaround using Reflection and a custom Attribute:

protected SerializeClassDemo(SerializationInfo info, StreamingContext context)
{
    List<PropertyInfo> propertySaveables = saveableProperties();
    foreach (PropertyInfo saveable in propertySaveables)
    {
        saveable.SetValue(this, info.GetValue(saveable.Name, saveable.PropertyType));
    }
    List<FieldInfo> fieldSaveables = saveableFields();
    foreach (FieldInfo saveable in fieldSaveables)
    {
        saveable.SetValue(this, info.GetValue(saveable.Name, saveable.FieldType));
    }
}

[SecurityPermission(SecurityAction.LinkDemand, Flags = SecurityPermissionFlag.SerializationFormatter)]
public void GetObjectData(SerializationInfo info, StreamingContext context)
{
    List<PropertyInfo> propertySaveables = saveableProperties();
    foreach (PropertyInfo saveable in propertySaveables)
    {
        info.AddValue(saveable.Name, saveable.GetValue(this), saveable.PropertyType);
    }
    List<FieldInfo> fieldSaveables = saveableFields();
    foreach (FieldInfo saveable in fieldSaveables)
    {
        info.AddValue(saveable.Name, saveable.GetValue(this), saveable.FieldType);
    }
}

private List<PropertyInfo> saveableProperties()
{
    List<PropertyInfo> saveables = new List<PropertyInfo>();
    PropertyInfo[] props = typeof(SerializeClassDemo).GetProperties();
    foreach (PropertyInfo prop in props)
    {
        SaveAttribute attrs = prop.GetCustomAttribute<SaveAttribute>();
        if (attrs != null)
        {
            saveables.Add(prop);
        }
    }
    return saveables;
}

private List<FieldInfo> saveableFields()
{
    List<FieldInfo> saveables = new List<FieldInfo>();
    FieldInfo[] fields = typeof(SerializeClassDemo).GetFields();
    foreach (FieldInfo field in fields)
    {
        SaveAttribute attrs = field.GetCustomAttribute<SaveAttribute>();
        if (attrs != null)
        {
            saveables.Add(field);
        }
    }
    return saveables;
}

Which would automatically serialize and deserialize the data like the default XmlSerialization does, but the way I used reflection only works with public members.

At this point, I've come to the realization that while I might eventually stumble through enough documentation pages to find an ideal solution, I'd probably save time and effort just asking - is there a relatively simple way to serialize/deserialize data, that works with internal/protected data, and allows me to satisfy a base constructor that requires arguments?

Edit: the project will be free, and the license for the project is GPL-3, which satisfies stack overflow's creative commons requirement for using any provided code. I won't make any money off any help provided. cheers.

  • maybe the objects are too complex - as i get it, the DataContractSerializer would do the work, your might call init-functions from your constructors and mark them as OnDeserialize. The only problem seem to be readonly properties. But then - what kind of values is it? generated by some constructor argument? why not splitting classes into "real" data and create (maybe smaller) objects with those readonly attributes in response? (Googling for serialization of readonly values results in many years of search for workarounds) – Sebastian Jan 30 '19 at 11:28
  • I don't disagree with you on the complexity - I'm just trying to find a solution that still works in spite of it, as I don't want to step on toes unless I have to. The readonly values are generally classes that are used within the constructor, so having them null (the default) throws errors. They can use the ?. and ?? operators in the constructor, I guess. – Andrew Baumher Jan 30 '19 at 21:35
  • Are you wedded to XML? [tag:json.net] has the ability to deserialize using parameterized constructors, see [How does JSON deserialization in C# work](https://stackoverflow.com/q/41870132/3744182), [JSON.net: how to deserialize without using the default constructor?](https://stackoverflow.com/q/23017716) and [Json.net `JsonConstructor` constructor parameter names](https://stackoverflow.com/q/43032552). – dbc Jan 31 '19 at 00:26

0 Answers0