如何序列化包含BitmapImage的类?
我有一个DeepCopy方法,该方法可以序列化传入参数的对象,并返回反序列化的对象以进行深度复制。
I have a DeepCopy method, which serializes the object passed in parameter and returns back the deserialized object to make deep copy.
我的方法是:
public static class GenericCopier<T>
{
public static T DeepCopy(object objectToCopy)
{
using (MemoryStream memoryStream = new MemoryStream())
{
BinaryFormatter binaryFormatter = new BinaryFormatter();
binaryFormatter.Serialize(memoryStream, objectToCopy);
memoryStream.Seek(0, SeekOrigin.Begin);
return (T)binaryFormatter.Deserialize(memoryStream);
}
}
}
如果传递给该参数的对象不包含任何BitmapImage字段和属性。
It works well, if the object passed to the parameter doesn't contain any BitmapImage Field and Properties.
public class MyClass
{
public string TestString {get; set;}
public BitmapImage TestImage { get; set;}
}
如果我使用MyClass制作DeepCopy,则
If I make DeepCopy of MyClass,
MyClass orginal = new MyClass(){ TestString = "Test"};
MyClass copy = GenericCopier<MyClass>.DeepCopy(orginal);
抛出异常
程序集中的类型'System.Windows.Media.Imaging.BitmapImage'未标记为可序列化
我找到了一种序列化BitmapImage的方法在这里
I found a method to serialize BitmapImage here
但是,如何混合使用两种类型序列化(BinaryFormatter和PngBitmapEncoder)以序列化MyClass?
But, How can i mix the both type of serialization (BinaryFormatter & PngBitmapEncoder) to serialize MyClass?
您在这里有两个选择:
选项1:实施 ISerializable
并快照到PNG
Option 1: Implement ISerializable
and Snapshot to PNG
您必须在这里执行的所有类都包含您的 BitmapImage
实施 ISerializable
接口,然后在 GetObjectData
,返回表示以下项编码的字节数组图片,例如PNG。然后在反序列化构造器中将PNG解码为新的 BitmapImage
。
What you must do here is have all classes that contain your BitmapImage
implement the ISerializable
interface, then, in GetObjectData
, return a byte array representing an encoding of the image, for instance PNG. Then in the deserialization constructor decode the PNG to a new BitmapImage
.
请注意,这会对图像进行快照,因此可能会丢失一些WPF数据。
由于您可能有多个包含 BitmapImage
的类,因此,最简单的方法是引入一个带有隐式转换为 BitmapImage
,例如:
Since you may have multiple classes that contain a BitmapImage
, the easiest way to do this is to introduce some wrapper struct with an implicit conversion from and to BitmapImage
, like so:
[Serializable]
public struct SerializableBitmapImageWrapper : ISerializable
{
readonly BitmapImage bitmapImage;
public static implicit operator BitmapImage(SerializableBitmapImageWrapper wrapper)
{
return wrapper.BitmapImage;
}
public static implicit operator SerializableBitmapImageWrapper(BitmapImage bitmapImage)
{
return new SerializableBitmapImageWrapper(bitmapImage);
}
public BitmapImage BitmapImage { get { return bitmapImage; } }
public SerializableBitmapImageWrapper(BitmapImage bitmapImage)
{
this.bitmapImage = bitmapImage;
}
public SerializableBitmapImageWrapper(SerializationInfo info, StreamingContext context)
{
byte[] imageBytes = (byte[])info.GetValue("image", typeof(byte[]));
if (imageBytes == null)
bitmapImage = null;
else
{
using (var ms = new MemoryStream(imageBytes))
{
var bitmap = new BitmapImage();
bitmap.BeginInit();
bitmap.CacheOption = BitmapCacheOption.OnLoad;
bitmap.StreamSource = ms;
bitmap.EndInit();
bitmapImage = bitmap;
}
}
}
#region ISerializable Members
void ISerializable.GetObjectData(SerializationInfo info, StreamingContext context)
{
byte [] imageBytes;
if (bitmapImage == null)
imageBytes = null;
else
using (var ms = new MemoryStream())
{
BitmapImage.SaveToPng(ms);
imageBytes = ms.ToArray();
}
info.AddValue("image", imageBytes);
}
#endregion
}
public static class BitmapHelper
{
public static void SaveToPng(this BitmapSource bitmap, Stream stream)
{
var encoder = new PngBitmapEncoder();
SaveUsingEncoder(bitmap, stream, encoder);
}
public static void SaveUsingEncoder(this BitmapSource bitmap, Stream stream, BitmapEncoder encoder)
{
BitmapFrame frame = BitmapFrame.Create(bitmap);
encoder.Frames.Add(frame);
encoder.Save(stream);
}
public static BitmapImage FromUri(string path)
{
var bitmap = new BitmapImage();
bitmap.BeginInit();
bitmap.UriSource = new Uri(path);
bitmap.EndInit();
return bitmap;
}
}
然后按以下方式使用它:
Then use it as follows:
[Serializable]
public class MyClass
{
SerializableBitmapImageWrapper testImage;
public string TestString { get; set; }
public BitmapImage TestImage { get { return testImage; } set { testImage = value; } }
}
public static class GenericCopier
{
public static T DeepCopy<T>(T objectToCopy)
{
using (MemoryStream memoryStream = new MemoryStream())
{
BinaryFormatter binaryFormatter = new BinaryFormatter();
binaryFormatter.Serialize(memoryStream, objectToCopy);
memoryStream.Seek(0, SeekOrigin.Begin);
return (T)binaryFormatter.Deserialize(memoryStream);
}
}
}
选项2:使用序列化代理直接克隆 BitmapImage
Option 2: Use Serialization Surrogates to Clone the BitmapImage
Directly
事实证明, BitmapImage
具有 Clone()
方法,因此可以合理地问:是否有可能重写二进制序列化以替换原始序列化一个克隆,而没有实际序列化吗?这样做可以避免快照到PNG的潜在数据丢失,因此看起来更可取。
It turns out that BitmapImage
has a Clone()
method, so it is reasonable to ask: is it somehow possible to override binary serialization to replace the original with a clone, without actually serializing it? Doing so would avoid the potential data loss of snapshotting to PNG and would thus appear preferable.
事实上,可以使用序列化替代,以用 IObjectReference
代理,其中包含代理创建的克隆副本的ID。
In fact, this is possible using serialization surrogates to replace the bitmap images with an IObjectReference
proxy containing an ID of a cloned copy created by the surrogate.
public static class GenericCopier
{
public static T DeepCopy<T>(T objectToCopy)
{
var selector = new SurrogateSelector();
var imageSurrogate = new BitmapImageCloneSurrogate();
imageSurrogate.Register(selector);
BinaryFormatter binaryFormatter = new BinaryFormatter(selector, new StreamingContext(StreamingContextStates.Clone));
using (MemoryStream memoryStream = new MemoryStream())
{
binaryFormatter.Serialize(memoryStream, objectToCopy);
memoryStream.Seek(0, SeekOrigin.Begin);
return (T)binaryFormatter.Deserialize(memoryStream);
}
}
}
class CloneWrapper<T> : IObjectReference
{
public T Clone { get; set; }
#region IObjectReference Members
object IObjectReference.GetRealObject(StreamingContext context)
{
return Clone;
}
#endregion
}
public abstract class CloneSurrogate<T> : ISerializationSurrogate where T : class
{
readonly Dictionary<T, long> OriginalToId = new Dictionary<T, long>();
readonly Dictionary<long, T> IdToClone = new Dictionary<long, T>();
public void Register(SurrogateSelector selector)
{
foreach (var type in Types)
selector.AddSurrogate(type, new StreamingContext(StreamingContextStates.Clone), this);
}
IEnumerable<Type> Types
{
get
{
yield return typeof(T);
yield return typeof(CloneWrapper<T>);
}
}
protected abstract T Clone(T original);
#region ISerializationSurrogate Members
public void GetObjectData(object obj, SerializationInfo info, StreamingContext context)
{
var original = (T)obj;
long cloneId;
if (original == null)
{
cloneId = -1;
}
else
{
if (!OriginalToId.TryGetValue(original, out cloneId))
{
Debug.Assert(OriginalToId.Count == IdToClone.Count);
cloneId = OriginalToId.Count;
OriginalToId[original] = cloneId;
IdToClone[cloneId] = Clone(original);
}
}
info.AddValue("cloneId", cloneId);
info.SetType(typeof(CloneWrapper<T>));
}
public object SetObjectData(object obj, SerializationInfo info, StreamingContext context, ISurrogateSelector selector)
{
var wrapper = (CloneWrapper<T>)obj;
var cloneId = info.GetInt64("cloneId");
if (cloneId != -1)
wrapper.Clone = IdToClone[cloneId];
return wrapper;
}
#endregion
}
public sealed class BitmapImageCloneSurrogate : CloneSurrogate<BitmapImage>
{
protected override BitmapImage Clone(BitmapImage original)
{
return original == null ? null : original.Clone();
}
}
在此实现中,您的主类保持不变:
In this implementation, your main classes remain unchanged:
[Serializable]
public class MyClass
{
BitmapImage testImage;
public string TestString { get; set; }
public BitmapImage TestImage { get { return testImage; } set { testImage = value; } }
}
尴尬,而 BitmapImage
具有 Clone
方法,它实际上并未实现 ICloneable
接口。如果有的话,上面的代码看起来更简洁,因为我们可以简单地克隆每个可克隆对象,而不用为 BitmapImage
调用特定方法。
Awkwardly, while BitmapImage
has a Clone
method, it does not actually implement the ICloneable
interface. If it had, the code above could look cleaner, because we could simply clone every cloneable object rather than calling a specific method for BitmapImage
.