0%

ini文件序列化与反序列化

以下是利用 C# 实现 INI 文件序列化与反序列化 的完整解决方案,结合反射和自定义特性,实现对象与 INI 文件的双向转换


实现方案

  1. 定义特性:标记类和字段对应的 INI 文件区域(Section)和键(Key)。
  2. 核心序列化类:通过反射解析特性,动态读写 INI 文件。
  3. 支持类型转换:自动处理基本类型、日期、枚举等常见数据类型。

完整代码

1. 定义特性类

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
using System;

// 标记类对应的 INI 文件区域(Section)
[AttributeUsage(AttributeTargets.Class)]
public class IniSectionAttribute : Attribute
{
public string SectionName { get; }

public IniSectionAttribute(string sectionName)
{
SectionName = sectionName;
}
}

// 标记字段/属性对应的 INI 键(Key)
[AttributeUsage(AttributeTargets.Field | AttributeTargets.Property)]
public class IniKeyAttribute : Attribute
{
public string KeyName { get; }

public IniKeyAttribute(string keyName = null)
{
KeyName = keyName;
}
}

2. 序列化工具类

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
using System;
using System.Collections.Generic;
using System.IO;
using System.Reflection;
using System.Text;

public static class IniSerializer
{
// 序列化对象到 INI 文件
public static void Serialize<T>(T obj, string filePath)
{
var type = typeof(T);
var sectionAttr = type.GetCustomAttribute<IniSectionAttribute>();
string section = sectionAttr?.SectionName ?? type.Name;

var sb = new StringBuilder();

foreach (var field in type.GetFields(BindingFlags.Public | BindingFlags.Instance))
{
ProcessMember(sb, section, field, field.GetValue(obj));
}

foreach (var prop in type.GetProperties(BindingFlags.Public | BindingFlags.Instance))
{
ProcessMember(sb, section, prop, prop.GetValue(obj));
}

File.WriteAllText(filePath, sb.ToString());
}

// 反序列化 INI 文件到对象
public static T Deserialize<T>(string filePath) where T : new()
{
var obj = new T();
var type = typeof(T);
var sectionAttr = type.GetCustomAttribute<IniSectionAttribute>();
string section = sectionAttr?.SectionName ?? type.Name;

var lines = File.ReadAllLines(filePath);
var data = new Dictionary<string, string>();

foreach (var line in lines)
{
if (line.StartsWith("[") && line.EndsWith("]"))
{
// 处理多 Section(此处示例仅处理单个 Section)
}
else if (line.Contains("="))
{
var parts = line.Split(new[] { '=' }, 2);
data[parts[0].Trim()] = parts[1].Trim();
}
}

foreach (var field in type.GetFields(BindingFlags.Public | BindingFlags.Instance))
{
SetMemberValue(obj, field, data);
}

foreach (var prop in type.GetProperties(BindingFlags.Public | BindingFlags.Instance))
{
SetMemberValue(obj, prop, data);
}

return obj;
}

private static void ProcessMember(StringBuilder sb, string section, MemberInfo member, object value)
{
var keyAttr = member.GetCustomAttribute<IniKeyAttribute>();
string keyName = keyAttr?.KeyName ?? member.Name;

if (value != null)
{
sb.AppendLine($"{keyName}={ConvertToString(value)}");
}
}

private static void SetMemberValue<T>(T obj, MemberInfo member, Dictionary<string, string> data)
{
var keyAttr = member.GetCustomAttribute<IniKeyAttribute>();
string keyName = keyAttr?.KeyName ?? member.Name;

if (data.TryGetValue(keyName, out string strValue))
{
object value = ConvertFromString(strValue, GetMemberType(member));

if (member is FieldInfo field)
field.SetValue(obj, value);
else if (member is PropertyInfo prop && prop.CanWrite)
prop.SetValue(obj, value);
}
}

private static Type GetMemberType(MemberInfo member)
{
return member.MemberType switch
{
MemberTypes.Field => ((FieldInfo)member).FieldType,
MemberTypes.Property => ((PropertyInfo)member).PropertyType,
_ => throw new NotSupportedException()
};
}

// 类型转换方法
private static string ConvertToString(object value)
{
return value switch
{
DateTime dt => dt.ToString("yyyy-MM-dd HH:mm:ss"),
Enum e => e.ToString(),
_ => value.ToString()
};
}

private static object ConvertFromString(string value, Type targetType)
{
if (targetType == typeof(string))
return value;

if (targetType.IsEnum)
return Enum.Parse(targetType, value);

return Convert.ChangeType(value, targetType);
}
}

使用示例

1. 定义数据类

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
[IniSection("UserSettings")]
public class UserConfig
{
[IniKey("username")]
public string Name { get; set; }

[IniKey("age")]
public int Age;

[IniKey("last_login")]
public DateTime LastLogin;

[IniKey("theme")] // 使用默认键名 "theme"
public string InterfaceTheme = "default";
}

2. 序列化对象到 INI

1
2
3
4
5
6
7
8
9
var config = new UserConfig
{
Name = "Alice",
Age = 30,
LastLogin = DateTime.Now,
InterfaceTheme = "dark"
};

IniSerializer.Serialize(config, "user_config.ini");

生成文件内容 (user_config.ini):

1
2
3
4
username=Alice
age=30
last_login=2023-10-05 14:30:00
theme=dark

3. 反序列化 INI 到对象

1
2
3
4
var loadedConfig = IniSerializer.Deserialize<UserConfig>("user_config.ini");
Console.WriteLine(loadedConfig.Name); // 输出: Alice
Console.WriteLine(loadedConfig.Age); // 输出: 30
Console.WriteLine(loadedConfig.LastLogin); // 输出: 2023/10/5 14:30:00

高级功能扩展

1. 支持多 Section

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
public class AppConfig
{
[IniSection("User")]
public UserConfig User { get; set; }

[IniSection("System")]
public SystemConfig System { get; set; }
}

// 修改序列化逻辑,递归处理嵌套对象
private static void ProcessMember(StringBuilder sb, string parentSection, MemberInfo member, object value)
{
var sectionAttr = member.DeclaringType.GetCustomAttribute<IniSectionAttribute>();
string currentSection = sectionAttr?.SectionName ?? parentSection;

if (value.GetType().IsClass && value.GetType() != typeof(string))
{
foreach (var subMember in value.GetType().GetMembers())
{
ProcessMember(sb, currentSection, subMember, subMember.GetValue(value));
}
}
else
{
// 原有处理逻辑
}
}

2. 自定义类型转换器

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
public interface IIniTypeConverter
{
string ConvertToString(object value);
object ConvertFromString(string value, Type targetType);
}

// 注册自定义转换器
public static class IniConverters
{
public static Dictionary<Type, IIniTypeConverter> Converters = new Dictionary<Type, IIniTypeConverter>
{
{ typeof(Version), new VersionConverter() }
};
}

public class VersionConverter : IIniTypeConverter
{
public string ConvertToString(object value) => ((Version)value).ToString();
public object ConvertFromString(string value, Type targetType) => Version.Parse(value);
}

3. 处理注释(扩展特性)

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
[AttributeUsage(AttributeTargets.Class | AttributeTargets.Field | AttributeTargets.Property)]
public class IniCommentAttribute : Attribute
{
public string Comment { get; }

public IniCommentAttribute(string comment)
{
Comment = comment;
}
}

// 在序列化时添加注释
if (member.GetCustomAttribute<IniCommentAttribute>() is { } commentAttr)
{
sb.AppendLine($"; {commentAttr.Comment}");
}

关键注意事项

  1. 性能优化

    • 对频繁调用的类型缓存 MemberInfo 和特性信息
    • 使用 StringBuilder 替代字符串拼接
  2. 错误处理

    1
    2
    3
    4
    5
    6
    7
    8
    try
    {
    return Convert.ChangeType(value, targetType);
    }
    catch (Exception ex)
    {
    throw new IniSerializationException($"Type conversion failed: {value} -> {targetType.Name}", ex);
    }
  3. 编码处理

    • 使用 Encoding.UTF8 读写文件:
    1
    File.WriteAllText(filePath, sb.ToString(), Encoding.UTF8);
  4. 安全限制

    • 处理私有成员需添加 BindingFlags.NonPublic
    • 对不可写属性跳过赋值

方案对比

方法 优点 缺点
手动读写 完全控制逻辑 代码冗余,维护成本高
反射+特性 自动化,代码简洁 性能较低
第三方库 功能完善(如 IniParser 需要引入外部依赖
Source Generators 零运行时开销,高性能 实现复杂度高

本方案通过反射和特性实现了 INI 文件与对象的双向转换,适用于配置管理、游戏存档等场景。对于高性能需求,建议结合缓存机制或使用预编译技术优化。