【问题标题】:Can .NET load and parse a properties file equivalent to Java Properties class?.NET 可以加载和解析与 Java Properties 类等效的属性文件吗?
【发布时间】:2010-10-03 21:27:49
【问题描述】:

在 C# 中是否有一种简单的方法来读取属性文件,该文件在单独的行中包含每个属性,后跟等号和值,如下所示:

ServerName=prod-srv1
Port=8888
CustomProperty=Any value

在 Java 中,Properties 类可以轻松处理这种解析:

Properties myProperties=new Properties();
FileInputStream fis = new FileInputStream (new File("CustomProps.properties"));
myProperties.load(fis);
System.out.println(myProperties.getProperty("ServerName"));
System.out.println(myProperties.getProperty("CustomProperty"));

我可以轻松地在 C# 中加载文件并解析每一行,但是是否有一种内置方法可以轻松获取属性,而无需自己解析键名和等号?我发现的 C# 信息似乎总是偏爱 XML,但这是一个我无法控制的现有文件,我更愿意将其保留为现有格式,因为它需要更多时间让另一个团队将其更改为 XML而不是解析现有文件。

【问题讨论】:

标签: c# configuration file-io load


【解决方案1】:

不,没有对此的内置支持。

您必须制作自己的“INIFileReader”。 也许是这样的?

var data = new Dictionary<string, string>();
foreach (var row in File.ReadAllLines(PATH_TO_FILE))
  data.Add(row.Split('=')[0], string.Join("=",row.Split('=').Skip(1).ToArray()));

Console.WriteLine(data["ServerName"]);

编辑:更新以反映 Paul 的评论。

【讨论】:

  • 如果属性值包含'=',是一个不好的方法
【解决方案2】:

大多数 Java“.properties”文件可以通过假设“=”作为分隔符来进行拆分 - 但格式要复杂得多,并且允许在属性名称中嵌入空格、等号、换行符和任何 Unicode 字符或价值。

我需要为 C# 应用程序加载一些 Java 属性,因此我实现了 JavaProperties.cs 以使用与 Java 版本相同的方法正确读取和写入“.properties”格式的文件 - 您可以在 http://www.kajabity.com/index.php/2009/06/loading-java-properties-files-in-csharp/ 找到它。

在那里,您会找到一个 zip 文件,其中包含该类的 C# 源代码和一些我测试过的示例属性文件。

享受吧!

【讨论】:

  • 这些类的代码现在在 GitHub 上。链接在主站点上。
【解决方案3】:

最后一节课。谢谢@eXXL

public class Properties
{
    private Dictionary<String, String> list;
    private String filename;

    public Properties(String file)
    {
        reload(file);
    }

    public String get(String field, String defValue)
    {
        return (get(field) == null) ? (defValue) : (get(field));
    }
    public String get(String field)
    {
        return (list.ContainsKey(field))?(list[field]):(null);
    }

    public void set(String field, Object value)
    {
        if (!list.ContainsKey(field))
            list.Add(field, value.ToString());
        else
            list[field] = value.ToString();
    }

    public void Save()
    {
        Save(this.filename);
    }

    public void Save(String filename)
    {
        this.filename = filename;

        if (!System.IO.File.Exists(filename))
            System.IO.File.Create(filename);

        System.IO.StreamWriter file = new System.IO.StreamWriter(filename);

        foreach(String prop in list.Keys.ToArray())
            if (!String.IsNullOrWhiteSpace(list[prop]))
                file.WriteLine(prop + "=" + list[prop]);

        file.Close();
    }

    public void reload()
    {
        reload(this.filename);
    }

    public void reload(String filename)
    {
        this.filename = filename;
        list = new Dictionary<String, String>();

        if (System.IO.File.Exists(filename))
            loadFromFile(filename);
        else
            System.IO.File.Create(filename);
    }

    private void loadFromFile(String file)
    {
        foreach (String line in System.IO.File.ReadAllLines(file))
        {
            if ((!String.IsNullOrEmpty(line)) &&
                (!line.StartsWith(";")) &&
                (!line.StartsWith("#")) &&
                (!line.StartsWith("'")) &&
                (line.Contains('=')))
            {
                int index = line.IndexOf('=');
                String key = line.Substring(0, index).Trim();
                String value = line.Substring(index + 1).Trim();

                if ((value.StartsWith("\"") && value.EndsWith("\"")) ||
                    (value.StartsWith("'") && value.EndsWith("'")))
                {
                    value = value.Substring(1, value.Length - 2);
                }

                try
                {
                    //ignore dublicates
                    list.Add(key, value);
                }
                catch { }
            }
        }
    }


}

使用示例:

//load
Properties config = new Properties(fileConfig);
//get value whith default value
com_port.Text = config.get("com_port", "1");
//set value
config.set("com_port", com_port.Text);
//save
config.Save()

【讨论】:

  • 非常有用的代码,但如果属性文件不存在,我会收到错误消息:调用 reload() 的构造函数创建文件并保持锁定,所以 @987654325 @无法保存。
  • 感谢代码尼克。在 System.IO.File.Create(filename) 中,我们可以添加一个.Close() 以确保文件不会保持打开状态。
【解决方案4】:

旧问题(2009 年 1 月)的另一个答案(2018 年 1 月)。

Java 属性文件的规范在@987654321@ 的JavaDoc 中有描述。一个问题是规范比我们可能的第一印象有点复杂。另一个问题是这里的一些答案随意添加了额外的规范——例如,;' 被视为注释行的开头,但它们不应该是。属性值周围的双引号/单引号被删除,但它们不应该被删除。

以下是需要考虑的点。

  1. 线有两种,自然线逻辑线
  2. 自然行以\n\r\r\n 或流的结尾结束。
  3. 通过使用反斜杠字符 \ 转义行终止符序列,可以将逻辑行分散到多个相邻的自然行中。
  4. 逻辑行中第二行和后续自然行开头的任何空白都将被丢弃。
  5. 空白是空格(\u0020)、制表符(\t\u0009)和换页(\f\u000C)。
  6. 正如规范中明确指出的那样,“仅检查行终止符序列之前的字符来确定行终止符是否被转义是不够的;行终止符必须有奇数个连续的反斜杠被转义。由于输入是从左到右处理的,因此在行终止符(或其他地方)之前的非零偶数个 2n 个连续反斜杠在转义处理后对 n 个反斜杠进行编码。"
  7. = 用作键和值之间的分隔符。
  8. : 也用作键和值之间的分隔符。
  9. 键和值之间的分隔符可以省略。
  10. 注释行的第一个非空白字符是#!,这意味着#! 之前的前导空白是允许的。
  11. 注释行不能扩展到下一个自然行,即使它的行终止符前面是\
  12. 如规范中明确说明的那样,=: 和空格如果被反斜杠转义,则可以嵌入到密钥中。
  13. 使用\r\n 转义序列甚至可以包含行终止符。
  14. 如果省略值,则使用空字符串作为值。
  15. \uxxxx 用于表示 Unicode 字符。
  16. 无效转义字符之前的反斜杠字符不会被视为错误;它被默默地丢弃。

因此,例如,如果test.properties 具有以下内容:

# A comment line that starts with '#'.
   # This is a comment line having leading white spaces.
! A comment line that starts with '!'.

key1=value1
  key2 : value2
    key3 value3
key\
  4=value\
    4
\u006B\u0065\u00795=\u0076\u0061\u006c\u0075\u00655
\k\e\y\6=\v\a\lu\e\6

\:\ \= = \\colon\\space\\equal

它应该被解释为以下键值对。

+------+--------------------+
| KEY  | VALUE              |
+------+--------------------+
| key1 | value1             |
| key2 | value2             |
| key3 | value3             |
| key4 | value4             |
| key5 | value5             |
| key6 | value6             |
| : =  | \colon\space\equal |
+------+--------------------+

Authlete.Authlete NuGet 包中的PropertiesLoader 类可以解释规范的格式。下面的示例代码:

using System;
using System.IO;
using System.Collections.Generic;
using Authlete.Util;

namespace MyApp
{
    class Program
    {
        public static void Main(string[] args)
        {
            string file = "test.properties";
            IDictionary<string, string> properties;

            using (TextReader reader = new StreamReader(file))
            {
                properties = PropertiesLoader.Load(reader);
            }

            foreach (var entry in properties)
            {
                Console.WriteLine($"{entry.Key} = {entry.Value}");
            }
        }
    }
}

将生成此输出:

key1 = value1
key2 = value2
key3 = value3
key4 = value4
key5 = value5
key6 = value6
: = = \colon\space\equal

Java 中的一个等价示例如下:

import java.util.*;
import java.io.*;

public class Program
{
    public static void main(String[] args) throws IOException
    {
        String file = "test.properties";
        Properties properties = new Properties();

        try (Reader reader = new FileReader(file))
        {
             properties.load(reader);
        }

        for (Map.Entry<Object, Object> entry : properties.entrySet())
        {
            System.out.format("%s = %s\n", entry.getKey(), entry.getValue());
        }    
    }
}

源代码@987654323@ 可以在authlete-csharp 中找到。 xUnitPropertiesLoader 的测试写在 @987654326@ 中。

【讨论】:

    【解决方案5】:

    我编写了一个方法,允许在文件中使用空行、注释和引用。

    例子:

    var1="value1"
    var2='value2'

    'var3=outcommented
    ;var4=也被淘汰了

    方法如下:

    public static IDictionary ReadDictionaryFile(string fileName)
    {
        Dictionary<string, string> dictionary = new Dictionary<string, string>();
        foreach (string line in File.ReadAllLines(fileName))
        {
            if ((!string.IsNullOrEmpty(line)) &&
                (!line.StartsWith(";")) &&
                (!line.StartsWith("#")) &&
                (!line.StartsWith("'")) &&
                (line.Contains('=')))
            {
                int index = line.IndexOf('=');
                string key = line.Substring(0, index).Trim();
                string value = line.Substring(index + 1).Trim();
    
                if ((value.StartsWith("\"") && value.EndsWith("\"")) ||
                    (value.StartsWith("'") && value.EndsWith("'")))
                {
                    value = value.Substring(1, value.Length - 2);
                }
                dictionary.Add(key, value);
            }
        }
    
        return dictionary;
    }
    

    【讨论】:

      【解决方案6】:

      是的,据我所知,没有内置的类可以做到这一点。

      但这不应该真的是一个问题吗?只需将Stream.ReadToEnd() 的结果存储在一个字符串中,根据新行拆分,然后在= 字符上拆分每条记录,它看起来很容易解析。剩下的就是一堆键值对,您可以轻松地将它们扔进字典中。

      这是一个可能适合您的示例:

      public static Dictionary<string, string> GetProperties(string path)
      {
          string fileData = "";
          using (StreamReader sr = new StreamReader(path))
          {
              fileData = sr.ReadToEnd().Replace("\r", "");
          }
          Dictionary<string, string> Properties = new Dictionary<string, string>();
          string[] kvp;
          string[] records = fileData.Split("\n".ToCharArray());
          foreach (string record in records)
          {
              kvp = record.Split("=".ToCharArray());
              Properties.Add(kvp[0], kvp[1]);
          }
          return Properties;
      }
      

      这是一个如何使用它的示例:

      Dictionary<string,string> Properties = GetProperties("data.txt");
      Console.WriteLine("Hello: " + Properties["Hello"]);
      Console.ReadKey();
      

      【讨论】:

      • 如果你用 '\n' 替换 "\n" 你就不需要 .ToCharArray()
      • 可能您的代码需要增强,以处理以# 开头的注释行,以及其他空字符,如key = value,应删除多余的空格。
      【解决方案7】:

      真正的答案是否定的(至少不是本身)。您仍然可以编写自己的代码来执行此操作。

      【讨论】:

        【解决方案8】:

        C# 通常使用基于 xml 的配置文件,而不是像你说的 *.ini 样式的文件,所以没有内置的东西来处理这个问题。然而,谷歌返回一个number of promising results

        【讨论】:

          【解决方案9】:

          我不知道有任何内置方法可以做到这一点。但是,这似乎很容易做到,因为您唯一需要担心的分隔符是换行符和等号。

          编写一个返回 NameValueCollection 或给定文件内容的 IDictionary 的例程将非常容易。

          【讨论】:

            【解决方案10】:

            您还可以使用具有默认值和限制集的 C# 自动属性语法。这里的优点是您可以在属性“文件”(现在实际上是一个类)中拥有任何类型的数据类型。另一个优点是您可以使用 C# 属性语法来调用属性。但是,您只需要为每个属性写几行代码(属性声明中的一行,构造函数中的一行)即可完成这项工作。

            using System;
            namespace ReportTester {
               class TestProperties
               {
                    internal String ReportServerUrl { get; private set; }
                    internal TestProperties()
                    {
                        ReportServerUrl = "http://myhost/ReportServer/ReportExecution2005.asmx?wsdl";
                    }
               }
            }
            

            【讨论】:

              【解决方案11】:

              为此有几个 NuGet 包,但目前都处于预发布版本。

              [更新] 截至 2018 年 6 月,Capgemini.Cauldron.Core.JavaProperties 现在处于稳定版本(版本 2.1.0 和 3.0.20)。

              【讨论】:

                【解决方案12】:

                我知道这不是您要问的,但以防万一:

                当您想要加载一个实际 Java 属性文件时,您需要适应它的编码。 The Java docs 表示编码为 ISO 8859-1,其中包含一些您可能无法正确解释的转义序列。例如,查看this SO answer 以了解将 UTF-8 转换为 ISO 8859-1(反之亦然)的必要条件

                当我们需要这样做时,我们找到了一个开源 PropertyFile.cs 并进行了一些更改以支持转义序列。这个类是读/写场景的好类。您还需要支持 PropertyFileIterator.cs 类。

                即使您没有加载真正的 Java 属性,也要确保您的 prop 文件可以表达您需要保存的所有字符(至少 UTF-8)

                【讨论】:

                  【解决方案13】:

                  不,没有:但我创建了一个简单的类来帮助:

                  public class PropertiesUtility
                  {
                      private static Hashtable ht = new Hashtable();
                      public void loadProperties(string path)
                      {
                          string[] lines = System.IO.File.ReadAllLines(path);
                          bool readFlag = false;
                          foreach (string line in lines)
                          {
                              string text = Regex.Replace(line, @"\s+", "");
                              readFlag =  checkSyntax(text);
                              if (readFlag)
                              {
                                  string[] splitText = text.Split('=');
                                  ht.Add(splitText[0].ToLower(), splitText[1]);
                              }
                          }
                      }
                  
                      private bool checkSyntax(string line)
                      {
                          if (String.IsNullOrEmpty(line) || line[0].Equals('['))
                          {
                              return false;
                          }
                  
                          if (line.Contains("=") && !String.IsNullOrEmpty(line.Split('=')[0]) && !String.IsNullOrEmpty(line.Split('=')[1]))
                          {
                              return true;
                          }
                          else
                          {
                              throw new Exception("Can not Parse Properties file please verify the syntax");
                          }
                      }
                  
                      public string getProperty(string key)
                      {
                          if (ht.Contains(key))
                          {
                              return ht[key].ToString();
                          }
                          else
                          {
                              throw new Exception("Property:" + key + "Does not exist");
                          }
                  
                      }
                  }
                  

                  希望这会有所帮助。

                  【讨论】:

                    猜你喜欢
                    • 2011-01-11
                    • 1970-01-01
                    • 1970-01-01
                    • 1970-01-01
                    • 2011-04-05
                    • 1970-01-01
                    • 1970-01-01
                    • 2012-10-01
                    相关资源
                    最近更新 更多