Related
There are a lot of different ways to read and write files (text files, not binary) in C#.
I just need something that is easy and uses the least amount of code, because I am going to be working with files a lot in my project. I only need something for string since all I need is to read and write strings.
Use File.ReadAllText and File.WriteAllText.
MSDN example excerpt:
// Create a file to write to.
string createText = "Hello and Welcome" + Environment.NewLine;
File.WriteAllText(path, createText);
...
// Open the file to read from.
string readText = File.ReadAllText(path);
In addition to File.ReadAllText, File.ReadAllLines, and File.WriteAllText (and similar helpers from File class) shown in another answer you can use StreamWriter/StreamReader classes.
Writing a text file:
using(StreamWriter writetext = new StreamWriter("write.txt"))
{
writetext.WriteLine("writing in text file");
}
Reading a text file:
using(StreamReader readtext = new StreamReader("readme.txt"))
{
string readText = readtext.ReadLine();
}
Notes:
You can use readtext.Dispose() instead of using, but it will not close file/reader/writer in case of exceptions
Be aware that relative path is relative to current working directory. You may want to use/construct absolute path.
Missing using/Close is very common reason of "why data is not written to file".
FileStream fs = new FileStream(txtSourcePath.Text,FileMode.Open, FileAccess.Read);
using(StreamReader sr = new StreamReader(fs))
{
using (StreamWriter sw = new StreamWriter(Destination))
{
sw.Writeline("Your text");
}
}
The easiest way to read from a file and write to a file:
//Read from a file
string something = File.ReadAllText("C:\\Rfile.txt");
//Write to a file
using (StreamWriter writer = new StreamWriter("Wfile.txt"))
{
writer.WriteLine(something);
}
using (var file = File.Create("pricequote.txt"))
{
...........
}
using (var file = File.OpenRead("pricequote.txt"))
{
..........
}
Simple, easy and also disposes/cleans up the object once you are done with it.
#AlexeiLevenkov pointed me at another "easiest way" namely the extension method. It takes just a little coding, then provides the absolute easiest way to read/write, plus it offers the flexibility to create variations according to your personal needs. Here is a complete example:
This defines the extension method on the string type. Note that the only thing that really matters is the function argument with extra keyword this, that makes it refer to the object that the method is attached to. The class name does not matter; the class and method must be declared static.
using System.IO;//File, Directory, Path
namespace Lib
{
/// <summary>
/// Handy string methods
/// </summary>
public static class Strings
{
/// <summary>
/// Extension method to write the string Str to a file
/// </summary>
/// <param name="Str"></param>
/// <param name="Filename"></param>
public static void WriteToFile(this string Str, string Filename)
{
File.WriteAllText(Filename, Str);
return;
}
// of course you could add other useful string methods...
}//end class
}//end ns
This is how to use the string extension method, note that it refers automagically to the class Strings:
using Lib;//(extension) method(s) for string
namespace ConsoleApp_Sandbox
{
class Program
{
static void Main(string[] args)
{
"Hello World!".WriteToFile(#"c:\temp\helloworld.txt");
return;
}
}//end class
}//end ns
I would never have found this myself, but it works great, so I wanted to share this. Have fun!
These are the best and most commonly used methods for writing to and reading from files:
using System.IO;
File.AppendAllText(sFilePathAndName, sTextToWrite);//add text to existing file
File.WriteAllText(sFilePathAndName, sTextToWrite);//will overwrite the text in the existing file. If the file doesn't exist, it will create it.
File.ReadAllText(sFilePathAndName);
The old way, which I was taught in college was to use stream reader/stream writer, but the File I/O methods are less clunky and require fewer lines of code. You can type in "File." in your IDE (make sure you include the System.IO import statement) and see all the methods available. Below are example methods for reading/writing strings to/from text files (.txt.) using a Windows Forms App.
Append text to an existing file:
private void AppendTextToExistingFile_Click(object sender, EventArgs e)
{
string sTextToAppend = txtMainUserInput.Text;
//first, check to make sure that the user entered something in the text box.
if (sTextToAppend == "" || sTextToAppend == null)
{MessageBox.Show("You did not enter any text. Please try again");}
else
{
string sFilePathAndName = getFileNameFromUser();// opens the file dailog; user selects a file (.txt filter) and the method returns a path\filename.txt as string.
if (sFilePathAndName == "" || sFilePathAndName == null)
{
//MessageBox.Show("You cancalled"); //DO NOTHING
}
else
{
sTextToAppend = ("\r\n" + sTextToAppend);//create a new line for the new text
File.AppendAllText(sFilePathAndName, sTextToAppend);
string sFileNameOnly = sFilePathAndName.Substring(sFilePathAndName.LastIndexOf('\\') + 1);
MessageBox.Show("Your new text has been appended to " + sFileNameOnly);
}//end nested if/else
}//end if/else
}//end method AppendTextToExistingFile_Click
Get file name from the user via file explorer/open file dialog (you will need this to select existing files).
private string getFileNameFromUser()//returns file path\name
{
string sFileNameAndPath = "";
OpenFileDialog fd = new OpenFileDialog();
fd.Title = "Select file";
fd.Filter = "TXT files|*.txt";
fd.InitialDirectory = Environment.CurrentDirectory;
if (fd.ShowDialog() == DialogResult.OK)
{
sFileNameAndPath = (fd.FileName.ToString());
}
return sFileNameAndPath;
}//end method getFileNameFromUser
Get text from an existing file:
private void btnGetTextFromExistingFile_Click(object sender, EventArgs e)
{
string sFileNameAndPath = getFileNameFromUser();
txtMainUserInput.Text = File.ReadAllText(sFileNameAndPath); //display the text
}
Or, if you are really about lines:
System.IO.File also contains a static method WriteAllLines, so you could do:
IList<string> myLines = new List<string>()
{
"line1",
"line2",
"line3",
};
File.WriteAllLines("./foo", myLines);
It's good when reading to use the OpenFileDialog control to browse to any file you want to read. Find the code below:
Don't forget to add the following using statement to read files: using System.IO;
private void button1_Click(object sender, EventArgs e)
{
if (openFileDialog1.ShowDialog() == DialogResult.OK)
{
textBox1.Text = File.ReadAllText(openFileDialog1.FileName);
}
}
To write files you can use the method File.WriteAllText.
class Program
{
public static void Main()
{
//To write in a txt file
File.WriteAllText("C:\\Users\\HP\\Desktop\\c#file.txt", "Hello and Welcome");
//To Read from a txt file & print on console
string copyTxt = File.ReadAllText("C:\\Users\\HP\\Desktop\\c#file.txt");
Console.Out.WriteLine("{0}",copyTxt);
}
}
private void Form1_Load(object sender, EventArgs e)
{
//Write a file
string text = "The text inside the file.";
System.IO.File.WriteAllText("file_name.txt", text);
//Read a file
string read = System.IO.File.ReadAllText("file_name.txt");
MessageBox.Show(read); //Display text in the file
}
Reading from file
string filePath = #"YOUR PATH";
List<string> lines = File.ReadAllLines(filePath).ToList();
Writing to file
List<string> lines = new List<string>();
string a = "Something to be written"
lines.Add(a);
File.WriteAllLines(filePath, lines);
Simply:
String inputText = "Hello World!";
File.WriteAllText("yourfile.ext",inputText); //writing
var outputText = File.ReadAllText("yourfile.ext"); //reading
You're looking for the File, StreamWriter, and StreamReader classes.
I'm working on converting some files, but I'm having some issues on the 2nd step of this.
Load file from source location
Save file to temp folder
Save converted file to Output location
I have 2 methods for reading the original file, but there is a problem with both of them.
Method 1: The file remains locked (so when something goes wrong, I have to restart the app)
Method 2: The temp file is empty
Anybody got an idea on how to fix one of those problems?
Utilities class
/// <summary>
/// Get document stream
/// </summary>
/// <param name="DocumentName">Input document name</param>
public static Stream GetDocumentStreamFromLocation(string documentLocation)
{
try
{
//ExStart:GetDocumentStream
// Method one: works, but locks file
return File.Open(documentLocation, FileMode.Open, FileAccess.Read);
// Method two: gives empty file on temp folder
using (FileStream fsSource = File.Open(documentLocation, FileMode.Open, FileAccess.Read))
{
var stream = new MemoryStream((int)fsSource.Length);
fsSource.CopyTo(stream);
return stream;
}
//ExEnd:GetDocumentStream
}
catch (FileNotFoundException ioEx)
{
Console.WriteLine(ioEx.Message);
return null;
}
}
/// <summary>
/// Save file in any format
/// </summary>
/// <param name="filename">Save as provided string</param>
/// <param name="content">Stream as content of a file</param>
public static void SaveFile(string filename, Stream content, string location = OUTPUT_PATH)
{
try
{
//ExStart:SaveAnyFile
//Create file stream
using (FileStream fileStream = File.Create(Path.Combine(Path.GetFullPath(location), filename)))
{
content.CopyTo(fileStream);
}
//ExEnd:SaveAnyFile
}
catch (System.Exception ex)
{
Console.WriteLine(ex.Message);
}
}
I Call the following functions as following:
public static StreamContent Generate(string sourceLocation)
{
// Get filename
var fileName = Path.GetFileName(sourceLocation);
// Create tempfilename
var tempFilename = $"{Guid.NewGuid()}_{fileName}";
// Put file in storage location
Utilities.SaveFile(tempFilename, Utilities.GetDocumentStreamFromLocation(sourceLocation), Utilities.STORAGE_PATH);
// ... More code
}
In order to copy the source file to a temp folder, the easiest way is to use the File.Copy method from the System.IO namespace. Consider the following:
// Assuming the variables have been set as you already had, this creates a copy in the intended location.
File.Copy(documentLocation, filename);
After some further digging. I found out that you can add a property in the File.Open that "fixes" this issue:
return File.Open(documentLocation, FileMode.Open, FileAccess.Read, FileShare.ReadWrite);
With the downside that you still can't move / rename the file, but the lock is removed.
Alright I want to create a .csv file in C#. I have been looking around and noticed a lot of people are using the system.IO.memorystream and system.io.streamwriter.
The problem is this: I have a web application. I want to give the user the ability to export to excel. Problem is, Excel cannot be installed on the server (don't ask). I want to be able to write an .csv sheet export for a report. Now, the reports headers and data will be different for all of the reports (looping through will solve this). Anybody have an example or better resources for me to go through?
This the approach i normally take. Probably not the most efficient though.
/// <summary>
/// Generates the contents of the log file.
/// </summary>
/// <returns>The contents of the log file.</returns>
internal string GenerateLogFile()
{
StringBuilder csvExport = new StringBuilder();
csvExport.AppendLine(Resources.CSVHeader);
foreach (DataRow row in this.logEntries.Rows)
{
csvExport.AppendLine(
string.Format(
"\"{0}\",\"{1}\",\"{2}\",\"{3}\",\"{4}\",\"{5}\",\"{6}\",\"{7}\",\"{8}\", \"{9}\"",
row[ColumnNames.LogTime], row[ColumnNames.Field1], row[ColumnNames.Field2], row[ColumnNames.Field3], row[ColumnNames.Field4], row[ColumnNames.Field5], row[ColumnNames.Field6], row[ColumnNames.Field7], row[ColumnNames.Field8], row[ColumnNames.Field9]));
}
return csvExport.ToString();
}
/// <summary>
/// Adds the CSV file to the response.
/// </summary>
/// <param name="csvExportContents">The contents of the CSV file.</param>
internal void DisplayLogFile(string csvExportContents)
{
byte[] data = new ASCIIEncoding().GetBytes(csvExportContents);
HttpContext.Current.Response.Clear();
HttpContext.Current.Response.ContentType = "APPLICATION/OCTET-STREAM";
HttpContext.Current.Response.AppendHeader("Content-Disposition", "attachment; filename=Export.csv");
HttpContext.Current.Response.OutputStream.Write(data, 0, data.Length);
HttpContext.Current.Response.End();
}
private void WriteItem<T>(StreamWriter sr, T item)
{
string itemString = item.ToString();
if(itemString.IndexOfAny(new char[] { '"', ',', '\n', '\r' }) != -1)//skip test and always escape for different speed/filesize optimisation
{
sr.Write('"');
sr.Write(itemString.Replace("\"", "\"\""));
sr.Write('"');
}
else
sr.Write(itemString);
}
private void WriteLine<T>(StreamWriter sr, IEnumerable<T> line)
{
bool first = true;
foreach(T item in line)
{
if(!first)
sr.Write(',');
first = false;
WriteItem(sr, item);
}
}
private void WriteCSV<T>(StreamWriter sr, IEnumerable<IEnumerable<T>> allLines)
{
bool first = true;
foreach(IEnumerable<T> line in allLines)
{
if(!first)
sr.Write('\n');
first = false;
WriteLine(sr, line);
}
}
private void WriteCSV<T>(HttpResponse response, IEnumerable<IEnumerable<T>> allLines)
{
response.ContentType = "text/csv";
WriteCSV(response.Output, allLines);
}
It can be worth also sending a content-disposition header with a recommended filename.
Edit: Of late, with cases where one needs to interspace an action between items in an enumeration (like the comma and newline above), I've preferred that rather keeping a boolean that keeps being checked, I handle the enumerator directly, and then handle the first element separate from the rest. I started doing this as a micro-opt in a efficiency-push but have grown to just find it a better expression of a code-path that differs for the first item. As such, I'd now write the above as:
private void WriteLine<T>(StreamWriter sr, IEnumerable<T> line)
{
using(var en = line.GetEnumerator())
if(en.MoveNext())
{
WriteItem(sr, en.Current);
while(en.MoveNext())
{
sr.Write(',');
WriteItem(sr, en.Current);
}
}
private void WriteCSV<T>(StreamWriter sr, IEnumerable<IEnumerable<T>> allLines)
{
using(var en = allLines.GetEnumerator())
if(en.MoveNext())
{
WriteLine(sr, en.Current);
while(en.MoveNext())
{
sr.Write('\n');
WriteLine(sr, en.Current);
}
}
}
You should be able to use the examples using System.IO.MemoryStream and System.IO.StreamWriter just fine.
Instead of writing the MemoryStream out to a file, you would return it as the ResponseStream in ASP.NET (making sure that you set the appropriate header values so the browser knows that it's downloading a file).
Excel is only required if you are using Office Interop. Using StringWriter, you are simply going to create a file and add some response information. Excel is only required when opening the file.
There is really no magic in creating a CSV file. The only problem with CSV files is that there is not really a standard, and most importers will just implement one particular behavior for the import process. If you are lucky, you will get something that can guess relatively well.
Generating the CSV file is just a matter of printing the lines. As for the actual details, familiarize yourself with:
http://en.wikipedia.org/wiki/Comma-separated_values
That being said, if what you want is to export to Excel, you could use Microsoft's OOXML SDK:
http://msdn.microsoft.com/en-us/library/bb448854.aspx
The OOXML SDK is a C# library that you can use to generate Excel files on the server. The Brian Jones blog is a great resource on how to use this library:
http://blogs.msdn.com/b/brian_jones/
If you don't mind using a 3rd party library and the LGPL license is okay in your project, then FileHelpers is a great tool for this.
This is the function used to create csv file:
private async Task<string> WriteCSV<ViewModel>(IEnumerable<ViewModel> viewModels, string path)
{
Type itemType = typeof(ViewModel);
var props = itemType.GetProperties(BindingFlags.Public | BindingFlags.Instance)
.OrderBy(p => p.Name);
var blobName = string.Empty;
using (var ms = new MemoryStream())
{
using (var writer = new System.IO.StreamWriter(ms))
{
writer.WriteLine(string.Join(", ", props.Select(p => p.Name)));
foreach (var item in viewModels)
{
writer.WriteLine(string.Join(", ", props.Select(p => p.GetValue(item, null))));
}
}
}
}
Otherwise you can refer below link:
https://travelcodingnlotsmore.wordpress.com/2013/06/06/creating-csv-file-from-data-in-list-object-in-c/
PrintCapabilities printCapabilites = SelectedPrinter.GetPrintCapabilities();
IEnumerable pagesizeList = printCapabilites.PageMediaSizeCapability;
The above code does not list all the page sizes that the printer driver supports and this is my problem.
As an example if you use the Microsoft XPS printer driver you will find that pagesizeList (above) will be missing some page sizes. "Letter Small" is one of the missing page sizes (MS Word will successfully list this page size though).
As a quick check I dumped the Printer capabilities to xml as below:
long gpCLen = _selectedPrinter.GetPrintCapabilitiesAsXml().Length;
FileStream fs = File.OpenWrite(#"c:\test.txt");
MemoryStream ms = _selectedPrinter.GetPrintCapabilitiesAsXml();
byte[] b = new byte[gpCLen];
ms.Read(b, 0, (int)gpCLen);
fs.Write(b, 0, (int)gpCLen);
fs.Close();
The PageMediaSize node in the xml file produced does indeed have all the page sizes, AND the missing ones.
Displayed pages seem to have their name start psk:
<psf:Option name="psk:ISOA4" constrained="psk:None">
but undisplayed pages seem to have:
<psf:Option name="ns0000:LETTERSMALL" constrained="psk:None">
Undisplayed Epson print driver pages are similar:
<psf:Option name="epns200:IndexCard5x8" constrained="psk:None">
Basically a page whos name starts 'psk' PageMediaSizeCapability displays, but if it's manufacture custom ie epns200, ns0000, it doesn't list. Any ideas why and how to fix this please? The nodes/Pages are there but PageMediaSizeCapability doesn't like it!
Thanks in advance
EDIT:
As MS state 'A PrintCapabilities object is an easy-to-work-with representation of a certain type of XML document called a PrintCapabilities document.' But the document has more info than the object Full description
you have to save the paper name that you read from PrintCapabilities xml, and the use it to create a PrintTicket xml (PrintTicket has a constructor that accepts a xml stream), and then use the PrintTicket. Here is an example of PrintTicket XML (ns0000:User0000000257 is hte name of a custom paper size that i created):
<psf:PrintTicket xmlns:psf="http://schemas.microsoft.com/windows/2003/08/printing/printschemaframework"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xmlns:xsd="http://www.w3.org/2001/XMLSchema"
xmlns:psk="http://schemas.microsoft.com/windows/2003/08/printing/printschemakeywords"
xmlns:ns0000="http://schemas.microsoft.com/windows/printing/oemdriverpt/Samsung_CLP-310 Series/5.1.2600.2180/" version="1">
<psf:Feature name="psk:PageMediaSize">
<psf:Option name="ns0000:User0000000257"></psf:Option>
</psf:Feature>
</psf:PrintTicket>
Even though this was an older thread, it was helpful in pointing us in the right direction when we needed to read and update custom properties.
The code below was adapted from this thread: XPS Printing, Tray selection and InputBinCapability (InputBin) = Problem: http://www.windows-tech.info/14/29c7cf575646cb39.php. Jo0815's answer at the bottom had most of what you see below, especially the XPath expressions that pointed us in the right direction.
Also, update the PrintQueue.UserPrintTicket with your changes, NOT the DefaultPrintTicket.
using System;
using System.Collections.Generic;
using System.IO;
using System.Linq;
using System.Printing;
using System.Text;
using System.Windows;
using System.Xml;
// Adapted
// From: XPS Printing, Tray selection and InputBinCapability (InputBin) = Problem
// Link: http://www.windows-tech.info/14/29c7cf575646cb39.php - last answer at bottom by Jo0815
namespace WpfApplication1
{
public static class WpfPrinterUtilities
{
#region GetPrintQueues
/// <summary>
/// Gets a dictionary of print queues where Key = print queue name
/// and Value = the print queue object.
/// </summary>
/// <param name="printQueueTypes">EnumeratedPrintQueueTypes params array of the types of print queues being requested.</param>
/// <returns>Dictionary of requested print queues where Key = print queue name and Value = the print queue object itself.</returns>
public static Dictionary<string, PrintQueue> GetPrintQueues(params EnumeratedPrintQueueTypes[] printQueueTypes)
{
var server = new PrintServer();
return server.GetPrintQueues(printQueueTypes).ToDictionary(pq => pq.ShareName != null ? pq.ShareName : pq.Name);
}
#endregion
#region GetInputBins
/// <summary>
/// Reads print queue configuration xml to retrieve the current list of input bins.
/// </summary>
/// <param name="printQueue">The print queue to query.</param>
/// <returns></returns>
public static Dictionary<string, string> GetInputBins(PrintQueue printQueue)
{
Dictionary<string, string> inputBins = new Dictionary<string, string>();
// Get the print queue PrintCapabilities.
XmlDocument xmlDoc = null;
using (MemoryStream stream = printQueue.GetPrintCapabilitiesAsXml())
{
// Read the JobInputBins out of the PrintCapabilities.
xmlDoc = new XmlDocument();
xmlDoc.Load(stream);
}
// Create NamespaceManager and add PrintSchemaFrameWork-Namespace (should be on DocumentElement of the PrintTicket).
// Prefix: psf NameSpace: xmlDoc.DocumentElement.NamespaceURI = "http://schemas.microsoft.com/windows/2003/08/printing/printschemaframework"
XmlNamespaceManager manager = new XmlNamespaceManager(xmlDoc.NameTable);
manager.AddNamespace(xmlDoc.DocumentElement.Prefix, xmlDoc.DocumentElement.NamespaceURI);
// Select all job input bins.
XmlNodeList nodeList = xmlDoc.SelectNodes("//psf:Feature[#name='psk:JobInputBin']/psf:Option/psf:Property", manager);
// Load the Dictionary with the bin values and names. The names will be used to modify the print ticket if necessary.
foreach (XmlNode node in nodeList)
{
inputBins.Add(node.LastChild.InnerText, node.ParentNode.Attributes[0].Value);
}
return inputBins;
}
#endregion
#region ModifyPrintTicket
/// <summary>
/// Modifes a print ticket xml after updating a feature value.
///
/// Sample usage:
/// Get Dictionary with Inputbins by calling the other method
/// and get "value" for the desired inputbin you'd like to use...
/// ...
/// desiredTray is then something like "NS0000:SurpriseOption7" for example.
/// defaultPrintTicket is the (Default)PrintTicket you want to modify from the PrintQueue for example
/// PrintTicket myPrintTicket = WpfPrinterUtils.ModifyPrintTicket(defaultPrintTicket, "psk:JobInputBin", desiredTray);
/// </summary>
/// <param name="ticket"></param>
/// <param name="featureName"></param>
/// <param name="newValue"></param>
/// <param name="printQueueName">Optional - If provided, a file is created with the print ticket xml. Useful for debugging.</param>
/// <param name="folder">Optional - If provided, the path for a file is created with the print ticket xml. Defaults to c:\. Useful for debugging.</param>
/// <param name="displayMessage">Optional - True to display a dialog with changes. Defaults to false. Useful for debugging.</param>
/// <returns></returns>
public static PrintTicket ModifyPrintTicket(PrintTicket ticket, string featureName, string newValue, string printQueueName = null, string folder = null, bool displayMessage = false)
{
if (ticket == null)
{
throw new ArgumentNullException("ticket");
}
// Read Xml of the PrintTicket xml.
XmlDocument xmlDoc = new XmlDocument();
xmlDoc.Load(ticket.GetXmlStream());
// Create NamespaceManager and add PrintSchemaFrameWork-Namespace hinzufugen (should be on DocumentElement of the PrintTicket).
// Prefix: psf NameSpace: xmlDoc.DocumentElement.NamespaceURI = "http://schemas.microsoft.com/windows/2003/08/printing/printschemaframework"
XmlNamespaceManager manager = new XmlNamespaceManager(xmlDoc.NameTable);
manager.AddNamespace(xmlDoc.DocumentElement.Prefix, xmlDoc.DocumentElement.NamespaceURI);
// Search node with desired feature we're looking for and set newValue for it
string xpath = string.Format("//psf:Feature[#name='{0}']/psf:Option", featureName);
XmlNode node = xmlDoc.SelectSingleNode(xpath, manager);
if (node != null)
{
if (node.Attributes["name"].Value != newValue)
{
if (displayMessage)
{
System.Windows.MessageBox.Show(string.Format("OldValue: {0}, NewValue: {1}", node.Attributes["name"].Value, newValue), "Input Bin");
}
node.Attributes["name"].Value = newValue;
}
}
// Create a new PrintTicket out of the XML.
PrintTicket modifiedPrintTicket = null;
using (MemoryStream stream = new MemoryStream())
{
xmlDoc.Save(stream);
stream.Position = 0;
modifiedPrintTicket = new PrintTicket(stream);
}
// For testing purpose save the print ticket to a file.
if (!string.IsNullOrWhiteSpace(printQueueName))
{
if (string.IsNullOrWhiteSpace(folder))
{
folder = "c:\\";
}
// Colons are not valid in a file name.
newValue = newValue.Replace(':', ';');
printQueueName = string.Format("{0} PrintTicket {1}.xml", Path.Combine(folder, printQueueName), newValue);
if (File.Exists(printQueueName))
{
File.Delete(printQueueName);
}
if (!Directory.Exists(Path.GetDirectoryName(printQueueName)))
{
Directory.CreateDirectory(Path.GetDirectoryName(printQueueName));
}
using (FileStream stream = new FileStream(printQueueName, FileMode.CreateNew, FileAccess.ReadWrite))
{
modifiedPrintTicket.GetXmlStream().WriteTo(stream);
}
}
return modifiedPrintTicket;
}
#endregion
}
}
I'm testing how the classes FileStream and StreamReader work togheter. Via a Console application.
I'm trying to go in a file and read the lines and print them on the console.
I've been able to do it with a while-loop, but I want to try it with a foreach loop.
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.IO;
namespace testing
{
public class Program
{
public static void Main(string[] args)
{
string file = #"C:\Temp\New Folder\New Text Document.txt";
using(FileStream fs = new FileStream(file, FileMode.Open, FileAccess.Read))
{
using(StreamReader sr = new StreamReader(fs))
{
foreach(string line in file)
{
Console.WriteLine(line);
}
}
}
}
}
}
The error I keep getting for this is: Cannot convert type 'char' to 'string'
The while loop, which does work, looks like this:
while((line = sr.ReadLine()) != null)
{
Console.WriteLine(line);
}
I'm probably overlooking something really basic, but I can't see it.
If you want to read a file line-by-line via foreach (in a reusable fashion), consider the following iterator block:
public static IEnumerable<string> ReadLines(string path)
{
using (StreamReader reader = File.OpenText(path))
{
string line;
while ((line = reader.ReadLine()) != null)
{
yield return line;
}
}
}
Note that this this is lazily evaluated - there is none of the buffering that you would associate with File.ReadAllLines(). The foreach syntax will ensure that the iterator is Dispose()d correctly even for exceptions, closing the file:
foreach(string line in ReadLines(file))
{
Console.WriteLine(line);
}
(this bit is added just for interest...)
Another advantage of this type of abstraction is that it plays beautifully with LINQ - i.e. it is easy to do transformations / filters etc with this approach:
DateTime minDate = new DateTime(2000,1,1);
var query = from line in ReadLines(file)
let tokens = line.Split('\t')
let person = new
{
Forname = tokens[0],
Surname = tokens[1],
DoB = DateTime.Parse(tokens[2])
}
where person.DoB >= minDate
select person;
foreach (var person in query)
{
Console.WriteLine("{0}, {1}: born {2}",
person.Surname, person.Forname, person.DoB);
}
And again, all evaluated lazily (no buffering).
To read all lines in New Text Document.txt:
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.IO;
namespace testing
{
public class Program
{
public static void Main(string[] args)
{
string file = #"C:\Temp\New Folder\New Text Document.txt";
using(FileStream fs = new FileStream(file, FileMode.Open, FileAccess.Read))
{
using(StreamReader sr = new StreamReader(fs))
{
while(!sr.EndOfStream)
{
Console.WriteLine(sr.ReadLine());
}
}
}
}
}
}
I have a LineReader class in my MiscUtil project. It's slightly more general than the solutions given here, mostly in terms of the way you can construct it:
From a function returning a stream, in which case it will use UTF-8
From a function returning a stream, and an encoding
From a function which returns a text reader
From just a filename, in which case it will use UTF-8
From a filename and an encoding
The class "owns" whatever resources it uses, and closes them appropriately. However, it does this without implementing IDisposable itself. This is why it takes Func<Stream> and Func<TextReader> instead of the stream or the reader directly - it needs to be able to defer the opening until it needs it. It's the iterator itself (which is automatically disposed by a foreach loop) which closes the resource.
As Marc pointed out, this works really well in LINQ. One example I like to give is:
var errors = from file in Directory.GetFiles(logDirectory, "*.log")
from line in new LineReader(file)
select new LogEntry(line) into entry
where entry.Severity == Severity.Error
select entry;
This will stream all the errors from a whole bunch of log files, opening and closing as it goes. Combined with Push LINQ, you can do all kinds of nice stuff :)
It's not a particularly "tricky" class, but it's really handy. Here's the full source, for convenience if you don't want to download MiscUtil. The licence for the source code is here.
using System;
using System.Collections;
using System.Collections.Generic;
using System.IO;
using System.Text;
namespace MiscUtil.IO
{
/// <summary>
/// Reads a data source line by line. The source can be a file, a stream,
/// or a text reader. In any case, the source is only opened when the
/// enumerator is fetched, and is closed when the iterator is disposed.
/// </summary>
public sealed class LineReader : IEnumerable<string>
{
/// <summary>
/// Means of creating a TextReader to read from.
/// </summary>
readonly Func<TextReader> dataSource;
/// <summary>
/// Creates a LineReader from a stream source. The delegate is only
/// called when the enumerator is fetched. UTF-8 is used to decode
/// the stream into text.
/// </summary>
/// <param name="streamSource">Data source</param>
public LineReader(Func<Stream> streamSource)
: this(streamSource, Encoding.UTF8)
{
}
/// <summary>
/// Creates a LineReader from a stream source. The delegate is only
/// called when the enumerator is fetched.
/// </summary>
/// <param name="streamSource">Data source</param>
/// <param name="encoding">Encoding to use to decode the stream
/// into text</param>
public LineReader(Func<Stream> streamSource, Encoding encoding)
: this(() => new StreamReader(streamSource(), encoding))
{
}
/// <summary>
/// Creates a LineReader from a filename. The file is only opened
/// (or even checked for existence) when the enumerator is fetched.
/// UTF8 is used to decode the file into text.
/// </summary>
/// <param name="filename">File to read from</param>
public LineReader(string filename)
: this(filename, Encoding.UTF8)
{
}
/// <summary>
/// Creates a LineReader from a filename. The file is only opened
/// (or even checked for existence) when the enumerator is fetched.
/// </summary>
/// <param name="filename">File to read from</param>
/// <param name="encoding">Encoding to use to decode the file
/// into text</param>
public LineReader(string filename, Encoding encoding)
: this(() => new StreamReader(filename, encoding))
{
}
/// <summary>
/// Creates a LineReader from a TextReader source. The delegate
/// is only called when the enumerator is fetched
/// </summary>
/// <param name="dataSource">Data source</param>
public LineReader(Func<TextReader> dataSource)
{
this.dataSource = dataSource;
}
/// <summary>
/// Enumerates the data source line by line.
/// </summary>
public IEnumerator<string> GetEnumerator()
{
using (TextReader reader = dataSource())
{
string line;
while ((line = reader.ReadLine()) != null)
{
yield return line;
}
}
}
/// <summary>
/// Enumerates the data source line by line.
/// </summary>
IEnumerator IEnumerable.GetEnumerator()
{
return GetEnumerator();
}
}
}
The problem is in:
foreach(string line in file)
{
Console.WriteLine(line);
}
Its because the "file" is string, and string implements IEnumerable. But this enumerator returns "char" and "char" can not be implictly converted to string.
You should use the while loop, as you sayd.
Slightly more elegant is the following...
using (var fileStream = new FileStream(file, FileMode.Open, FileAccess.Read))
{
using (var streamReader = new StreamReader(fileStream))
{
while (!streamReader.EndOfStream)
{
yield return reader.ReadLine();
}
}
}
Looks like homework to me ;)
You're iterating over the filename (a string) itself which gives you one character at a time. Just use the while approach that correctly uses sr.ReadLine().
Instead of using a StreamReader and then trying to find lines inside the String file variable, you can simply use File.ReadAllLines:
string[] lines = File.ReadAllLines(file);
foreach(string line in lines)
Console.WriteLine(line);
You are enumerating a string, and when you do that, you take one char at the time.
Are you sure this is what you want?
foreach(string line in file)
A simplistic (not memory efficient) approach of iterating every line in a file is
foreach (string line in File.ReadAllLines(file))
{
..
}
I presume you want something like this:
using ( FileStream fileStream = new FileStream( file, FileMode.Open, FileAccess.Read ) )
{
using ( StreamReader streamReader = new StreamReader( fileStream ) )
{
string line = "";
while ( null != ( line = streamReader.ReadLine() ) )
{
Console.WriteLine( line );
}
}
}