【问题标题】:Customized Serialization自定义序列化
【发布时间】:2017-03-14 19:18:49
【问题描述】:

我有一些必须序列化的对象:

class Displayable{
  string name;
  Sprite icon;
}

icon 字段需要自定义序列化,因为 Sprites 已经序列化(在不同的文件中,具有自己的格式,由游戏引擎),我只需要存储一种引用它们的方法(假设是 @ 987654329@,是Dictionary<string, Sprite>中的键)。

使用BinaryFormatter 我尝试实现ISerializableISerializationSurrogate 但这两种方法都会在反序列化时创建一个新的Sprite 对象实例,因此它们不适合我的情况。我想拥有与ISerializationSurrogate 相同的功能,除了我不想要SetObjectData(object obj, SerializationInfo info, StreamingContext context, ISurrogateSelector selector) 中的第一个参数,因为我需要返回一个我已经在内存中拥有的对象,而不是从反序列化器接收一个新实例并用它填充数据。

我也尝试过像 SharpSerializer 这样的外部库,但我不喜欢公共无参数构造函数的限制,它并不能真正让您自定义特殊类的序列化。 我读过有关 ProtoBuffers 的文章,但它不支持继承,我在其他地方需要它。

所以我的要求是:

  • 继承支持
  • 能够为某些类型定义自定义序列化
  • 这些自定义类型的反序列化不应自行创建实例

有没有图书馆这样做?

否则,我是不是太挑剔了?您通常如何实现对存储在其他地方的对象的引用的序列化?

提前谢谢你。

编辑: 这就是我想要的东西

public class SerializableSprite : ISerializationSurrogate
{
  public static Dictionary<string, Sprite> sprites;
  public void GetObjectData(object obj, SerializationInfo info, StreamingContext context) {
    Sprite sprite = obj as Sprite;
    info.AddValue("spriteKey", sprite.name);
  }
  // The first parameter in this function is a newly instantiated Sprite, which I don't need
  public object SetObjectData(object obj, SerializationInfo info, StreamingContext context, ISurrogateSelector selector) {
    return sprites[info.GetString("spriteKey")];
  }
}

【问题讨论】:

  • 你真的尝试过二进制序列化(通过BinaryFormmatter)吗?它涵盖了您的所有要求。例如,如果 Sprite 实例在多个正在序列化/反序列化的对象之间共享,则它只会被序列化/反序列化一次 - 您不需要任何自定义逻辑。
  • Json.NET 通过PreserveReferencesHandlingTypeNameHandling 支持这一点。 DataContractSerializer 通过 [DataContract(IsReference=true]data contract known types 支持此功能。但是你能详细说明你到目前为止所做的尝试吗?您的问题似乎是推荐问题。
  • 我知道 BinaryFormatter 支持通过引用进行序列化。就我而言,Sprites 在其他地方被序列化,这意味着在不同的文件和不同的格式中(通过游戏引擎,而不是我的代码)。我只是确保在反序列化对它们的引用之前加载它们。我也知道如何手动序列化和反序列化这些引用,唯一的问题是我不希望在反序列化时出现新的 Sprite 实例,因为我已经在内存中拥有它们。
  • @dbc 我尝试实现 ISerializableISerializationSurrogate 但这两种方法都会在反序列化时创建 Sprite 对象的新实例,因此它们不适合我的情况。我想拥有与ISerializationSurrogate 相同的功能,除了我不想要SetObjectData(object obj, SerializationInfo info, StreamingContext context, ISurrogateSelector selector) 中的第一个参数,因为我需要返回一个我已经在内存中拥有的对象,而不是从反序列化器接收一个新实例并用它填充数据。
  • @dogiordano - 我尝试实现 ISerializableISerializationSurrogate - 当你这样做时,你使用的是什么序列化程序?您是在寻求替代序列化程序的建议还是寻求帮助以正确实施ISerializationSurrogate?如果是后者,您能否edit您的问题以显示您当前使用的序列化程序以及您尝试的失败?

标签: c# serialization deserialization


【解决方案1】:

为了防止BinaryFormatter 在反序列化期间创建Sprite 的新实例,序列化期间您可以调用SerializationInfo.SetType(Type) 来指定替代类型信息——通常是一些代理类型——插入到序列化流中。在反序列化期间,SetObjectData() 将传递一个代理实例而不是“真实”类型来初始化。该代理类型必须反过来实现IObjectReference,以便最终可以将“真实”对象插入到对象图中,特别是通过在您的精灵表中查找它。

以下是这样做的:

class ObjectReferenceProxy<T> : IObjectReference
{
    public T RealObject { get; set; }

    #region IObjectReference Members

    object IObjectReference.GetRealObject(StreamingContext context)
    {
        return RealObject;
    }

    #endregion
}

public sealed class SpriteSurrogate : ObjectLookupSurrogate<string, Sprite>
{
    static Dictionary<string, Sprite> sprites = new Dictionary<string, Sprite>();
    static Dictionary<Sprite, string> spriteNames = new Dictionary<Sprite, string>();

    public static void AddSprite(string name, Sprite sprite)
    {
        if (name == null || sprite == null)
            throw new ArgumentNullException();
        sprites.Add(name, sprite);
        spriteNames.Add(sprite, name);
    }

    public static IEnumerable<Sprite> Sprites
    {
        get
        {
            return sprites.Values;
        }
    }

    protected override string GetId(Sprite realObject)
    {
        if (realObject == null)
            return null;
        return spriteNames[realObject];
    }

    protected override Sprite GetRealObject(string id)
    {
        if (id == null)
            return null;
        return sprites[id];
    }
}

public abstract class ObjectLookupSurrogate<TId, TRealObject> : ISerializationSurrogate where TRealObject : class
{
    public void Register(SurrogateSelector selector)
    {
        foreach (var type in Types)
            selector.AddSurrogate(type, new StreamingContext(StreamingContextStates.All), this);
    }

    IEnumerable<Type> Types
    {
        get
        {
            yield return typeof(TRealObject);
            yield return typeof(ObjectReferenceProxy<TRealObject>);
        }
    }

    protected abstract TId GetId(TRealObject realObject);

    protected abstract TRealObject GetRealObject(TId id);

    #region ISerializationSurrogate Members

    public void GetObjectData(object obj, SerializationInfo info, StreamingContext context)
    {
        var original = (TRealObject)obj;
        var id = GetId(original);
        info.AddValue("id", id);
        // use Info.SetType() to force the serializer to construct an object of type ObjectReferenceWrapper<TRealObject> during deserialization.
        info.SetType(typeof(ObjectReferenceProxy<TRealObject>));
    }

    public object SetObjectData(object obj, SerializationInfo info, StreamingContext context, ISurrogateSelector selector)
    {
        // Having constructed an object of type ObjectReferenceWrapper<TRealObject>, 
        // look up the real sprite using the id in the serialization stream.
        var wrapper = (ObjectReferenceProxy<TRealObject>)obj;
        var id = (TId)info.GetValue("id", typeof(TId));
        wrapper.RealObject = GetRealObject(id);
        return wrapper;
    }

    #endregion
}

然后将其应用于BinaryFormatter,如下所示:

        var selector = new SurrogateSelector();
        var spriteSurrogate = new SpriteSurrogate();
        spriteSurrogate.Register(selector);

        BinaryFormatter binaryFormatter = new BinaryFormatter(selector, new StreamingContext());

示例fiddle

更新

虽然上面的代码在 .Net 3.5 及更高版本中工作,但由于 IObjectReference 的一些问题,尽管在那里编译成功,但它显然不能在 中工作。以下内容也适用于 .Net 3.5 及更高版本,并且还通过从 ISerializationSurrogate.SetObjectData() 返回真实对象来避免使用 IObjectReference。因此它也应该在 unity3d 中工作(在 cmets 中确认):

public sealed class SpriteSurrogate : ObjectLookupSurrogate<string, Sprite>
{
    static Dictionary<string, Sprite> sprites = new Dictionary<string, Sprite>();
    static Dictionary<Sprite, string> spriteNames = new Dictionary<Sprite, string>();

    public static void AddSprite(string name, Sprite sprite)
    {
        if (name == null || sprite == null)
            throw new ArgumentNullException();
        sprites.Add(name, sprite);
        spriteNames.Add(sprite, name);
    }

    public static IEnumerable<Sprite> Sprites
    {
        get
        {
            return sprites.Values;
        }
    }

    protected override string GetId(Sprite realObject)
    {
        if (realObject == null)
            return null;
        return spriteNames[realObject];
    }

    protected override Sprite GetRealObject(string id)
    {
        if (id == null)
            return null;
        return sprites[id];
    }
}

public abstract class ObjectLookupSurrogate<TId, TRealObject> : ISerializationSurrogate where TRealObject : class
{
    class SurrogatePlaceholder
    {
    }

    public void Register(SurrogateSelector selector)
    {
        foreach (var type in Types)
            selector.AddSurrogate(type, new StreamingContext(StreamingContextStates.All), this);
    }

    IEnumerable<Type> Types
    {
        get
        {
            yield return typeof(TRealObject);
            yield return typeof(SurrogatePlaceholder);
        }
    }

    protected abstract TId GetId(TRealObject realObject);

    protected abstract TRealObject GetRealObject(TId id);

    #region ISerializationSurrogate Members

    public void GetObjectData(object obj, SerializationInfo info, StreamingContext context)
    {
        var original = (TRealObject)obj;
        var id = GetId(original);
        info.AddValue("id", id);
        // use Info.SetType() to force the serializer to construct an object of type SurrogatePlaceholder during deserialization.
        info.SetType(typeof(SurrogatePlaceholder));
    }

    public object SetObjectData(object obj, SerializationInfo info, StreamingContext context, ISurrogateSelector selector)
    {
        // Having constructed an object of type SurrogatePlaceholder, 
        // look up the real sprite using the id in the serialization stream.
        var id = (TId)info.GetValue("id", typeof(TId));
        return GetRealObject(id);
    }

    #endregion
}

示例fiddle #2

【讨论】:

  • 谢谢,这似乎正是我想要的。我会尽快测试并提供反馈。
  • @dogiordano - 答案已更新为可能适用于 unity3d 和 .net 的替代版本。
  • 哇...成功了!你已经非常有用了。我不知道该怎么感谢你。如果可以的话,我会更多地支持你。
猜你喜欢
  • 1970-01-01
  • 1970-01-01
  • 2011-11-09
  • 2012-06-08
  • 2011-02-25
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
相关资源
最近更新 更多