确保具有特定键的对象静态创建一次

问题描述 投票:0回答:1

我想在 C# 中实现以下内容:

  • 具有属性
    Key
    的类,将用于唯一标识一个对象
  • 一组预定义的静态对象
using System.Text.Json;

public sealed class Test
{
    private static readonly HashSet<string> _existingKeys = [];

    public static readonly Test One = new("one");
    public static readonly Test Two = new("two");

    public Test(string key)
    {
        if (!_existingKeys.Add(key))
        {
            throw new ArgumentException($"Key '{key}' is already in use.");
        }

        Key = key;
    }

    public string Key { get; }
}

// Example usage
class Program
{
    static void Main()
    {
        try
        {
            var three = new Test("one");
        }
        catch (ArgumentException ex)
        {
            Console.WriteLine(ex.Message); // Output: Key 'one' is already in use.
        }

        var one = JsonSerializer.Serialize(Test.One);
        try
        {
            var deserialized = JsonSerializer.Deserialize<Test>(one);
        }
        catch (ArgumentException ex)
        {
            Console.WriteLine(ex.Message); // Output: Key 'one' is already in use.
        }
    }
}

我面临的问题是反序列化,因为它尝试实例化内存中可能已存在的对象。有解决方法吗?我想避免手动维护键映射,因为某些类将有很多预定义的对象,并且很容易错过对象。此外,在某些用例中,需要在另一个程序集中的定义类之外定义对象。

请注意,我只关心用户定义对象的唯一性。

public static readonly Test One = new("one");

诸如反序列化/反射之类的事情超出了范围。

c#
1个回答
0
投票

这看起来像是 Flyweight 模式的变体,因此您可能需要编辑该类,使其看起来像这样:

[JsonConverter(typeof(TestJsonConverter))]
public sealed class Test
{
    private static readonly Dictionary<string, Test> _existingObjects = new();

    public static readonly Test One = Create("one");
    public static readonly Test Two = Create("two");

    private Test(string key)
    {
        Key = key;
    }

    public static Test Create(string key)
    {
        if (_existingObjects.TryGetValue(key, out var existing))
            return existing;

        var newObject = new Test(key);
        _existingObjects[key] = newObject;
        return newObject;
    }

    public string Key { get; }
}

请注意,构造函数现在是

private
,因此所有客户端代码都必须使用
Create
函数。
static
字段
One
Two
也可以执行此操作。

它不是维护一组已知 ID,而是维护已知对象的哈希图。

现在通过键创建新对象永远不会失败,而只是在哈希图中查找已知对象后。

JSON 序列化是棘手的部分,但正如 shingo 的评论所建议的那样你可以使用自定义转换器:

internal class TestJsonConverter : JsonConverter<Test>
{
    public override Test? Read(
        ref Utf8JsonReader reader,
        Type typeToConvert,
        JsonSerializerOptions options)
    {
        var key = reader.GetString();
        if (key is null)
            return null;
        return Test.Create(key);
    }

    public override void Write(
        Utf8JsonWriter writer,
        Test value,
        JsonSerializerOptions options)
    {
        writer.WriteStringValue(value.Key);
    }
}

这只是一个概念证明,您可能需要根据您的特定要求进行修改。

这些测试现已通过:

[Fact]
public void CreateOneByKey()
{
    var actual = Test.Create("one");
    Assert.Equal(Test.One, actual);
}

[Fact]
public void RoundTripOne()
{
    var one = JsonSerializer.Serialize(Test.One);
    var actual = JsonSerializer.Deserialize<Test>(one);
    Assert.Equal(Test.One, actual);
}
© www.soinside.com 2019 - 2024. All rights reserved.