【问题标题】:How would one serialize nested collections when using a custom ISerializationSurrogate?使用自定义 ISerializationSurrogate 时如何序列化嵌套集合?
【发布时间】:2013-10-10 15:13:25
【问题描述】:

我正在尝试使用通用序列化代理来控制通过使用 NetDataContractSerializer 进行序列化的字段的命名。到目前为止,这个概念运行良好,除了每当我遇到一个在 GetObjectData 方法中保存对集合的引用的字段时,我都会遇到一个问题在被序列化的集合中。对于数组或列表等集合,我需要做些什么特别的事情吗?

我已经添加了我的完整代码的副本以及下面的示例,希望有人能够指出我正确的方向。

static void Main()
{
    var obj = new Club(
        "Fight Club", 
        Enumerable.Range(1, 3).Select(i => new Member() { Age = i, Name = i.ToString() }));

    var streamingContext = new StreamingContext(StreamingContextStates.Clone);
    //var serializer = new NetDataContractSerializer(streamingContext);
    var serializer = new NetDataContractSerializer(streamingContext, int.MaxValue, false, System.Runtime.Serialization.Formatters.FormatterAssemblyStyle.Full, new MySurrogateSelector(streamingContext));
    var ms = new MemoryStream();
    serializer.Serialize(ms, obj);

    Console.WriteLine("Before serializing: \n{0}\n", obj);

    ms.Position = 0;
    var xml = XElement.Load(ms);
    Console.WriteLine("Serialized object: \n\n{0}\n", xml);

    ms.Position = 0;
    var deserializedObj = serializer.Deserialize(ms);

    Console.WriteLine("After deserializing: \n{0}\n", deserializedObj);
    Console.ReadKey();
}

public class MySurrogateSelector : ISurrogateSelector
{
    private ISerializationSurrogate _surrogate = new BackingFieldSerializationSurrogate();

    public MySurrogateSelector(StreamingContext streamingContext)
    {
    }

    public void ChainSelector(ISurrogateSelector selector)
    {
        throw new System.NotImplementedException();
    }

    public ISurrogateSelector GetNextSelector()
    {
        throw new System.NotImplementedException();
    }

    public ISerializationSurrogate GetSurrogate(System.Type type, StreamingContext context, out ISurrogateSelector selector)
    {
        selector = null;
        return _surrogate;
    }
}


public class BackingFieldSerializationSurrogate : ISerializationSurrogate
{
    private static Regex _backingFieldRegex = new Regex("<(.*)>k__BackingField", RegexOptions.Singleline | RegexOptions.Compiled);

    public void GetObjectData(object obj, SerializationInfo info, StreamingContext context)
    {
        info.SetType(obj.GetType());
        var fields = obj.GetType().GetFields(BindingFlags.FlattenHierarchy | BindingFlags.NonPublic | BindingFlags.Public | BindingFlags.Instance);
        foreach (var field in fields)
        {
            string propertyName;
            info.AddValue(
                TryGetPropertyNameFromBackingField(field.Name, out propertyName) ? GenerateCustomBackingFieldName(propertyName) :
                field.Name,
                field.GetValue(obj),
                field.FieldType);
        }
    }

    public object SetObjectData(object obj, SerializationInfo info, StreamingContext context, ISurrogateSelector selector)
    {
        var fields = obj.GetType().GetFields(BindingFlags.FlattenHierarchy | BindingFlags.NonPublic | BindingFlags.Public | BindingFlags.Instance);

        foreach (var field in fields)
        {
            string propertyName;
            field.SetValue(
                obj,
                info.GetValue(
                TryGetPropertyNameFromBackingField(field.Name, out propertyName) ? GenerateCustomBackingFieldName(propertyName) :
                field.Name,
                field.FieldType));
        }

        return obj;
    }

    private static bool TryGetPropertyNameFromBackingField(string fieldName, out string propertyName)
    {
        var match = _backingFieldRegex.Match(fieldName);
        if (match.Groups.Count == 1)
        {
            propertyName = null;
            return false;
        }

        propertyName = match.Groups[1].Value;
        return true;
    }

    private static string GenerateCustomBackingFieldName(string propertyName)
    {
        return "_" + propertyName + "_k__BackingField";
    }
}

[Serializable]
public class Member
{
    public int Age { get; set; }

    public string Name { get; set; }

    public override string ToString()
    {
        return string.Format("Member {0}, Age: {1}", Name, Age);
    }
}

[Serializable]
public class Club
{
    public string Name { get; set; }

    public IList<Member> Members { get; private set; }

    public Club(string name, IEnumerable<Member> members)
    {
        Name = name;
        //Members = new List<Member>(members);
        Members = members.ToArray();
    }

    public override string ToString()
    {
        if (Members == null)
        {
            return Name;
        }

        return Name + ", Total members: " + Members.Count + "\n\t" +  string.Join("\n\t", Members);
    }
}

如果有人好奇,我尝试这种技术的原因是为了解决导致内部处理 XmlException 的问题,该问题让我担心性能影响。

System.Xml.XmlException occurred
  HResult=-2146232000
  Message=Name cannot begin with the '<' character, hexadecimal value 0x3C.
  Source=System.Xml
  LineNumber=0
  LinePosition=1
  StackTrace:
       at System.Xml.XmlConvert.VerifyNCName(String name, ExceptionType exceptionType)
  InnerException: 

【问题讨论】:

    标签: .net serialization netdatacontractserializer


    【解决方案1】:

    抱歉,我没有给其他人太多回应的机会,但我发现了似乎是根本问题。在查看了基本的 SurrogateSelector 类之后,我意识到也许我没有为集合返回正确类型的代理,这导致我走了几条路,直到我最终尝试检测数组并在这些情况下从 GetSurrogate 返回 null。就像魔法一样,一切似乎都按预期开始了。

    public ISerializationSurrogate GetSurrogate(Type type, StreamingContext context, out ISurrogateSelector selector)
    {
        if (type.IsArray)
        {
            selector = null;
            return null;
        }
    
        selector = null;
        return _surrogate;
    }
    

    起初我有点惊讶,这似乎也纠正了我也遇到问题的其他类型的泛型集合,例如 List,但基本上这些集合似乎仍然分解为数组和直接对象引用,所以也许数组是我失踪的唯一特殊情况。我希望这可以帮助其他可能遇到类似问题的人来处理使用序列化代理类的复杂性。

    【讨论】:

      猜你喜欢
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 2012-11-18
      • 1970-01-01
      • 2011-09-22
      • 2010-12-19
      • 2021-12-23
      相关资源
      最近更新 更多