[英]String Parsing in C#
什么是以#的形式解析C#字符串的最有效方法
"(params (abc 1.3)(sdc 2.0)(www 3.05)....)"
到表單中的結構
struct Params
{
double abc,sdc,www....;
}
謝謝
編輯結構總是有相同的參數(相同的名稱,只有雙打,在編譯時已知)..但訂單不被授予..一次只有一個結構..
using System;
namespace ConsoleApplication1
{
class Program
{
struct Params
{
public double abc, sdc;
};
static void Main(string[] args)
{
string s = "(params (abc 1.3)(sdc 2.0))";
Params p = new Params();
object pbox = (object)p; // structs must be boxed for SetValue() to work
string[] arr = s.Substring(8).Replace(")", "").Split(new char[] { ' ', '(', }, StringSplitOptions.RemoveEmptyEntries);
for (int i = 0; i < arr.Length; i+=2)
typeof(Params).GetField(arr[i]).SetValue(pbox, double.Parse(arr[i + 1]));
p = (Params)pbox;
Console.WriteLine("p.abc={0} p.sdc={1}", p.abc, p.sdc);
}
}
}
注意:如果您使用類而不是結構,則不需要裝箱/拆箱。
你需要支持多個結構嗎? 換句話說,這是否需要是動態的; 或者您是否在編譯時知道結構定義?
用正則表達式解析字符串將是顯而易見的選擇。
這是一個正則表達式,將解析您的字符串格式:
private static readonly Regex regParser = new Regex(@"^\(params\s(\((?<name>[a-zA-Z]+)\s(?<value>[\d\.]+)\))+\)$", RegexOptions.Compiled);
在字符串上運行該正則表達式將為您提供兩個名為“name”和“value”的組。 每個組的Captures
屬性將包含名稱和值。
如果在編譯時結構類型未知,那么您將需要使用反射來填充字段。
如果您想在運行時生成結構定義,則需要使用Reflection來發出類型; 或者您需要生成源代碼。
哪個部分有問題?
根據你的完整語法,你有幾個選擇:如果它是一個非常簡單的語法,你不必測試它中的錯誤,你可以簡單地使用下面的(這將是快速的)
var input = "(params (abc 1.3)(sdc 2.0)(www 3.05)....)";
var tokens = input.Split('(');
var typeName = tokens[0];
//you'll need more than the type name (assembly/namespace) so I'll leave that to you
Type t = getStructFromType(typeName);
var obj = TypeDescriptor.CreateInstance(null, t, null, null);
for(var i = 1;i<tokens.Length;i++)
{
var innerTokens = tokens[i].Trim(' ', ')').Split(' ');
var fieldName = innerTokens[0];
var value = Convert.ToDouble(innerTokens[1]);
var field = t.GetField(fieldName);
field.SetValue(obj, value);
}
然而,這種簡單的方法需要一個符合要求的字符串,否則它會行為不端。
如果語法有點復雜,例如嵌套(),那么這種簡單的方法將無法工作。
你可以嘗試使用regEx,但仍然需要一個相當簡單的語法,所以如果你最終有一個復雜的語法,你最好的選擇是一個真正的解析器。 反諷很容易使用,因為你可以用簡單的c#寫出來(雖然BNF的一些知識是一個優點)。
正則表達式可以為您完成這項工作:
public Dictionary<string, double> ParseString(string input){
var dict = new Dictionary<string, double>();
try
{
var re = new Regex(@"(?:\(params\s)?(?:\((?<n>[^\s]+)\s(?<v>[^\)]+)\))");
foreach (Match m in re.Matches(input))
dict.Add(m.Groups["n"].Value, double.Parse(m.Groups["v"].Value));
}
catch
{
throw new Exception("Invalid format!");
}
return dict;
}
使用它像:
string str = "(params (abc 1.3)(sdc 2.0)(www 3.05))";
var parsed = ParseString(str);
// parsed["abc"] would now return 1.3
這可能比為每個可能的輸入字符串創建許多不同的結構,並使用反射來填充它們更合適。 我不認為這是值得的。
此外,我假設輸入字符串始終與您發布的格式完全相同。
您可能會考慮執行足夠的字符串操作以使輸入看起來像標准命令行參數,然后使用現成的命令行參數解析器(如NDesk.Options)來填充Params對象。 你放棄了一些效率,但你在可維護性方面做得很好。
public Params Parse(string input)
{
var @params = new Params();
var argv = ConvertToArgv(input);
new NDesk.Options.OptionSet
{
{"abc=", v => Double.TryParse(v, out @params.abc)},
{"sdc=", v => Double.TryParse(v, out @params.sdc)},
{"www=", v => Double.TryParse(v, out @params.www)}
}
.Parse(argv);
return @params;
}
private string[] ConvertToArgv(string input)
{
return input
.Replace('(', '-')
.Split(new[] {')', ' '});
}
這是一個開箱即用的方法:convert()到{}和[SPACE]到“:”,然后使用System.Web.Script.Serialization.JavaScriptSerializer.Deserialize
string s = "(params (abc 1.3)(sdc 2.0))"
.Replace(" ", ":")
.Replace("(", "{")
.Replace(")","}");
return new System.Web.Script.Serialization.JavaScriptSerializer().Deserialize(s);
我只會做一個基本的遞歸下降解析器。 它可能比你想要的更通用,但沒有別的東西會快得多。
聲明:本站的技術帖子網頁,遵循CC BY-SA 4.0協議,如果您需要轉載,請注明本站網址或者原文地址。任何問題請咨詢:yoyou2525@163.com.