Use yield in for loop - c#

I have such function to read large txt files
private static IEnumerable<string> ReadLineFromFile(TextReader fileReader)
{
using (fileReader)
{
string currentLine;
while ((currentLine = fileReader.ReadLine()) != null)
{
yield return currentLine;
}
}
}
yield can be used only in foreach loop.
TextReader readFile = new StreamReader(file_path);
foreach (string url in ReadLineFromFile(readFile))
{
}
I need to re-write this function to use it with for loop. To state with Iterator the line to read.
I tried something but my attempts were unsuccessful
Any ideas?

The yield keyword can be used in any iterator block, not just foreach - your code should work.
The issue with your code is you are disposing of the TextReader after reading the first line.
Move the using statement outside the ReadLineFromFile method i.e.
using (var reader = new StreamReader(file_path))
{
foreach(string url in ReadLineFromFile(reader))
{
...
}
}
...
private static IEnumerable<string> ReadLineFromFile(TextReader fileReader)
{
while ((var currentLine = fileReader.ReadLine()) != null)
{
yield return currentLine;
}
}
I mistakenly assumed that using yield whilst having the enumerable code wrapped with a using would cause it to dispose early, however, I ran your code and it works fine!
Alternatively, if you fancy writing less code you could accomplish the same thing using File.ReadLines
foreach (var line in File.ReadLines(file_path))
{
...
}

Related

Lambda into a method

I have a method where I'm reading a textfile.
I have to get the words in the textfile which start with "ART".
I have a foreach loop which loops through the method.
class ProductsList
{
public static void Main()
{
String path = #"D:\ProductsProjects\products.txt";
GetProducts(path, s => s.StartsWith("ART"));
//foreach (String productin GetProducts(path, s => s.StartsWith("ART")))
//Console.Write("{0}; ", word);
}
My method looks like this:
public static String GetProducts(String path, Func<String, bool> lambda)
{
try {
using (StreamReader sr = new StreamReader(path)){
string[] products= sr.ReadToEnd().Split(' ');
// need to get all the products starting with ART
foreach (string s in products){
return s;
}
}
}
catch (IOException ioe){
Console.WriteLine(ioe.Message);
}
return ="";
}
}
I'm having problems with the lambda in the method, I'm new to working with lambda's and I don't really know how to apply the lambda in the method.
I'm sorry if I can't really explain myself that well.
just add it here
foreach (string s in products.Where(lambda))
Update:
you should change your method like this to return a list of products and not just a single
public static IEnumerable<string> GetProducts(String path, Func<String, bool> lambda)
{
using (StreamReader sr = new StreamReader(path))
{
string[] products = sr.ReadToEnd().Split(' ');
// need to get all the products starting with ART
foreach (string s in products.Where(lambda))
{
yield return s;
}
}
}
Your code is wrong in that it only ever returns the one string, you want to return multiple strings, if the list of products is large this could also take a while, I'd recommend doing it this way:
public static IEnumerable<string> GetProducts(string path, Func<string, bool> matcher)
{
using(var stream = new FileStream(path, FileMode.Open, FileAccess.Read, FileShare.None))
{
using(var reader = new StreamReader(stream))
{
do
{
var line = reader.ReadLine();
if (matcher(line)) yield return line
}while(!reader.EndOfFile)
}
}
}
Then using it is as simple as:
foreach(var product in GetProducts("abc.txt", s => s.StartsWith("ART")))
{
Console.WriteLine("This is a matching product: {0}", product);
}
This code has the benefit of returning all of the lines that match the predicate (the lambda), as well as doing so using an iterator block, which means it doesn't actually read the next line until you ask for it.

How to read a txt file and load the contents into an arraylist?

I am new to C# and to programming in general. I am trying to read the contents of a txt file and load them to an arraylist. I can't figure out what condition to use in my while loop.
void LoadArrayList()
{
TextReader tr;
tr = File.OpenText("C:\\Users\\Maattt\\Documents\\Visual Studio 2010\\Projects\\actor\\actors.txt");
string Actor;
while (ActorArrayList != null)
{
Actor = tr.ReadLine();
if (Actor == null)
{
break;
}
ActorArrayList.Add(Actor);
}
}
void LoadArrayList()
{
TextReader tr;
tr = File.OpenText("C:\\Users\\Maattt\\Documents\\Visual Studio 2010\\Projects\\actor\\actors.txt");
string Actor;
Actor = tr.ReadLine();
while (Actor != null)
{
ActorArrayList.Add(Actor);
Actor = tr.ReadLine();
}
}
You can do it with just 2 lines of code
string[] Actor = File.ReadAllLines("C:\\Users\\Maattt\\Documents\\Visual Studio 2010\\Projects\\actor\\actors.txt");
ArrayList list = new ArrayList(Actor);
This is how it should be
void LoadArrayList()
{
string[] lines = System.IO.File.ReadAllLines(#"C:\Users\Maattt\Documents\Visual Studio 2010\Projects\actor\actors.txt");
// Display the file contents by using a foreach loop.
foreach (string Actor in lines)
{
ActorArrayList.Add(Actor);
}
}
Just rearrange it like this:
Actor = tr.ReadLine();
while (Actor != null)
{
ActorArrayList.Add(Actor);
Actor = tr.ReadLine();
}
If you look at the documentation for the TextReader.ReadLine method, you'll see that it returns either a string, or null if there are no more lines. So, what you can do is loop and check null against the results of the ReadLine method.
while(tr.ReadLine() != null)
{
// We know there are more items to read
}
With the above, though, you're not capturing the result of ReadLine. So you need to declare a string to capture the result and to use inside the while loop:
string line;
while((line = tr.ReadLine()) != null)
{
ActorArrayList.Add(line);
}
Also, I would suggest using a generic list, such as List<T> instead of the non-generic ArrayList. Using something like List<T> gives you more type safety and reduces the possibility of invalid assignments or casts.

C# NullReferenceException was unhandled

I am trying to use a CSV parser which I found on the net in my project. The problem is I am getting a null reference exception when I try to convert the string to a Tag and my collection does not get populated. Can anyone assist? Thanks
CSV Parser
private static IEnumerable<string[]> parseCSV(string path)
{
List<string[]> parsedData = new List<string[]>();
try
{
using (StreamReader readFile = new StreamReader(path))
{
string line;
string[] row;
while ((line = readFile.ReadLine()) != null)
{
row = line.Split(',');
parsedData.Add(row);
}
}
}
catch (Exception e)
{
System.Windows.MessageBox.Show(e.Message);
}
return parsedData;
}
Tag Class
public class Tag
{
public Tag(string name, int weight)
{
Name = name;
Weight = weight;
}
public string Name { get; set; }
public int Weight { get; set; }
public static IEnumerable<Tag> CreateTags(IEnumerable<string> words)
{
Dictionary<string, int> tags = new Dictionary<string, int>();
foreach (string word in words)
{
int count = 1;
if (tags.ContainsKey(word))
{
count = tags[word] + 1;
}
tags[word] = count;
}
return tags.Select(kvp => new Tag(kvp.Key, kvp.Value));
}
}
Validate all method arguments before you use them!
It breaks on this line: foreach (string word in words)
Remember that foreach loops work by calling GetEnumerator on the collection iterated over. That is, your foreach loop causes a call to words.GetEnumerator, and this call fails if words is null.
Therefore, validate your argument words by adding a guard at the very start of your CreateTags method:
if (words == null)
{
throw new ArgumentNullException("words");
}
This will help you find the location in your code where null is passed into CreateTags, and you can then continue fixing the calling code.
Suggestion: Avoid null whenever possible.
As a very general rule, try to avoid using null values whenever possible. For example, when your code is dealing with sets and collections of items, you could make sure that it also works correctly with empty collections. In a second step, make sure that you never use null to represent an empty collection; instead, use e.g. LINQ's Enumerable.Empty<TItem>() generator to create an empty collection.
One place where you could start doing this is in the CreateTags method by ensuring that no matter what the inputs are, that method will always return a valid, non-null (but possibly empty) collection:
if (words == null)
{
return Enumerable.Empty<Tag>(); // You could do without LINQ by writing:
// return new Tag[] { };
}
Every method should run sanity checks on the arguments it accepts to ensure the arguments are valid input parameters. I would probably do something like
public static IEnumerable<Tag> CreateTags(IEnumerable<string> words)
{
if(words==null)
{
//either throw a new ArgumentException or
return null; //or return new Dictionary<string,int>();
}
Dictionary<string, int> tags = new Dictionary<string, int>();
foreach (string word in words)
{
int count = 1;
if (tags.ContainsKey(word))
{
count = tags[word] + 1;
}
tags[word] = count;
}
return tags.Select(kvp => new Tag(kvp.Key, kvp.Value));
}
As to why your "words" param is null, it would be helpful to see the CSV file you are trying to parse in.
Hope this helps!

How to delete a line having line number using c#?

My file named as test.txt contains
This document is divided into about 5 logical sections starting with a feature and structure overview, followed by an overview of built in column and cell types. Next is an overview of working with data, followed by an overview of specific major features. Lastly, a “best practice” section concludes the main part of this document.
Now i want to delete 2nd line of the file.
How to do it using c#?
Thanks in advance.
Naveenkumar
List<string> lines = File.ReadAllLines(#"filename.txt").ToList();
if(lines.Count>lineNum){
lines.RemoveAt(lineNum);
}
File.WriteAllLines(#"filename.txt",lines.ToArray());
You can acheive this by splitting the text by \n and then using LINQ to select the lines you want to keep, and re-joining them.
var lineNum=5;
var lines=File
.ReadAllText(#"src.txt")
.Split('\n');
var outTxt=String
.Join(
"\n",
lines
.Take(lineNum)
.Concat(lines.Skip(lineNum+1))
.ToArray()
);
Here's a pretty efficient way to do it.
FileInfo x = new FileInfo(#"path\to\original");
string xpath = x.FullName;
FileInfo y = new FileInfo(#"path\to\temporary\new\file");
using (var reader = x.OpenText())
using (var writer = y.AppendText())
{
// write 1st line
writer.WriteLine(reader.ReadLine());
reader.ReadLine(); // skip 2nd line
// write all remaining lines
while (!reader.EndOfStream)
{
writer.WriteLine(reader.ReadLine());
}
}
x.Delete();
y.MoveTo(xpath);
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.IO;
namespace rem2ndline
{
class Program
{
static void Main(string[] args)
{
string inPath = #"c:\rem2ndline.txt";
string outPath = #"c:\rem2ndlineresult.txt";
StringBuilder builder = new StringBuilder();
using (FileStream fso = new FileStream(inPath, FileMode.Open))
{
using (StreamReader rdr = new StreamReader(fso))
{
int lineCount = 0;
bool canRead = true;
while (canRead)
{
var line = rdr.ReadLine();
lineCount++;
if (line == null)
{
canRead = false;
}
else
{
if (lineCount != 2)
{
builder.AppendLine(line);
}
}
}
}
}
using(FileStream fso2 = new FileStream(outPath, FileMode.OpenOrCreate))
{
using (StreamWriter strw = new StreamWriter(fso2))
{
strw.Write(builder.ToString());
}
}
}
}
}
Here's what I'd do. The advantage is that you don't have to have the file in memory all at once, so memory requirements should be similar for files of varying sizes (as long as the lines contained in each of the files are of similar length). The drawback is that you can't pipe back to the same file - you have to mess around with a Delete and a Move afterwards.
The extension methods may be overkill for your simple example, but those are two extension methods I come to rely on again and again, as well as the ReadFile method, so I'd typically only have to write the code in Main().
class Program
{
static void Main()
{
var file = #"C:\myFile.txt";
var tempFile = Path.ChangeExtension(file, "tmp");
using (var writer = new StreamWriter(tempFile))
{
ReadFile(file)
.FilterI((i, line) => i != 1)
.ForEach(l => writer.WriteLine(l));
}
File.Delete(file);
File.Move(tempFile, file);
}
static IEnumerable<String> ReadFile(String file)
{
using (var reader = new StreamReader(file))
{
while (!reader.EndOfStream)
{
yield return reader.ReadLine();
}
}
}
}
static class IEnumerableExtensions
{
public static IEnumerable<T> FilterI<T>(
this IEnumerable<T> seq,
Func<Int32, T, Boolean> filter)
{
var index = 0;
foreach (var item in seq)
{
if (filter(index, item))
{
yield return item;
}
index++;
}
}
public static void ForEach<T>(
this IEnumerable<T> seq,
Action<T> action)
{
foreach (var item in seq)
{
action(item);
}
}
}

Advance C# ReadLine() to next line in a function call

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.
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;
}
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.
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.
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.
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.
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
{
}
}

Categories

Resources