C# 字符串解析技术

C# 字符串解析技术,c#,.net,string,parsing,C#,.net,String,Parsing,我试图找到一种将消息字符串解析为对象的好方法。 该字符串为固定长度,如下所述 协议=int(2) 消息类型=字符串(1) 测量=字符串(4) 等 执行一个简单的String.Split将起作用,但我认为当您开始接近字符串末尾时,可能会有点麻烦。e、 g: var field1 = s.SubString(0,2); var field2 = s.SubString(2,4); .... var field99 = s.SubString(88,4); // difficult magic

我试图找到一种将消息字符串解析为对象的好方法。 该字符串为固定长度,如下所述

  • 协议=int(2)
  • 消息类型=字符串(1)
  • 测量=字符串(4)
执行一个简单的
String.Split
将起作用,但我认为当您开始接近字符串末尾时,可能会有点麻烦。e、 g:

var field1 = s.SubString(0,2);
var field2 = s.SubString(2,4);
....
var field99 = s.SubString(88,4); // difficult magic numbers
我考虑过使用正则表达式,并认为这可能更令人困惑

我试图想出一个优雅的解决方案,在那里我可以创建一个解析器,它被传递一个“config”,详细说明如何解析字符串

类似于

 MyConfig config = new MyConfig()
 config.Add("Protocol",    Length=2, typeof(int));
 config.Add("MessageType", Length=1, typeof(char));


 Parser p = new Parser(config);
 var parserResult = p.Parse(message);
var types = new[] {typeof(int), typeof(string), typeof(string), typeof(DateTime), /*etc..*/ };
var data = parser.ReadFields();
var firstVal = Convert.ChangeType(data[0], types[0]); 
var secondVal = Convert.ChangeType(data[1], types[1]); 
// etc..
// or in a loop: 
for (var i = 0; i<data.Length;++i){
  var valAsString = data[i];
  var thisType = types[i];
  var value = Convert.ChangeType(valAsString , thisType);
  // do something with value
}

…但我现在正在兜圈子,什么也没得到。任何指点都会有很大的帮助

一个想法是:
GetNextCharacters(int-position,int-length,out-newPosition)
它为您提供下一个
length
字符、您想要的字符串以及下一次调用的新位置


这样,您只需在每次调用中更改
长度

您可以为每个字符串部分定义一个具有属性的类,以及一个指定开始/结束位置的自定义属性(例如FieldItem),在构造函数中,您可以传递整个字符串,然后根据属性编写一些内部逻辑(使用反射)根据子字符串(开始、结束)的使用情况以及从自定义属性获取的索引,从提供的字符串(可能是ReadString方法,或其他任何方法)加载每个属性。
我认为,这种方式比定义特殊的正则表达式更简洁,而且您只需编辑属性属性就可以轻松更改字段定义。

如果使用正确的方式,我认为正则表达式不会令人困惑。您可以使用命名的捕获组,并且可以非常简洁地定义它(前三个字段就是一个示例,您可以根据需要进行扩展):


因此,一个简单的消息结构:

class Message
{
    public DateTime DateTime { get; set; }
    public int Protocol { get; set; }
    public string Measurement { get; set; }
    public string Type { get; set; }
    //....
}
与知道如何反序列化的类组合:

class MessageSerializer
{
    public Message Deserialize(string str)
    {
        Message message = new Message();
        int index = 0;
        message.Protocol = DeserializeProperty(str, ref index, 2, Convert.ToInt32);
        message.Type = DeserializeProperty(str, ref index, 1, Convert.ToString);
        message.Measurement = DeserializeProperty(str, ref index, 4, Convert.ToString);
        message.DateTime = DeserializeProperty<DateTime>(str, ref index, 16, (s) =>
        {
            // Parse date time from 2013120310:28:55 format
            return DateTime.ParseExact(s, "yyyyMMddhh:mm:ss", CultureInfo.CurrentCulture);
        });
        //...
        return message;
    }

    static T DeserializeProperty<T>(string str, ref int index, int count, 
        Func<string, T> converter)
    {
        T property = converter(str.Substring(index, count));
        index += count;
        return property;
    }
}
类消息序列化程序
{
公共消息反序列化(字符串str)
{
消息消息=新消息();
int指数=0;
message.Protocol=反序列化属性(str,ref index,2,Convert.ToInt32);
message.Type=反序列化属性(str,ref-index,1,Convert.ToString);
message.Measurement=反序列化属性(str,ref-index,4,Convert.ToString);
message.DateTime=反序列化属性(str,ref index,16,(s)=>
{
//从2013120310:28:55格式解析日期时间
return DateTime.ParseExact(s,“yyyyMMddhh:mm:ss”,CultureInfo.CurrentCulture);
});
//...
返回消息;
}
静态T反序列化属性(字符串str、ref int index、int count、,
Func转换器)
{
T属性=转换器(str.Substring(index,count));
指数+=计数;
归还财产;
}
}

如果输入字符串中的属性是固定宽度的,则Regex在实现和性能方面都是开销。创建泛型解析器的想法是好的,但是如果要实现多个解析器,这是有意义的。因此,如果只有一个特定的实现,就没有理由进行抽象

我只会选择StringReader

using (var reader = new StringReader(input)) {
}
…然后创建一些助手扩展方法,如下所示:

// just a sample code, to get the idea

public static string ReadString(this TextReader reader, int count)
{
    var buffer = new char[count];
    reader.Read(buffer, 0, count);
    return string.Join(string.Empty, buffer);
}

public static int ReadNumeric(this TextReader reader, int count)
{
    var str = reader.ReadString(count);
    int result;
    if (int.TryParse(str, out result))
    {
        return result;
    }
    // handle error
}

// ...
最后的用法如下:

using (var reader = new StringReader(input)) {
    var protocol = reader.ReadNumeric(2);
    var messageType = reader.ReadString(1);
    var measurement = reader.ReadString(4);
    // ...
}
[StructLayout(LayoutKind.Sequential, CharSet = CharSet.Unicode, Pack = 1)]
public struct TData
{
    [MarshalAs(UnmanagedType.ByValTStr, SizeConst = 2)]
    public protocol string;
    [MarshalAs(UnmanagedType.ByValTStr, SizeConst =1)]
    public messageType string;
    [MarshalAs(UnmanagedType.ByValTStr, SizeConst = 4)]
    public measurement 
...
    public int getProtocol(){return Convert.ToInt32(protocol);}
...
}

public string get(){
   var strSource="03EMSTR...";
    IntPtr pbuf = Marshal.StringToBSTR(buf);
    TData data= (TData)Marshal.PtrToStructure(pbuf,typeof(TData))
}

您可能能够利用该类。它可以接受用于解析的字段长度列表

using (var parser = new TextFieldParser(new StringReader(s))){
     parser.TextFieldType = FieldType.FixedWidth;
     parser.SetFieldWidths(2,1,4 /*etc*/);
     while (!parser.EndOfData)
     {
         var data = parser.ReadFields(); //string[]
     }
}
但是,这只会将数据拆分为字符串数组。如果您的所有类型都是可转换的,那么您可能会执行以下操作

 MyConfig config = new MyConfig()
 config.Add("Protocol",    Length=2, typeof(int));
 config.Add("MessageType", Length=1, typeof(char));


 Parser p = new Parser(config);
 var parserResult = p.Parse(message);
var types = new[] {typeof(int), typeof(string), typeof(string), typeof(DateTime), /*etc..*/ };
var data = parser.ReadFields();
var firstVal = Convert.ChangeType(data[0], types[0]); 
var secondVal = Convert.ChangeType(data[1], types[1]); 
// etc..
// or in a loop: 
for (var i = 0; i<data.Length;++i){
  var valAsString = data[i];
  var thisType = types[i];
  var value = Convert.ChangeType(valAsString , thisType);
  // do something with value
}

在这种情况下,您可能能够利用
动态
关键字,尽管我对它的经验很少,我不确定它是否有什么不同:

dynamic firstVal=Convert.ChangeType(数据[0],类型[0])

请注意,
dynamic
关键字以及
TextFieldParser
类都会带来性能损失,至少对于较大的字符串/文件而言,
TextFieldParser
类不是性能最好的(请参阅其他SO文章)。当然,如果您所做的只是解析一个字符串,那么使用
TextFieldParser
对您的案例来说也可能是过分的

如果您有一个表示此数据的dto/poco类,则始终可以将
ReadFields()
返回的字符串数组传递到dto上的构造函数中,该构造函数可以为您填充数据。。。即:

class Message {
    public DateTime DateTime { get; set; }
    public int Protocol { get; set; }
    public string Type { get; set; }
    public string Measurement {get;set;}
    public Message(string[] data) {
       Protocol = int.Parse(data[0]);
       Type = data[1];
       Measurement = data[2];
       DateTime = DateTime.Parse(data[3]);
    }
}

正如您所说,如果字符串是静态的,则可以使用marshal类,如下所示:

using (var reader = new StringReader(input)) {
    var protocol = reader.ReadNumeric(2);
    var messageType = reader.ReadString(1);
    var measurement = reader.ReadString(4);
    // ...
}
[StructLayout(LayoutKind.Sequential, CharSet = CharSet.Unicode, Pack = 1)]
public struct TData
{
    [MarshalAs(UnmanagedType.ByValTStr, SizeConst = 2)]
    public protocol string;
    [MarshalAs(UnmanagedType.ByValTStr, SizeConst =1)]
    public messageType string;
    [MarshalAs(UnmanagedType.ByValTStr, SizeConst = 4)]
    public measurement 
...
    public int getProtocol(){return Convert.ToInt32(protocol);}
...
}

public string get(){
   var strSource="03EMSTR...";
    IntPtr pbuf = Marshal.StringToBSTR(buf);
    TData data= (TData)Marshal.PtrToStructure(pbuf,typeof(TData))
}

我认为这种方法可以使您的代码非常纯净和易于维护。

分别对每一个代码使用regex。如果我是honestRegex是提取和解析字符串的合适工具,那么它将非常简单,不会将
Regex
与捕获组混淆。Regex组映射到MyConfig,但类型除外,为什么要构建自己的?然而,我不知道如何在这里利用类型。你有大开关吗?的确,正则表达式很难阅读,但您可以通过许多注释仔细构建它。把它分成几部分,就不会让人感到困惑了。我最近回答了一个类似的问题。我创建了一个函数,它使用如下字符串[]tabLocations={1,20,30,50,70}这样的输入数组解析固定宽度的字符串;它不是
parseRegex.Groups
,而是
match.Groups
。您缺少一个
元组之后