问题
I am using BinaryFormatter
to do binary serialization of some objects in C#. However, some of the objects contain classes that I access via a DLL and do not have the source code for, so I can\'t mark them with the Serializable
attribute. Is there a straightforward way to serialize them anyway? I have a workaround which involves taking class NoSource
and making a new class SerializableNoSource
for which the constructor takes a NoSource
object and extracts all the information I need from it, but it\'s hacky. Are there any better alternatives?
回答1:
You could create a serialization surrogate.
Imagine that we have a class defined in a referenced assembly that we have no control over that looks like this:
public class Person
{
public string Name { get; set; }
public int Age { get; set; }
public DriversLicense License;
}
// An instance of this type will be part of the object graph and will need to be
// serialized also.
public class DriversLicense
{
public string Number { get; set; }
}
In order to serialize this object you will need to define a serialization surrogate for each type in the object graph.
To create a serialization surrogate you simply need to create a type that implements the ISerializationSurrogate interface:
public class PersonSurrogate : ISerializationSurrogate
{
/// <summary>
/// Manually add objects to the <see cref="SerializationInfo"/> store.
/// </summary>
public void GetObjectData(object obj, SerializationInfo info, StreamingContext context)
{
Person person = (Person) obj;
info.AddValue("Name", person.Name);
info.AddValue("Age", person.Age);
info.AddValue("License", person.License);
}
/// <summary>
/// Retrieves objects from the <see cref="SerializationInfo"/> store.
/// </summary>
/// <returns></returns>
public object SetObjectData(object obj, SerializationInfo info, StreamingContext context, ISurrogateSelector selector)
{
Person person = (Person)obj;
person.Name = info.GetString("Name");
person.Age = info.GetInt32("Age");
person.License = (DriversLicense) info.GetValue("License", typeof(DriversLicense));
return person;
}
}
public class DriversLicenseSurrogate : ISerializationSurrogate
{
/// <summary>
/// Manually add objects to the <see cref="SerializationInfo"/> store.
/// </summary>
public void GetObjectData(object obj, SerializationInfo info, StreamingContext context)
{
DriversLicense license = (DriversLicense)obj;
info.AddValue("Number", license.Number);
}
/// <summary>
/// Retrieves objects from the <see cref="SerializationInfo"/> store.
/// </summary>
/// <returns></returns>
public object SetObjectData(object obj, SerializationInfo info, StreamingContext context, ISurrogateSelector selector)
{
DriversLicense license = (DriversLicense)obj;
license.Number = info.GetString("Number");
return license;
}
}
Then you need to let your IFormatter
know about the surrogates by defining and initializing a SurrogateSelector
and assigning it to your IFormatter
.
private static void SerializePerson(Person person)
{
if (person == null)
throw new ArgumentNullException("person");
using (var memoryStream = new MemoryStream())
{
//Configure our surrogate selectors.
var surrogateSelector = new SurrogateSelector();
surrogateSelector.AddSurrogate(typeof (Person), new StreamingContext(StreamingContextStates.All),
new PersonSurrogate());
surrogateSelector.AddSurrogate(typeof (DriversLicense), new StreamingContext(StreamingContextStates.All),
new DriversLicenseSurrogate());
//Serialize the object
IFormatter formatter = new BinaryFormatter();
formatter.SurrogateSelector = surrogateSelector;
formatter.Serialize(memoryStream, person);
//Return to the beginning of the stream
memoryStream.Seek(0, SeekOrigin.Begin);
//Deserialize the object
Person deserializedPerson = (Person) formatter.Deserialize(memoryStream);
}
}
Using a serialization surrogate is by no means straightforward, and can actually become quite verbose when the type you are trying to serialize has private & protected fields that need to be serialized.
But as you are already manually serializing the values you need, I don't think that is an issue. The use of a surrogate is a more unifom way of handling a scenario like this and should make you feel more comfortable.
回答2:
You might be able to use Mono.Cecil to add the [SerializableAttribute]
to the classes, but I wouldn't do it if there is another way of achieving the desired result.
回答3:
I agree with @Servy, if the author of the class did not anticipate that it would be serialized, you should not attempt to serialize it directly. So you're doing the right thing from an architectural standpoint. To make your current approach less, "hacky," consider implementing ISerializable for classes that contain references to non-serializable objects.
回答4:
Create a new class, inherit the existing class that is not marked with serialization attribute and implement ISerializable interface.
If the class is sealed then you can use Json.NET and then convert it to binary and vice versa (Sucks big time, use it if nothing else can help :)).
回答5:
I think the cleaner way would be to implenment ISerializable Interface and mange yourself the serialization and the reverse process. In MSDN we can find :
serialization cannot be added to a class after it has been compiled....
来源:https://stackoverflow.com/questions/13166105/is-it-possible-to-do-net-binary-serialization-of-an-object-when-you-dont-have