Parsing pipe delimited lines I am parsing the following line of code via a specific format.
Line is:


S|111111|87654321|Bar UK|BCreace UK|GBP|24/08/2010|



The Format is:


Index    Field Length    
S0 -     1               
S1 -     6
S2 -     34
....
...
S6 -     10



I am validating using many if statements. Could anyone suggest a better approach?

private static StatementLineResult Validate(string delimitedRecord)
{
    if (delimitedRecord == null)
        throw new ArgumentNullException("delimitedRecord");

    var items = delimitedRecord.Split('|');
    if(items.Length != 19)
        throw new Exception("Improper line format");

    var errorMessage = new Dictionary&lt;string, string&gt;();

    var bankIdentifierCodes = new List&lt;string&gt; {"ABCDGB2L", "EFGHGB2L"};

    if (items[0].Length != 1 || items[0] != "S")
        errorMessage.Add("Record Identifier","Invalid Record Identifier");

    var sortCode = 0;
    if (!int.TryParse(items[1], out sortCode) || items[1].Length &gt; 6)
        errorMessage.Add("Sort Code", "Invalid sort code");

    if (string.IsNullOrEmpty(items[2]) || items[1].Length &gt; 34)
        errorMessage.Add("Account Number", "Invalid account number");

    if (string.IsNullOrEmpty(items[3]) || items[1].Length &gt; 35)
        errorMessage.Add("Account Alias", "Invalid account alias");

    if (string.IsNullOrEmpty(items[4]) || items[1].Length &gt; 35)
        errorMessage.Add("Account Name", "Invalid account name");

    if(string.IsNullOrEmpty(items[5]) || items[5].Length != 3)
        errorMessage.Add("Account Currency", "Invalid account currency");

    if (!string.IsNullOrEmpty(items[6]) &amp;&amp; items[6].Length &gt; 20)
        errorMessage.Add("Account Type", "Invalid account type");

    if(string.IsNullOrEmpty(items[7]) || items[7].Length != 8 || !bankIdentifierCodes.Contains(items[7],StringComparer.OrdinalIgnoreCase))
        errorMessage.Add("Bank Identifier Code", "Invalid bank identifier code");

    if (!string.IsNullOrEmpty(items[8]) &amp;&amp; items[8].Length &gt; 35)
        errorMessage.Add("Bank Name", "Invalid bank name");

    if (!string.IsNullOrEmpty(items[9]) &amp;&amp; items[9].Length &gt; 27)
        errorMessage.Add("Bank Branch Name", "Invalid bank branch name");

    DateTime transactionDate;
    if (!DateTime.TryParse(items[10], out transactionDate))
        errorMessage.Add("Transaction Date", "Invalid date");

    if (!string.IsNullOrEmpty(items[11]) &amp;&amp; items[11].Length &gt; 25)
        errorMessage.Add("Narrative Line 1", "Invalid narrative line 1");
    if (!string.IsNullOrEmpty(items[12]) &amp;&amp; items[12].Length &gt; 25)
        errorMessage.Add("Narrative Line 2", "Invalid narrative line 2");
    if (!string.IsNullOrEmpty(items[13]) &amp;&amp; items[13].Length &gt; 25)
        errorMessage.Add("Narrative Line 13", "Invalid narrative line 13");
    if (!string.IsNullOrEmpty(items[14]) &amp;&amp; items[14].Length &gt; 25)
        errorMessage.Add("Narrative Line 14", "Invalid narrative line 14");
    if (!string.IsNullOrEmpty(items[15]) &amp;&amp; items[15].Length &gt; 25)
        errorMessage.Add("Narrative Line 15", "Invalid narrative line 15");

    if(_transactionTypes.First(item=&gt;item.TransactionType==items[16]) == null)
        errorMessage.Add("Transaction Type", "Invalid transaction type");

    decimal debitValue;
    if(items[17] != "" &amp;&amp; !Decimal.TryParse(items[17], out debitValue))
        errorMessage.Add("Debit Value", "Invalid debit amount");

    decimal creditValue;
    if (items[18] != "" &amp;&amp; !Decimal.TryParse(items[18], out creditValue))
        errorMessage.Add("Credit Value", "Invalid credit amount");

    return new StatementLineResult()
    {
        ErrorMessages = errorMessage,
        Data = BankLineData(delimitedRecord),
        IsValid = errorMessage.Count==0
    };
}
In the object initializer syntax you're using here:


return new StatementLineResult()
{
    ErrorMessages = errorMessage,
    Data = BankLineData(delimitedRecord),
    IsValid = errorMessage.Count==0
};



The parentheses are redundant; object initializer calls the parameterless constructor by default, so you don't need to call it explicitly.

However there's a design smell here; using this syntax tells me your StatementLineResult class looks something like this:

public class StatementLineResult
{
    public Dictionary&lt;string,string&gt; ErrorMessages { get; set; }
    public SomeType Data { get; set; }
    public bool IsValid { get; set; }
}


Notice how everything could be set from the outside, even with invalid state? What if IsValid is set to false when there are items in ErrorMessages? Nonsensical isn't it!

The type is crying to be immutable. Make the properties get-only, and assign their values from the constructor. If the logic for IsValid is "has no error messages", then it's probably redundant. In any case, it should be get-only and return ErrorMessages.Count == 0, not an arbitrary bool value.

Don't expose a Dictionary (or a List, for that matter) like that. Expose an IEnumerable&lt;T&gt; - your client code needs only to iterate the values, and doesn't care about the Key and Value concepts. What's the type of T then?

How about a tiny little class?

public class FieldValidationError
{
    public FieldValidationError(string fieldName, string message)
    {
        _fieldName = fieldName;
        _message = message;
    }

    private readonly string _fieldName;
    public string FieldName { get { return _fieldName; } }

    private readonly string _message;
    public string Message { get { return _message; } }
}


Then instead of exposing a Dictionary&lt;string,string&gt;, you can expose a much more expressive IEnumerable&lt;FieldValidationError&gt;, and not worry about the possibility that client code could tamper with the results.



Random thoughts:


Don't compare strings to null or "" - use string.IsNullOrEmpty instead... which you are using.. just not consistently.
Don't throw System.Exception. Use the most appropriate existing exception type, or make one if none fits the bill appropriately. But don't throw the base exception type; it forces client code to catch (Exception), which makes it catch all kinds of nasties that you would probably rather see bubble up the call stack and crash your app. In this case, throwing an ArgumentException would probably be fine; deriving an InvalidFieldCountException from it could work too.


Spot the issue:


if (!int.TryParse(items[1], out sortCode) || items[1].Length &gt; 6)
    errorMessage.Add("Sort Code", "Invalid sort code");

if (string.IsNullOrEmpty(items[2]) || items[1].Length &gt; 34)
    errorMessage.Add("Account Number", "Invalid account number");

if (string.IsNullOrEmpty(items[3]) || items[1].Length &gt; 35)
    errorMessage.Add("Account Alias", "Invalid account alias");

if (string.IsNullOrEmpty(items[4]) || items[1].Length &gt; 35)
    errorMessage.Add("Account Name", "Invalid account name");

if(string.IsNullOrEmpty(items[5]) || items[5].Length != 3)
    errorMessage.Add("Account Currency", "Invalid account currency");



Looks like a copy-pasta bug. I think the part after the || operator is actually intended to use the same index as the part before.



This is clearly a bug:


if(_transactionTypes.First(item=&gt;item.TransactionType==items[16]) == null)



First does not return null when there's no item: it throws an InvalidOperationException instead. You probably meant to use FirstOrDefault:

if(_transactionTypes.FirstOrDefault(item =&gt; item.TransactionType == items[16]) == null)


...but that's sub-optimal, too; if that's meant to say "if there's no _transactionTypes item for items[16]", then it can be expressed more concisely like this:

if(!_transactionTypes.Any(item =&gt; item.TransactionType == items[16]))