【问题标题】:Read values from a non-delimited string into class object将非定界字符串中的值读入类对象
【发布时间】:2014-02-10 11:51:03
【问题描述】:

我有一个结构如下的字符串:

Student Name________AgeAddress_______________________Bithday___Lvl

例子:

Jonh Smith         016Some place in NY, USA               01/01/2014L01

如您所见,没有像 |, 这样的分隔符

此外,字段之间没有空格(如果选中,则年龄/地址和生日/级别之间没有空格。

每个字段的大小是静态的,因此如果数据的长度较小,则它将包含空格。

我有一个需要填写这些信息的课程:

public class StudentData
{
    public char[] _name = new char[20];
    public string name;
    public char[] _age = new char[3];
    public string age;
    public char[] _address = new char[30];
    public string address;
    public char[] _bday = new char[10];
    public string bday;
    public char[] _level = new char[3];
    public string level;
}

有没有办法自动动态地做到这一点?

我的意思是我真的不想这样写代码:

myClass.name = stringLine.substring(0,19);
myClass.age = stringLine.substring(20,22);

那是因为我有比本示例中添加的字段更多的字段以及更多带有其他不同数据的字符串行。

更新:“Smith”和“016”之间应该有很多空格,但我不知道如何编辑。

Update2:如果我使用 StringReader.Read(),我可以避免使用子字符串和索引,但它仍然不是那么动态,因为我需要为每个字段重复这 3 行。

StringReader reader = new StringReader(stringLine);
reader.Read(myClass._name, 0 myClass._name.Length);
myClass.name = new string(myClass._name);

【问题讨论】:

  • 抱歉,如果您要解析没有分隔符的固定长度文本文件,您别无选择,只能映射每个字段的位置和长度。您应该尝试在位置和长度上使用常量而不是幻数,这样可以减少出错的机会。
  • 为什么数据输入这么糟糕?有没有比非分隔文本文件更好的方法?
  • 数据输入来自另一个遗留系统。我无法修改或替换它。 :(

标签: c# .net string substring stringreader


【解决方案1】:

鉴于您的要求,我想出了一个有趣的解决方案。尽管如此,它可能比使用上述String.SubString() 方法更复杂和更长。

但是,此解决方案可转移到其他类型和其他字符串。我使用了AttributesPropertiesReflection 的概念来按固定长度解析字符串并设置类属性。

请注意,我确实更改了您的 StudentData 类以遵循更传统的编码风格。按照 MSDN 上的这个方便的指南:http://msdn.microsoft.com/en-us/library/xzf533w0(v=vs.71).aspx

这是新的StudentData 类。请注意,它使用属性而不是字段。 (这里不讨论)。

public class StudentData
{
    string name;
    string age;
    string address;
    string bday;
    string level;

    [FixedLengthDelimeter(0, 20)]
    public string Name { get { return this.name; } set { this.name = value; } }

    [FixedLengthDelimeter(1, 3)]
    public string Age { get { return this.age; } set { this.age = value; } }

    [FixedLengthDelimeter(2, 30)]
    public string Address { get { return this.address; } set { this.address = value; } }

    [FixedLengthDelimeter(3, 10)]
    public string BDay { get { return this.bday; } set { this.bday = value; } }

    [FixedLengthDelimeter(4, 3)]
    public string Level { get { return this.level; } set { this.level = value; } }
}

注意每个属性都有一个名为FixedLengthDelimeter 的属性,它接受两个参数。

  1. OrderNumber
  2. FixedLength

OrderNumber 参数表示字符串中的顺序(不是位置),而是我们从字符串中处理的顺序。第二个参数表示解析字符串时字符串的Length。这是完整的属性类。

[AttributeUsage(AttributeTargets.Property, AllowMultiple = false)]
public sealed class FixedLengthDelimeterAttribute : Attribute
{
    public FixedLengthDelimeterAttribute(int orderNumber, int fixedLength)
    {
        this.fixedLength = fixedLength;
        this.orderNumber = orderNumber;
    }

    readonly int fixedLength;

    readonly int orderNumber;

    public int FixedLength { get { return this.fixedLength; } }

    public int OrderNumber { get { return this.orderNumber; } }
}

现在属性已经很简单了。接受我们之前在构造函数中讨论过的两个参数。

最后还有一种方法可以将字符串解析成对象类型如。

public static class FixedLengthFormatter
{
    public static T ParseString<T>(string inputString)
    {
        Type tType = typeof(T);
        var properties = tType.GetProperties(BindingFlags.Instance | BindingFlags.Public); //;.Where(x => x.GetCustomAttributes(typeof(FixedLengthDelimeterAttribute), false).Count() > 0);

        T newT = (T)Activator.CreateInstance(tType);

        Dictionary<PropertyInfo, FixedLengthDelimeterAttribute> dictionary = new Dictionary<PropertyInfo, FixedLengthDelimeterAttribute>();
        foreach (var property in properties)
        {
            var atts = property.GetCustomAttributes(typeof(FixedLengthDelimeterAttribute), false);
            if (atts.Length == 0)
                continue;
            dictionary[property] = atts[0] as FixedLengthDelimeterAttribute;
        }
        foreach (var kvp in dictionary.OrderBy(x => x.Value.OrderNumber))
        {
            int length = kvp.Value.FixedLength;
            if (inputString.Length < length)
                throw new Exception("error on attribute order number:" + kvp.Value.OrderNumber + " the string is too short.");
            string piece = inputString.Substring(0, length);
            inputString = inputString.Substring(length);
            kvp.Key.SetValue(newT, piece.Trim(), null);
        }
        return newT;
    }
}

上面的方法是解析字符串的。这是一个非常基本的实用程序,可以读取所有应用了FixedLengthDelimeter 属性的属性Dictionary。然后枚举该字典(按OrderNumber 排序),然后在输入字符串上调用SubString() 方法两次。

第一个子字符串用于解析下一个 Token,而第二个子字符串重置 inputString 以开始处理下一个令牌。

最后,当它解析字符串时,它会将解析后的字符串应用于提供给方法的类Type 的属性。

现在可以像这样简单地使用它:

string data1 = "Jonh Smith          016Some place in NY, USA         01/01/2014L01";
StudentData student = FixedLengthFormatter.ParseString<StudentData>(data1);

这是做什么的:

  • 针对固定长度格式的属性属性解析字符串。

这不能做什么:

  • 它确实将已解析的字符串转换为另一种类型。因此,所有属性都必须是字符串。 (这可以通过添加一些类型转换逻辑来轻松调整)。
  • 它没有经过很好的测试。这仅针对少数样本进行了测试。
  • 这绝不是唯一或最好的解决方案。

【讨论】:

    【解决方案2】:

    您可以使用FileHelpers library (NuGet)。

    只需使用属性定义输入文件的结构:

    [FixedLengthRecord]
    public class StudentData
    {
        [FieldFixedLength(20)]
        [FieldTrim(TrimMode.Right)] 
        public string name;
        [FieldFixedLength(3)]
        public string age;
        [FieldFixedLength(30)]
        [FieldTrim(TrimMode.Right)] 
        public string address;
        [FieldFixedLength(10)]
        public string bday;
        [FieldFixedLength(3)]
        public string level;
    }
    

    然后简单地使用FileHelperEngine&lt;T&gt;读取文件:

    var engine = new FileHelperEngine<StudentData>();
    var students = engine.ReadFile(filename);
    

    【讨论】:

      猜你喜欢
      • 2021-12-15
      • 2012-01-15
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      相关资源
      最近更新 更多