一个强大易用的配置类: DictionaryHelper
郝伟 2022/01/30

1 简介

在Windows平台进行配置读取时,通常使用系统提供的系统提供的ini的读取API:

[DllImport("kernel32")]
private static extern bool GetPrivateProfileString(string lpApplicationName, string lpKeyName, string lpDefault, StringBuilder lpReturnedString, int nSize, string lpFileName);
[DllImport("kernel32")]
private static extern bool WritePrivateProfileString(string lpApplicationName, string lpKeyName, string lpString, string lpFileName);

使用这两个函数进行进行文件读取,具有以下问题:

  1. 每次都要读写都需要多个参数,不易于使用;
  2. 函数执行时实际上需要遍历整个配置文件,IO性能比较差;

另外,记录通用使用字典表示,即使用key-value表示,这也会存在问题:

  1. key通常是由字符串表示的,而字符串编码是是不验证的,所以可能会导致出错;
  2. value通常也是字符串,所以也会有转换的问题。

为解决以上问题,特编写此类。

2 使用方法

本类名为 DictionaryHelper,内部使用字典表示,提供了一系列的方法进行操作。主要使用方法参考以下示例。

static void DictionaryTest()
{
    // 直接初始化一个新对象。
    var dict = new DictionaryHelper();
    
    // 也可以从一个文件中初始化。
    dict = new DictionaryHelper ("config.dict"); 

    // 读取值,两种方式,具体可以对比v5和v6
    var v1 = dict.GetInt(cf.port, 0);
    var v2 = dict.GetFloat(cf.Temperature, 0);
    var v3 = dict.GetDouble(cf.TestValue, 0);
    var v4 = dict.GetDateTime("LastUpdateTime");
    var v5 = dict.GetBoolean(cf.ShowConfig);
    var v6 = dict[cf.ShowConfig];

    Console.WriteLine($"v1={v1},\ttype={v1.GetType().Name}");
    Console.WriteLine($"v2={v2},\ttype={v2.GetType().Name}");
    Console.WriteLine($"v3={v3},\ttype={v3.GetType().Name}");
    Console.WriteLine($"v4={v4},\ttype={v4.GetType().Name}");
    Console.WriteLine($"v5={v5},\ttype={v5.GetType().Name}");
    Console.WriteLine($"v6={v6},\ttype={v6.GetType().Name}\n");


    // 设置值,有以下两种方式
    // 方式1:直接使用字符串赋值
    dict["Name"]="Jack";
    dict[cf.port] = 10082 + "";

    // 方式2:使用SetValue方式
    dict.SetValue("FinalValue", 15.232);
    dict.SetValue("CurrentDate", DateTime.Now);


    // 输出为配置文件
    Console.WriteLine(dict.ToCSharpString() + "\n");

    // 保存为config.dict
    dict.Save("config.dict");
}

可以得到以下输出

v1=10082,       type=Int32
v2=39.2,        type=Single
v3=192.5322,    type=Double
v4=2021/10/15 15:35:22, type=DateTime

public class cf
{
    public static string ip = "ip";
    public static string port = "port";
    public static string user = "user";
    public static string pwd = "pwd";
    public static string mapping1 = "mapping1";
    public static string mapping2 = "mapping2";
    public static string Name = "Name";
    public static string LastUpdateTime = "LastUpdateTime";
    public static string Temperature = "Temperature";
    public static string TestValue = "TestValue";
    public static string ShowConfig = "ShowConfig";
}

3 生成配置类

通过调用 ToCSharpString() 方法,可以生成文件 cf.cs 类,可以添加到程序中,用于方便代码输入,具体内容如下。这样使用 cf.ip 就可以表示 “ip”。

public class cf
{
    public static string ip = "ip";
    public static string port = "port";
    public static string user = "user";
    public static string pwd = "pwd";
    public static string mapping1 = "mapping1";
    public static string mapping2 = "mapping2";
    public static string Name = "Name";
    public static string LastUpdateTime = "LastUpdateTime";
    public static string Temperature = "Temperature";
    public static string TestValue = "TestValue";
    public static string ShowConfig = "ShowConfig";
}

4 配置文件结构

以下是输出的 config.dict 文件。

ip=192.168.14.53
port=10082
user=administrator
pwd=123456
mapping1=C:\data\CTL01|/root/CTL/01
mapping2=C:\data\CTL02|/root/CTL/01
Name=Jack
LastUpdateTime=2021-10-15 15:35:22

5 实现代码

以下为 DictionaryHelper.cs 类的源代码。

using System.IO;
using System.Collections.Generic;
using System.Text;
using System;
    
/// <summary>
/// 用于从字符中加载数据。
/// </summary>
public class DictionaryHelper
{
    /// <summary>
    /// 用于存储变量的值。
    /// </summary>
    Dictionary<string, string> dict = new Dictionary<string, string>();

    string configFilepath = "config.dict";
    /// <summary>
    /// 配置文件路径,使用的格式为字典。
    /// </summary>
    public string ConfigFilepath
    {
        get => configFilepath;
    }

    /// <summary>
    /// 将输入字符串解析为int,如果解析成功,返回解析值,否则返回defaultValue(默认值为-1)。
    /// </summary>
    /// <param name="key">输入字符串</param>
    /// <param name="defaultValue">默认值,解析失败时返回此值。</param>
    /// <returns></returns>
    public int GetInt(string key, int defaultValue = -1)
    {
        var value = this[key];
        return value != null && int.TryParse(value, out int v) ? v : defaultValue;
    }

    /// <summary>
    /// 将输入字符串解析为float,如果解析成功,返回解析值,否则返回defaultValue(默认值为-1)。
    /// </summary>
    /// <param name="key">输入字符串</param>
    /// <param name="defaultValue">默认值,解析失败时返回此值。</param>
    /// <returns></returns>
    public float GetFloat(string key, float defaultValue = -1)
    {
        var value = this[key];
        return value != null && float.TryParse(value, out float v) ? v : defaultValue;
    }

    /// <summary>
    /// 将输入字符串解析为double,如果解析成功,返回解析值,否则返回defaultValue(默认值为-1)。
    /// </summary>
    /// <param name="key">输入字符串</param>
    /// <param name="defaultValue">默认值,解析失败时返回此值。</param>
    /// <returns></returns>
    public double GetDouble(string key, double defaultValue = -1)
    {
        var value = this[key];
        return value != null && double.TryParse(value, out double v) ? v : defaultValue;
    }


    /// <summary>
    /// 将输入字符串解析为 boolean,如果解析成功,返回解析值,否则返回defaultValue(默认值为false)。
    /// 为了保证使用更方便,结果为 true 时不区别大小写,或者结果为1也可以。
    /// </summary>
    /// <param name="key">输入字符串</param>
    /// <param name="defaultValue">默认值,解析失败时返回此值。</param>
    /// <returns></returns>
    public bool GetBoolean(string key, bool defaultValue = false)
    {
        var value = this[key];
        return value != null && (value.ToLower() == "true" || value == "1") ? true : defaultValue;
    }

    /// <summary>
    /// 将输入字符串解析为 DateTime,如果解析成功,返回解析值,否则返回 DateTime.MinValue。
    /// </summary>
    /// <param name="key">输入字符串</param>
    /// <returns></returns>
    public DateTime GetDateTime(string key)
    {
        var v = dict[key];
        DateTime dt = DateTime.MinValue;
        try { dt = DateTime.Parse(v); } catch { }
        return dt;
    }

    /// <summary>
    /// 初始化空字典,不进行任何加载。
    /// </summary>
    public DictionaryHelper() { }

    /// <summary>
    /// 使用dictfile初始化,如果失败则返回 new Dictionary<string, string>对象。 
    /// </summary>
    /// <param name="dictfile"></param>
    public DictionaryHelper(string dictfile)
    {
        configFilepath = dictfile;
        Load(configFilepath);
    }


    /// <summary>
    /// 返回所有的密钥,只读属性,每次返回新实例,修改不会影响原Keys。
    /// </summary>
    public List<string> Keys
    {
        get
        {
            List<string> keys = new List<string>();
            foreach (string key in dict.Keys)
                keys.Add(key);
            return keys;
        }
    }

    /// <summary>
    /// 字典中是否包括指定的值。
    /// </summary>
    /// <param name="key"></param>
    /// <returns></returns>
    public bool ContainsKey(string key) => key !=null && dict.ContainsKey(key);


    /// <summary>
    /// 如果字典dict中包括key,则返回 dict[key],否则返回null
    /// </summary>
    /// <param name="key">访问的键。</param>
    /// <returns></returns>
    public string this[string key]
    {
        get => key != null && dict != null && dict.ContainsKey(key) ? dict[key] : null;
        set
        {
            // Dictionary 类可以直接使用 dict[key]=value 的方式添加,【无论key是否存在!】
            if (value != null)
                dict[key] = value;
        }
    }

    /// <summary>
    /// 将目标对象object以键key赋值给字典。
    /// </summary>
    /// <param name="key">键值名。</param>
    /// <param name="value">数据对象。</param>
    public void SetValue(string key, object value)
    {
        if (value != null)
            this[key] = value.ToString();
    }

    /// <summary>
    /// 从文件中加载数据内容,加载失败则字典内容为空,但不是null。
    /// </summary>
    /// <param name="configfile">默认为空,使用 ConfigFilepath 进行加载,如果不存在,则为空字典,但不是null。</param>
    public void Load(string configfile = null)
    {
        dict = LoadDictionary(configfile == null ? ConfigFilepath : configfile);
        dict = dict == null ? new Dictionary<string, string>() : dict;
    }

    /// <summary>
    /// 将文件
    /// </summary>
    /// <param name="filepath"></param>
    public void Save(string filepath)
    {
        StringBuilder sb = new StringBuilder();
        foreach (var key in dict.Keys)
            sb.AppendLine($"{key}={dict[key]}");
        File.WriteAllText(filepath, sb.ToString(), Encoding.UTF8);
    }

    /// <summary>
    /// 根据当前类,生成用于访问的CS类。
    /// </summary>
    /// <returns></returns>
    public string ToCSharpString(string className = "cf")
    {
        StringBuilder sb = new StringBuilder();
        sb.AppendLine($"public class {className}");
        sb.AppendLine("{");

        foreach (var key in dict.Keys)
            sb.AppendLine($"    public static string {key} = \"{key}\";");

        sb.AppendLine("}");
        return sb.ToString();
    }

    /// <summary>
    /// 从文件中加载字典,加载失败则返回null。
    /// </summary> 
    /// <param name="configFile"></param>
    public static Dictionary<string, string> LoadDictionary(string configFile, char separator = '=', string encoding = "UTF-8")
    {
        // 文件如果不存在则返回null。
        var dicfile = Path.GetFullPath(configFile);
        if (!File.Exists(dicfile))
            return null;

        Dictionary<string, string> dic = new Dictionary<string, string>();
        foreach (var row in File.ReadAllLines(dicfile))
        {
            // 如果右侧有空格,则会被清除
            var rowdata = row.TrimStart();

            // 如果是注释(以#或//开头的行),则跳过
            if (rowdata.StartsWith("#") || rowdata.StartsWith("//"))
                continue;

            // 定位
            int pos = row.IndexOf('=');
            if (pos < 0 || pos >= row.Length)
                continue;

            // 解析出Key和Value进行添加
            string key = row.Substring(0, pos);
            string value = row.Substring(pos + 1);
            if (!dic.ContainsKey(key))
                dic.Add(key, value);
        }
        return dic;
    }
}