mbraceproject / FsPickler

A fast multi-format message serializer for .NET
http://mbraceproject.github.io/FsPickler/
MIT License
326 stars 52 forks source link

Extenally marking types as excluded/nonserialized/serializable #26

Closed damageboy closed 9 years ago

damageboy commented 9 years ago

Hi, I'm trying to dump my current serialization setup for a a large object graph of types I have.

The way I've started to work with FsPickler is to start sprinkling Serializable attributes all over my code. This it perfectly acceptable, but the problem I'm experiencing is with non Serializeable types.

I would like to be able to somehow inform FsPickler that a type is not serializable (rather than marking each field in other classes that is of that type).

I cannot seem to find this ability in the current code in FieldPicklers.fs or anywhere else in FsPickler.

I would like to work on this, but I thought that it would be better to start some sort of a discussion on how these things should be done.

I can see two approaches:

  1. Add something along the lines of a NotSerializableAttribute class that would come from FsPickler... This sounds like a very BAD idea, since it would mean that user code now actually depends on FsPickler, and would generally suck, so I don't think this is even worth pursuing
  2. Provide a Set/List of excluded types, so that when FsPickler encounters these types, it will automatically treat the fields that points to these types as-if they were marked with the NonSerialized attribute

In addition to what I've outline in (2), the same idea could be applied for the use of SerializableAttribute, i.e., another Set/List could be supplied that would contain types that SHOULD be treated as Serializable even if they are not marked as such through the Serializable attribute. This will have the additional advantages:

Any thoughts? Is the direction of adding Sets of Types to the (de)serialization api-points acceptable?

eiriktsarpalis commented 9 years ago

The F# compiler will automatically attach the Serializable attribute to most type definitions. To mark a type as non-serializable in F# you need to explicitly disable this behaviour:

[<AutoSerializable(false)>]
type Foo = class end

FsPickler will treat such types as non-serializable unless other serialization schemata are explicitly specified. For more information have a look here: http://msdn.microsoft.com/en-us/library/ee370515.aspx

damageboy commented 9 years ago

I should have specified that I'm using this to serialize a bunch of C# classes as well..

In addition, if I read the code properly (and I might not) if a class that is serializable but has a field pointing to a non-serializeable class, it throws an NonSerializableTypeException, with no possibility of skipping that field.

What I would like is to supply types that FsPickler will treat as NonSerialized according to their type instead of throwing an exception.

eiriktsarpalis commented 9 years ago

C# shouldn't be a problem, since classes do not carry the Serializable attribute by default.

As regards non-serializable fields, these can always be ignored by attaching the NonSerialized attribute.

See also http://msdn.microsoft.com/en-us/library/axwwbcs6(v=vs.110).aspx or http://msdn.microsoft.com/en-us/library/72hyey7b(v=vs.110).aspx

For a more detailed example, http://stackoverflow.com/a/635662

In general, I would recommend against field based serialization, as it tends to be implementation sensitive. You could have a look at either ISerializable or DataContract. I have written an overview of the available options here: http://nessos.github.io/FsPickler/overview.html

damageboy commented 9 years ago

I'm probably not explaining myself properly. I have two problems, that could be solved by marking types (not fields!, like you suggest) as non-serializable (somehow):

  1. I have many types that I need to serialize, that ALL contain a field to a type I do NOT want to serialize, for example an NLog logger. I would love to have an EASY way (i.e. not do it N times for N classes) to mark the NLog Logger references as non-serializable @ the type level, and NOT the field level
  2. Some of these references happen to be automatic properties in C#, which I CANNOT mark as NonSerialized, since C#'s NonSerialized attribute cannot be used on properties, EVEN IF they are automatic properties

I could in theory go for the DataContract path, but then I would have to start sprinkling DataMemeber fairy dust on hundreds of members to explicitly include each member in each and one of my classes in the hierarchy, instead of marking the 5-6 types I would like to skip instead...

Is my problem statement clearer now?

eiriktsarpalis commented 9 years ago

Ok, I think I understand your problem now. I cannot say it is clear to me what is the best solution.

By the way, what serializer are you currently using and how do you solve this issue? Most libraries that I know of ivariably fail when trying to serialize objects with nonserializable fields.

damageboy commented 9 years ago

Hi, I currently use protobuf, but instead of proviting protomember/protoignore attributes all over the place, I have one class that through reflection uses the "ad-hoc" mode of protobuf and build a protobuf-net RuntimeTypeModel, which allows me to decide at runtime what/how to serialize...

Here's another idea, while scanning the types in FsPickler, it shouldn't be too hard to test against external "attributes". .NET supports this conecpt through System.ComponentModel.TypeDescriptor where you can, at runtime "register" attributes for a specific type, i.e.:

TypeDescriptor.AddAttributes(typeof(SomeClassIWantToSerielize), new[] { new PickleMeAttribute() });
TypeDescriptor.AddAttributes(typeof(SomeClassINeverWantToSerielize), new[] { new DontPickleMeAttribute() });

That way, the Picker reflection code has another source of "meta-data" to consule while discovering types. This adds a dependency between FsPickler -> System.ComponentModel, but the code doing the serialization can add any class-level attribute @ runtime, without inflicting any dependencies on the code where the types to be serialized are residing...

This sort of approach works better with the existing code base, I think, in the sense that it's just another satatic, globally available registry of meta-data, much like reflection to consult while going over the type hierarchy? Does that make sense to you in FSPickler?

eiriktsarpalis commented 9 years ago

Original versions of the library did carry a custom pickler plugging mechanism that could be set at runtime. You could define you own serialization/deserialization methods (in your case, no action on serialization - always returning null on deserialization) and override the default provided by the pickler generation mechanism. This had the problem of lacking "referential transparency": for instance, two independent libraries collocated in the same process registering conflicting serializers for the same type. This is a legitimate concern for the type of software that we make, so we initially addressed it by forcing discrete "pickler universes" for every consumer of the FsPickler library. This solution was particularly heavyweight and introduced a new class of errors in which a fresh pickler cache could be created upon every serialization/deserialization. The current version of the library has deliberately done away with all that and enforces a global, "referentially transparent" pickler generator in type definitions act as the sole source of serialization information. The advised way in which this can be circumvented is either through explicit use of pickler combinators or wrapper types, although admittedly these are methods more suited to F# than C#.

I'll certainly think more about this, since your concern is definitely legitimate. In the meantime, do you think that IgnoreDataMemberAttribute would convenience you? It is relatively straightforward to implement atm.

eiriktsarpalis commented 9 years ago

@damageboy I just added support for the IgnoreDataMemberAttribute in 7afe9ea4a7e67456cc61f868b40e57cbe8436f2e. Let me know if this helps with your problem.

eiriktsarpalis commented 9 years ago

This has been partially addressed as of 1.2.5, closing.