首页 > 解决方案 > 带有代理项的 Protobuf-net 对象图参考

问题描述

据我所知,从 v2 开始的 protobuf-net 支持引用,但它们不能与代理一起使用(在这种情况下会抛出异常“反序列化期间引用跟踪的对象更改引用” )

我想知道是否有一些我没有考虑过使其工作的解决方法。

下面是重现上述异常的我的测试用例的代码。

课程

public class Person
{
    public Person(string name, GenderType gender)
    {
        Name = name;
        Gender = gender;
    }
    public string Name { get; set; }
    public GenderType Gender { get; set; }
}

[Flags]
public enum GenderType : byte
{
    Male = 1,
    Female = 2,
    Both = Male | Female
}

public class Family
{
    public Family(List<Person> people, Person familyHead = null)
    {
        People = people;

        FamilyHead = familyHead;
    }

    public List<Person> People { get; set; }

    public Person FamilyHead { get; set; }
}

public class PersonSurrogate
{
    public string Name { get; set; }
    public byte Gender { get; set; }

    public PersonSurrogate(string name, byte gender)
    {
        Name = name;
        Gender = gender;
    }       

    #region Static Methods

    public static implicit operator Person(PersonSurrogate surrogate)
    {
        if (surrogate == null) return null;

        return new Person(surrogate.Name, (GenderType)surrogate.Gender);

    }

    public static implicit operator PersonSurrogate(Person source)
    {
        return source == null ? null : new PersonSurrogate(source.Name, (byte)source.Gender);
    }

    #endregion       
}

public class FamilySurrogate
{
    public FamilySurrogate(List<Person> people, Person familyHead)
    {
        People = people;
        FamilyHead = familyHead;
    }

    public List<Person> People { get; set; }

    public Person FamilyHead { get; set; }

    #region Static Methods

    public static implicit operator Family(FamilySurrogate surrogate)
    {
        if (surrogate == null) return null;

        return new Family(surrogate.People, surrogate.FamilyHead);

    }

    public static implicit operator FamilySurrogate(Family source)
    {
        return source == null ? null : new FamilySurrogate(source.People, source.FamilyHead);
    }

    #endregion
}

串行器

/// <summary>
/// Class with model for protobuf serialization
/// </summary>
public class FamilySerializer
{    
    public GenderType GenderToInclude;

    public FamilySerializer(Family family, GenderType genderToInclude = GenderType.Both)
    {
        GenderToInclude = genderToInclude;
        Family = family;

        Init();
    }

    private void Init()
    {
        Model = RuntimeTypeModel.Create();
        FillModel();
        Model.CompileInPlace();         
    }

    public FamilySerializer()
    {
        Init();
    }

    public Family Family { get; set; }
    public RuntimeTypeModel Model { get; protected set; }

    protected virtual void FillModel()
    {
        Model = RuntimeTypeModel.Create();

        Model.Add(typeof(Family), false)
            .SetSurrogate(typeof(FamilySurrogate));

        MetaType mt = Model[typeof(FamilySurrogate)];
        mt.Add(1, "People");
        mt.AddField(2, "FamilyHead").AsReference = true;  // Exception "A reference-tracked object changed reference during deserialization" - because using surrogate.
        mt.UseConstructor = false;

        Model.Add(typeof(Person), false)
            .SetSurrogate(typeof(PersonSurrogate));

        mt = Model[typeof(PersonSurrogate)]
            .Add(1, "Name")
            .Add(2, "Gender");
        mt.UseConstructor = false; // Avoids to use the parameterless constructor.
    }

    public void Save(string fileName)
    {            
        using (Stream s = File.Open(fileName, FileMode.Create, FileAccess.Write))
        {
            Model.Serialize(s, Family, new ProtoBuf.SerializationContext(){Context = this});
        }
    }

    public void Open(string fileName)
    {
        using (Stream s = File.Open(fileName, FileMode.Open, FileAccess.Read))
        {
            Family = (Family)Model.Deserialize(s, null, typeof(Family), new ProtoBuf.SerializationContext(){Context = this});
        }
    }
}

测试用例

private Family FamilyTestCase(string fileName, bool save)
{           
    if (save)
    {
        var people = new List<Person>()
        {
            new Person("Angus", GenderType.Male),
            new Person("John", GenderType.Male),
            new Person("Katrina", GenderType.Female),           
        };
        var fam = new Family(people, people[0]);

        var famSer = new FamilySerializer(fam);

        famSer.Save(fileName);

        return fam;
    }
    else
    {
        var famSer = new FamilySerializer();

        famSer.Open(fileName);

        if (Object.ReferenceEquals(fam.People[0], fam.FamilyHead))
        {
            // I'd like this condition would be satisfied
        }

        return famSer.Family;
    }
}

标签: c#serializationprotobuf-net

解决方案


我认为现在这只是一个不受支持的场景,我不知道有什么方法可以让它神奇地工作;这可能是我可以在某个时候回到的东西,但是有许多更高优先级的事情会优先考虑。

我在这里通常的建议 - 这适用于任何序列化程序,而不仅仅是 protobuf-net:任何时候你发现自己遇到了序列化程序的限制,或者甚至只是在序列化程序中配置一些尴尬的东西:停止与序列化程序作斗争。当人们尝试序列化他们的常规域模型时,几乎总是会出现这种问题,而域模型中的某些内容并不适合他们选择的序列化器。不要尝试神秘的魔法:拆分你的模型——让你的模型非常适合你希望你的应用程序看到的内容,并创建一个单独的模型非常适合您的序列化程序的模型。那么你就不需要像“代理”这样的概念了。如果您使用多种序列化格式,或者在同一序列化格式中有多个不同“版本”的布局:拥有多个序列化模型

尝试在模型上为多个主人服务真的不值得头疼。


推荐阅读