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
相关产品推荐
相关产品推荐

