You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

C#中无需实例获取子类属性实现通用JSON序列化加载

问题:JSON序列化加载时避免创建无用实例获取扩展名

我需要给多个类实现磁盘数据的JSON序列化保存与加载功能,计划通过抽象基类复用代码,且每个子类对应不同的文件扩展名。保存功能因持有实例,可直接通过抽象属性实现,但加载时没有实例,且C#不支持抽象静态属性,当前加载方法必须创建一个无实际用途的实例来获取扩展名。我希望找到仅通过基类函数实现的更优方案,目前已有子类实现的替代方案,但仍寻求更好的解决办法。

当前代码实现:

// 这个方法没问题,虽然其实不需要new约束,但为了和Load函数保持一致就保留了
public static void Save<T>(string filename, T obj) where T : JsonSerializeable, new() {
    if (filename.IsNullOrWhiteSpace()) filename = $"{DEFAULT_FILENAME}";

    string path = Path.ChangeExtension($"{DIRECTORY_NAME}/{filename}", obj.fileExtension);

    path = Path.ChangeExtension(path, obj.fileExtension);

    if (!Directory.Exists(DIRECTORY_NAME)) Directory.CreateDirectory(DIRECTORY_NAME);

    File.WriteAllText(path, JsonConvert.SerializeObject(obj, Formatting.None));
}

// 这个方法能运行,但我很反感必须创建一个实例只是为了获取属性值
public static bool Load<T>(string filename, out T obj) where T : JsonSerializeable, new() {
    if (filename.IsNullOrWhiteSpace() || !Directory.Exists(DIRECTORY_NAME)) {
        obj = default;
        return false;
    }

    string fileExtension = new T().fileExtension; // TODO -.-

    string path = Path.ChangeExtension($"{DIRECTORY_NAME}/{filename}", fileExtension);

    if (!File.Exists(path)) {
        obj = default;
        return false;
    }

    obj = JsonConvert.DeserializeObject<T>(File.ReadAllText(path));

    return true;
}

可行解决方案

方案1:使用自定义特性标记扩展名

通过特性为每个子类绑定扩展名,加载时利用反射读取特性值,无需创建实例。

首先定义特性:

[AttributeUsage(AttributeTargets.Class)]
public class FileExtensionAttribute : Attribute
{
    public string Extension { get; }

    public FileExtensionAttribute(string extension)
    {
        // 确保扩展名带.前缀
        Extension = extension.StartsWith(".") ? extension : $".{extension}";
    }
}

给子类标记特性:

[FileExtension(".data")]
public class MyDataClass : JsonSerializeable
{
    public override string fileExtension => ".data";
    // 其他类实现
}

修改Load方法:

public static bool Load<T>(string filename, out T obj) where T : JsonSerializeable
{
    obj = default;

    if (string.IsNullOrWhiteSpace(filename) || !Directory.Exists(DIRECTORY_NAME))
        return false;

    // 读取类型上的特性
    var extensionAttr = typeof(T).GetCustomAttribute<FileExtensionAttribute>();
    if (extensionAttr == null)
        throw new InvalidOperationException($"类型 {typeof(T)} 未标记FileExtensionAttribute特性");

    string path = Path.ChangeExtension(Path.Combine(DIRECTORY_NAME, filename), extensionAttr.Extension);

    if (!File.Exists(path))
        return false;

    obj = JsonConvert.DeserializeObject<T>(File.ReadAllText(path));
    return true;
}

方案2:静态字典维护类型-扩展名映射

在基类中维护一个静态字典,子类通过静态构造函数注册自身的扩展名,加载时直接从字典中查询。

修改抽象基类:

public abstract class JsonSerializeable
{
    public abstract string fileExtension { get; }
    // 存储类型到扩展名的映射
    internal static readonly Dictionary<Type, string> ExtensionMap = new();

    // 子类调用此方法注册扩展名
    protected static void RegisterExtension<T>(string extension) where T : JsonSerializeable
    {
        var formattedExtension = extension.StartsWith(".") ? extension : $".{extension}";
        ExtensionMap[typeof(T)] = formattedExtension;
    }
}

子类实现静态构造函数注册:

public class MyDataClass : JsonSerializeable
{
    static MyDataClass()
    {
        RegisterExtension<MyDataClass>(".data");
    }

    public override string fileExtension => ".data";
    // 其他类实现
}

更新Load方法:

public static bool Load<T>(string filename, out T obj) where T : JsonSerializeable
{
    obj = default;

    if (string.IsNullOrWhiteSpace(filename) || !Directory.Exists(DIRECTORY_NAME))
        return false;

    if (!JsonSerializeable.ExtensionMap.TryGetValue(typeof(T), out var fileExtension))
        throw new InvalidOperationException($"类型 {typeof(T)} 未注册扩展名");

    string path = Path.ChangeExtension(Path.Combine(DIRECTORY_NAME, filename), fileExtension);

    if (!File.Exists(path))
        return false;

    obj = JsonConvert.DeserializeObject<T>(File.ReadAllText(path));
    return true;
}

方案3:C# 9.0+ 静态抽象接口成员(推荐)

如果使用C# 9及以上版本,可以将JsonSerializeable改为接口,定义静态抽象属性,直接通过类型访问扩展名,完全无需实例。

定义接口:

public interface IJsonSerializeable
{
    // 静态抽象属性,子类必须实现
    static abstract string FileExtension { get; }
}

子类实现接口:

public class MyDataClass : IJsonSerializeable
{
    public static string FileExtension => ".data";
    // 其他类实现
}

修改Save和Load方法:

public static void Save<T>(string filename, T obj) where T : IJsonSerializeable
{
    if (string.IsNullOrWhiteSpace(filename)) filename = DEFAULT_FILENAME;

    string path = Path.ChangeExtension(Path.Combine(DIRECTORY_NAME, filename), T.FileExtension);

    if (!Directory.Exists(DIRECTORY_NAME)) Directory.CreateDirectory(DIRECTORY_NAME);

    File.WriteAllText(path, JsonConvert.SerializeObject(obj, Formatting.None));
}

public static bool Load<T>(string filename, out T obj) where T : IJsonSerializeable, new()
{
    obj = default;

    if (string.IsNullOrWhiteSpace(filename) || !Directory.Exists(DIRECTORY_NAME))
        return false;

    string path = Path.ChangeExtension(Path.Combine(DIRECTORY_NAME, filename), T.FileExtension);

    if (!File.Exists(path))
        return false;

    obj = JsonConvert.DeserializeObject<T>(File.ReadAllText(path));
    return true;
}

内容的提问来源于stack exchange,提问作者Piflik

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.08.03 08:25:32