自定义HashSet的 JSON反序列化

编程语言 2026-07-12

我在使用一个C#库,它实现了一个自定义的 HashSet<T>,名为 ObservableHashSet<T>。你可以在这里找到更多信息 这里。这里也有 ObservableHashSet的源代码

简短版本:

在对 JsonConvert.PopulateObject 在一个 ObservableHashSet 上使用时,会创建一个新的HashSet实例(这会中断此前附加到它的可观察事件),而不是使用Clear和 Add方法来填充HashSet。

尽管 ObservableHashSet 类型确实实现了 IEnumerable 接口,但这也许还不够?如果我需要修改这个类,为了使反序列化能够正确填充对象,应该实现哪个接口?

示例

举例来说,考虑以下情况:

public class ContactLibrary
{
    [JsonProperty] public ObservableHashSet<People> peoples = new ObservableHashSet<People>();
}

public class People
{
    public string name;
    public People(string name)
    {
        this.name = name;
    }
}

public class Main
{
    public void TestMethod()
    {
        //Setup objects
        ContactLibrary contacts = new ContactLibrary();
        contacts.peoples.CollectionChanged += Peoples_CollectionChanged;

        //Add a contact
        contacts.peoples.Add(new People("Charlie")); //Trigger a CollectionChanged

        //Save the current contacts using json serialization
        string json_save = JsonConvert.SerializeObject(contacts);

        //Add a new contact
        contacts.peoples.Add(new People("Emma")); //Trigger a CollectionChanged

        //Actually, revert back to cancel adding the last contact
        JsonConvert.PopulateObject(json_save, contacts);

        //Add a new contact
        contacts.peoples.Add(new People("Emily")); //DOES NOT Trigger a CollectionChanged, because people got re-instantiated.
    }

    private void Peoples_CollectionChanged(INotifyCollectionChangedEventArgs<People> e)
    {
        switch(e.Action)
        {
            case System.Collections.Specialized.NotifyCollectionChangedAction.Add:
                Debug.Log("New people added : " + e.NewItem.name);
            break;
        }
    }
}

我正在使用一个JSON字符串来存储类A 的一个实例的状态,并用这个字符串来还原一个先前的状态。通过这样做,JsonConvert.PopulateObjectObservableHashSet<People> peoples 字段创建了一个新实例,打破了在peoples字段的第一个实例上设置的事件监听器。

解决方案

正如 @HootanHT的已删除答案中所提到的,你应该实现 ICollection<T>。这是Json.NET实际在决定是否能够就地填充集合时所查找的接口契约。Json.NET的工作逻辑如下:

  • 如果属性不为null且实现 ICollection<T>,则向其中添加项。它不会调用 Clear()(例如你可以拥有默认项)。
  • 否则创建一个新集合。

但你不需要把它封装在另一个类型中。你可以直接派生一个实现 ICollection<T> 的新类型。大多数函数已经由基类实现,你只需要一个透传的 Add 以及 CopyTo

public class MyObservableHashSet<T> : ObservableHashSet<T>, ICollection<T>
    where T : notnull
{
    public MyObservableHashSet() : base() {}    
    public MyObservableHashSet(int capacity) : base(capacity) {}
    public MyObservableHashSet(IEqualityComparer<T>? comparer) : base(comparer) {}
    public MyObservableHashSet(IEnumerable<T> collection) : base(collection) {}
    public MyObservableHashSet(IEnumerable<T> collection, IEqualityComparer<T>? comparer) : base(collection, comparer) {}

    void ICollection<T>.Add(T item) => Add(item);

    public void CopyTo(T[] array, int arrayIndex)
    {
        if (Count + arrayIndex > array.Length) throw new IndexOutOfRangeException();
        var i = arrayIndex;
        foreach (var item in this)
            array[i++] = item;
    }
}
站内所有文章版权归属LeftHeroAI导航站,无授权禁止任何主体转载、抄袭、复制内容,亦不得私自架设镜像站点。一经侵权,本站将通过法律途径追责。

相关文章