在使用自定义ISerializationSurrogate时,如何序列化嵌套集合?

时间:2013-10-03 19:13:20

标签: .net serialization netdatacontractserializer

我正在尝试使用通用序列化代理来控制通过使用NetDataContractSerializer序列化的字段的命名。这个概念到目前为止工作得很好,除了我遇到一个问题,我遇到一个字段,该字段在GetObjectData方法中持有对集合的引用,调用AddValue并且为它提供我的集合类型似乎导致没有项目在序列化的集合中。对于诸如数组或列表之类的集合,我需要做些什么特别的事情吗?

我已经在下面添加了一个完整代码的副本,希望有人能指出我正确的方向。

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: 

1 个答案:

答案 0 :(得分:0)

对不起,我没有给别人太多机会回应,但我发现了什么似乎是潜在的问题。在查看了基础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,我也遇到了问题,但基本上看起来这些集合仍然分解为数组和直接对象引用,所以也许数组是我失踪的唯一特例。我希望这可能会帮助那些可能遇到类似问题的人处理使用序列化代理类的复杂性。