C# 分析文本文件 - 按索引访问类字段

本文关键字:索引 访问 字段 文本 文件 | 更新日期: 2023-09-27 18:37:00

我想解析一个包含几十个条目的文本文件。现在,我有一个愚蠢的解决方案,可以逐行读取并与硬编码字符串进行比较:

while ((line = reader.ReadLine()) != null) //returns null if end of stream
{
    cmpStr = "MODE";
    try
    {
        if (line.Equals(cmpStr))                        
            GlobalData.mode = Convert.ToInt32(line.Remove(0, cmpStr.Length));
    }
    catch { }
    cmpStr = "TIME_YEAR";
    try
    {
        if (line.Equals(cmpStr))                        
            GlobalData.time_year = Convert.ToInt32(line.Remove(0, cmpStr.Length));
    }
    catch { }

    // ... repeat to parse the remaining lines
}

GlobalData 是一个静态类,如下所示:

public static class GlobalData
{
    public static int mode;                 
    public static int time_year;
    public static int time_month;
    public static int time_day;
    public static int time_hour;
    public static int time_minute;
    // other entries omitted
    public static string[] GlobalKeywords = new  string[37] 
    {
        "MODE", 
        "TIME_YEAR",
        "TIME_MONTH",
        "TIME_DAY",
        "TIME_HOUR",
        "TIME_MINUTE",
        // other entries omitted      
    };
}

如果可以按索引访问我的静态字段,我会这样做:

int i = 0;
while ((line = reader.ReadLine()) != null) 
{
    cmpStr = GlobalData.GlobalKeywords[i];    // when i == 0: cmpStr = "MODE"
    if (line.Equals(cmpStr))
        GlobalData[i] = Convert.ToInt32(line.Remove(0, cmpStr.Length));
        // GlobalData[0] would be GlobalData.mode, and so on (but doesn't work)
    i++;
}
catch { }

因此,即使我可以设置一个循环来与关键字的字符串数组进行比较,如何分配静态类的某个字段?

BR克里斯

C# 分析文本文件 - 按索引访问类字段

我不确定您的业务限制是什么,因此很难提出一个万无一失的解决方案,尽管有几点:

cmpStr = "MODE";
try
{
   if (line.Equals(cmpStr))                        
      GlobalData.mode = Convert.ToInt32(line.Remove(0, cmpStr.Length));
}

这不会像您(可能预期的那样)工作 - 如果line.Equals("MODE")line.Remove(0, "MODE".Length)是一个空字符串。您可能想要的是 line.StartsWith(cmpStr)line.Contains(cmpStr) .

全局数据是一个静态类

对于您正在做的事情来说,这似乎不是一个好方法。您可能希望阅读静态类以及何时使用它们(MSDN 是一个很好的起点,尽管它显然不能涵盖所有内容:http://msdn.microsoft.com/en-us/library/79b3xss3%28v=vs.80%29.aspx)。

除此之外,您可能可以简单地将所有 int 字段替换为字典(尽管请重新考虑上述静态方法):

public static Dictionary<String, int> Items = new Dictionary<String, int>();

然后,您的解析代码可能如下所示:

while ((line = reader.ReadLine()) != null) //returns null if end of stream
{
   var matchingString
      = GlobalData.GlobalKeywords.FirstOrDefault(s => line.StartsWith(s));
   if (matchingString != null)
      GlobalData[matchingString]
           = Convert.ToInt32(line.Remove(0, matchingString.Length));
}

然后,您将能够使用以下方法获取该数据: GlobalData.Items["MODE"] .

最后一点:您可以考虑在全局数据类中引入常量值,例如:

public const String MODE = "MODE";

然后您可以使用GlobalData.Items[GlobalData.MODE]并避免拼写错误:编写GlobalData.Items[GlobalData.MODe]会导致编译错误。

替换这个:

public static int mode;                 
public static int time_year;
public static int time_month;
public static int time_day;
public static int time_hour;
public static int time_minute;

有了这个:

public static Dictionary<string, int> my_values = new Dictionary<string, int>();

然后替换:

GlobalData[i] = Convert.ToInt32(line.Remove(0, cmpStr.Length));

跟:

GlobalData.my_values[cmpStr] = Convert.ToInt32(line.Remove(0, cmpStr.Length));

这应该做你想做的事,即使我不明白你期望Convert.ToInt32如何工作。您调用Remove的方式将创建一个空字符串(可能会转换为 0,我不记得了),即使没有,该行也不包含数字,因为您成功地将其与"MODE"这样的字符串进行了比较。

解决问题的一种优雅方法是为每个可接受的字符串准备不同的操作。您使用Dictionary(Of String, <Action>)其中 Action 是一种常见的委托类型,它在输入中接收字符串,并知道如何根据行首的关键字对其进行处理。

// The common signature for every methods stored in the value part of the dictionary
public delegate void ParseLine(string line);
// Global dictionary where you store the strings as keyword
// and the ParseLine as the delegate to execute
Dictionary<String, ParseLine> m_Actions = new Dictionary<String, ParseLine>() ;
void Main()
{
    // Initialize the dictionary with the delegate corresponding to the strings keys
    m_Actions.Add("MODE", new ParseLine(Task1));
    m_Actions.Add("TIME_YEAR", new ParseLine(Task2));
    m_Actions.Add("TIME_MONTH", new ParseLine(Task3));
    m_Actions.Add("TIME_DAY", new ParseLine(Task4));
    .....
    while ((line = reader.ReadLine()) != null) 
    {
        // Search the space that divide the keyword from the value on the same line
        string command = line.Substring(0, line.IndexOf(' ')).Trim();
        // a bit of error checking here is required
        if(m_Actions.ContainsKey(command))
            m_Actions[command](line);
    }
}
void Task1(string line)
{
    // this will handle the MODE line
    GlobalData.Mode = Convert.ToInt32(line.Substring(line.IndexOf(' ')+1).Trim());
}
void Task2(string line)
{
     GlobalData.time_year = Convert.ToInt32(line.Substring(line.IndexOf(' ')+1).Trim());
}
void Task3(string line)
{
    .....
}
void Task4(string line)
{
    .....
}

一种简单(但不是很干净)的方法是将索引器添加到全局数据类,并根据索引决定要设置的字段。但是每次添加字段时都必须扩展索引器(基本上是将 if/switch 从 while 循环移动到索引器中)。
如果可以将关键字与字段名称匹配,也可以使用反射。这性能不是很好,但只要您可以将关键字映射到新字段名称,就不需要扩展。
另一种方法是创建字典>。在此字典中,您注册关键字,例如(伪代码):类级别变量:

private keywordsDict = new Dictionary<string, Action<int>>();

在构造函数中:

keywordsDict.Add("MODE", delegate(value) GlobalData.mode = value);

在 while 循环中:

var action = keywordsDict[line];
action(value);

在后面的方法中,如果您有新的关键字/字段,则只需要扩展字典,而不需要扩展算法本身。

也许我可以告诉你如何在 C# 中实现它 (GlobalData[i]) 认为这不是你要找的答案。

    class GlobalData
    {
        private string[] array = new string[10];
        public GlobalData()
        {
           //now initialize array
           array[0] = "SomeThingA";
           array[1] = "SomeThingB";//continue initialization.
        }
        public string this[int index]
        {
            get {return array[index];}
        }
    }

现在客户端可以使用 GlobalData,例如 ,

    GlobalData gd = new GlobalData();
    gd[1] = "SomeOtherThing" ; //set the value.
    string value = gd[1];//get the value

但这不能通过使类静态来完成,因为您看到它适用于"this"