tags:

views:

494

answers:

6

Hello,

In my C# app I'm trying to feed into ReadLine() a simple text document with 7 digit strings separated line by line. What I'm attempting to do is grab the next 7 digit string each time the function is called. Here's what I have so far:

string invoiceNumberFunc()
    {
        string path = @"C:\Users\sam\Documents\GCProg\testReadFile.txt";
        try
        {
            using (StreamReader sr = new StreamReader(path))
            {
                invoiceNumber = sr.ReadLine();

            }

        }
        catch (Exception exp)
        {
            Console.WriteLine("The process failed: {0}", exp.ToString());
        }
       return invoiceNumber;
    }

How do I advance to the next line each time the invoiceNumberFunc() is called?

Thanks in advance.

+1  A: 

You need to use the same StreamReader rather than creating a new one. Each time you create a new one and dispose of the old one you're starting right back at the start of the file.

Try passing the same StreamReader reference in or keeping a record of the position you are at in the stream and using Seek() on the base stream if necessary. I'd recommend the first of these personally.

Ian
+7  A: 

You'd need to keep hold of the StreamReader between calls, either passing it into the method as a new parameter or making it a member variable of the class.

Personally I prefer the idea of it becoming a parameter, so that it never ends up as a member variable - that makes the life-time easier to manage:

void DoStuff()
{
    string path = @"C:\Users\sam\Documents\GCProg\testReadFile.txt";
    using (StreamReader sr = new StreamReader(path))
    {
        while (keepGoing) // Whatever logic you have
        {
            string invoice = InvoiceNumberFunc(sr);
            // Use invoice
        }
    }
}

string InvoiceNumberFunc(TextReader reader)
{
    string invoiceNumber;
    try
    {
        invoiceNumber = reader.ReadLine();
    }
    catch (Exception exp)
    {
        Console.WriteLine("The process failed: {0}", exp.ToString());
    }
    return invoiceNumber;
}
Jon Skeet
+1  A: 

You need to rework this, so that you're not creating the streamreader inside the method, but rather creating it at the class level, and just using it in the method, then disposing/closing the reader when you are done. Something like:

class MyClass
{
    private StreamReader sr;

    string invoiceNumberFunc()
    {
        if (sr == null) 
            sr = new StreamReader(path);

        if (sr.EndOfStream)  {
            sr.Close();
            sr = null;
            return string.Empty;
        }

        try {
            return sr.ReadLine();
        }
        catch(Exception exp) {
            Console.WriteLine("Process failed {0}",exp.ToString());
            return string.Empty;
        }
    }
}

In this case, it might also be a good idea to make your class IDisposable so you can verify that the StreamReader gets disposed, and also potentially make "initialize"/"close" routines, instead of doing the initialize and shutdown how I did here.

Reed Copsey
+2  A: 

You can't, since you create and dispose the stream reader in the function. Two ways come to mind:

You could store the stream reader in a member variable, or read all at once and store an array in a member variable.

Or you make it an iterator method by changing the return type to IEnumerable<string>, and changing the part in the using block to:

while ((invoiceNumber = sr.ReadLine()) != null) {
    yield return invoiceNumber;
}

This way, you can call foreach on your invoiceNumberFunc.

OregonGhost
An iterator block to read lines form a file is one of my favorite patterns.
Joel Coehoorn
+1  A: 

What you are looking for is the yield command:-

IEnumerable<string> GetInvoiceNumbers()
{
    string path = @"C:\Users\sam\Documents\GCProg\testReadFile.txt";
    using (StreamReader sr = new StreamReader(path))
    {
        while (!sr.EndOfStream)
        {
           yield return sr.ReadLine();
        }
    }
}

Now you can consume the return of this function with a simple for each:-

foreach(string invoiceNumber in GetInvoiceNumbers())
{
   //Do Stuff with invoice number
}

Or get creative with LINQ.

AnthonyWJones
+1  A: 

An other way of doing this is to transform your function in an iterator block using the yield return statement

The only thing is to make sure you add a finaly clause to your try and remove the catch as the yield return cannot be used in a naked try / catch. So your code would become:

    IEnumerable<String> invoiceNumberFunc()
    {
        string path = @"C:\Users\sam\Documents\GCProg\testReadFile.txt";
        try
        {
            using ( System.IO.StreamReader sr = new System.IO.StreamReader( path ) )
            {
                String invoiceNumber;
                while ( ( invoiceNumber = sr.ReadLine() ) != null )
                {
                    yield return sr.ReadLine();
                }
            }
        }
        finally
        {
        }
    }
Locksfree
+1, Good catch (excuse the pun) on the catch issue.
AnthonyWJones
Um, why not remove the try finally entirely then? It's not doing any good in this case.
Jon Skeet