I am trying to unit test a method that calls StreamWriter. I am trying to use System.IO.Abstraction in order to mock StreamWriter however I can't find the interface on the last NuGet looked into the source code as well but have no idea what is the replacement for this, other stuff like FileInfo is working as expected.
Thanks,
Taking #BarryMcDermid's answer and altering it slightly you can do something along the lines of:
using (Stream fs = _fileSystem.FileStream.Create(filePath, FileMode.Create))
{
ms.CopyTo(fs);
fs.Flush();
fs.Close();
}
By declaring the 'FileStream' as a Stream instead of a FileStream you'll then be able to use System.IO.Abstraction.TestingHelpers to test this code without getting exceptions.
There's a more fully worked example of that in my question here.
I was also looking for how to mock a FileStream via System.IO.Abstractions and couldn't see it initially. It's Hanging off the FileInfo Object. It results in slightly clunky code and required a cast. My Original Code:
FileStream fileStreamBack = null;
using (fileStreamBack = new FileStream(fileFrom, FileMode.Open, FileAccess.Read))
using (var fileStreamf = new FileStream(fileTo, FileMode.Create, FileAccess.Write))
{
fileStreamBack.CopyTo(fileStreamf); // Use the .Net
fileStreamBack.Flush(); // Making sure
fileStreamBack.Close(); // Making sure
}
Now Replaced with
FileStream fileStreamBack = null;
using (fileStreamBack = (FileStream)_fileSystem.FileInfo.FromFileName(fileFrom).Open(FileMode.Open, FileAccess.Read))
using (var fileStreamf = (FileStream)_fileSystem.FileInfo.FromFileName(fileTo).Open(FileMode.Create, FileAccess.Write))
{
fileStreamBack.CopyTo(fileStreamf); // Use the .Net
fileStreamBack.Flush(); // Making sure
fileStreamBack.Close(); // Making sure
}
There is also a MockFileStream object in the System.IO.Abstractions.TestingHelpers (for our convenience!)
Related
using declarations were just introduced in C# 8.0 but they don't behave the same as using blocks, or so i think.
The following nested using block works fine:
using (var resource = Assembly.GetExecutingAssembly().GetManifestResourceStream(serviceKey))
using (var file = new FileStream(path, FileMode.Create, FileAccess.Write))
{
resource?.CopyTo(file);
}
But when i convert to a using declaration as follows, i get an IOException which says the file is being used by another process:
using var resource = Assembly.GetExecutingAssembly().GetManifestResourceStream(serviceKey);
using var file = new FileStream(path, FileMode.Create, FileAccess.Write);
resource?.CopyTo(file);
I want to understand what's different and how\when to use the new using declaration?
Both using declaration differ in the way they resolve scope.
Old Using used to define its own scope using the curly braces,
using var resource = Assembly.GetExecutingAssembly().GetManifestResourceStream(serviceKey);
using (var file = new FileStream(path, FileMode.Create, FileAccess.Write))
{
resource?.CopyTo(file);
}
Here both resource and file will be disposed the moment the closing braces are found.
With, The new declaration if you haven,t defined a scope like the above, It will automatically attach to the nearest scope,
void certainMethod()
{
using var resource = Assembly.GetExecutingAssembly().GetManifestResourceStream(serviceKey);
using var file = new FileStream(path, FileMode.Create, FileAccess.Write);
resource?.CopyTo(file);
}
Here when the method call to certainMethod ends, Dispose for resource and file will be called.
Edit: To your case,
There shouln't be any issue if your code is doing just this, But if there are two of such blocks, First one will work but second will fail,
Example,
void certainMethod()
{
using var resource = Assembly.GetExecutingAssembly().GetManifestResourceStream(serviceKey);
using var file = new FileStream(path, FileMode.Create, FileAccess.Write);
resource?.CopyTo(file);
using var oneMoreFile = new FileStream(path, FileMode.Create, FileAccess.Write);
//This will fail
resource?.CopyTo(oneMoreFile );
}
in my Hololens app i want to write data into a file which i can then view over the Device Portal. The Data contains just the time from one airtap on a special object to another airtap.
The problem ist that there will be no file created in the Device Portal under /LocalAppData/YourApp/LocalState
Thanks in advance
Jonathan
public void StopTime()
{
TimeSpan ts = time.Elapsed;
time.Stop();
path = Path.Combine(Application.persistentDataPath, "Messdaten.txt");
using (TextWriter writer = File.CreateText(path))
{
writer.Write(ts);
}
}
I usually use a FileStream and a StreamWriter and make sure the FileMode.Create is set.
See also How to write text to a file for more approaches
using (var file = new FileStream(filePath, FileMode.Create, FileAccess.Write, FileShare.Write))
{
using (var writer = new StreamWriter(file, Encoding.UTF8))
{
writer.Write(content);
}
}
With that I never had any trouble on the HoloLens so far.
You also might want to use something like ts.ToString() in order to format the value to your needs.
Alternatively you could also try Unity's Windows.File (only available for UWP) but than you need to have byte[] as input. E.g. from here
long c = ts.Ticks;
byte[] d = BitConverter.GetBytes(c);
File.WriteAllBytes(path, d);
The simplest thing to do is to use File.WriteAllText method https://learn.microsoft.com/en-us/dotnet/api/system.io.file.writealltext?view=netframework-4.7.2
Obviously there are many ways that could work but it is good to stick to the simplest solution.
I have the method :
public static void Main()
{
string path = #"C:\Temp\ProgrammingInCSharp\DirectoryInfo\111.txt";
using (FileStream fileStream = File.Create(path))
{
using (BufferedStream bufferedStream = new BufferedStream(fileStream))
{
using (StreamWriter streamWriter = new StreamWriter(bufferedStream))
{
streamWriter.WriteLine("A line of text.");
}
}
}
}
Which is using 3 using statements and method works perfectly : create 111.txt and write "A line of text." inside it.
But when I change the method to :
public static void Main()
{
string path = #"C:\Temp\ProgrammingInCSharp\DirectoryInfo\111.txt";
FileStream fileStream = File.Create(path);
BufferedStream bufferedStream = new BufferedStream(fileStream);
StreamWriter streamWriter = new StreamWriter(bufferedStream);
streamWriter.WriteLine("A line of text.");
}
It just creates 111.txt file but doesn't write "A line of text." inside.
I cannot understand why.
As I read using Statement just call Dispose() method when the object leaves the scope of using Statement. So it should be used to dispose unmanaged code from CLR but why without using Statement I cannot write the text message to my machine's file?
There is caching going on in the background: WriteLine writes to a cache, not directly to the file. So if the file isn't closed properly, the cache doesn't actually get written to the disk. The using statement disposes the object, which flushes the cache into the file and closes the file.
You can see the source code for StreamWriter.Dispose() here (notice that it calls Flush()): https://referencesource.microsoft.com/#mscorlib/system/io/streamwriter.cs,236
Note that with multiple using statements, you only need one code block. The effect is the same, but it's just easier to read.
using (FileStream fileStream = File.Create(path))
using (BufferedStream bufferedStream = new BufferedStream(fileStream))
using (StreamWriter streamWriter = new StreamWriter(bufferedStream))
{
streamWriter.WriteLine("A line of text.");
}
I am reading a file using StreamReader fileReader = File.OpenText(filePath). I would like to modify one line in the file in memory and push the modified stream to another method.
What I would like to avoid is reading the whole file into a string and modifying the string (doesn't scale). I would also like to avoid modifying the actual file.
Is there a straightforward way of doing this?
There is no built-in way to do that in .Net framework.
Stream and StreamReader/StreamWriter classes are designed to be chained if necessary (like GZipStream wraps stream to compress it). So you can create wrapper StreamReader and update data as you need for every operation after calling wrapped reader.
You can open two stream -one for read, one for write- at the same time. I tested simple code that works, but not sure that's what you want:
// "2.bar\r\n" will be replaced by "!!!!!\r\n"
File.WriteAllText("test.txt",
#"1.foo
2.bar
3.fake");
// open inputStream for StreamReader, and open outputStream for StreamWriter
using (var inputStream = File.Open("test.txt", FileMode.Open, FileAccess.Read, FileShare.ReadWrite))
using (var reader = new StreamReader(inputStream))
using (var outputStream = File.Open("test.txt", FileMode.Open, FileAccess.Write, FileShare.Read))
using (var writer = new StreamWriter(outputStream))
{
var position = 0L; // track the reading position
var newLineLength = Environment.NewLine.Length;
while (!reader.EndOfStream)
{
var line = reader.ReadLine();
// your particular conditions here.
if (line.StartsWith("2."))
{
// seek line start position
outputStream.Seek(position, SeekOrigin.Begin);
// replace by something,
// but the length should be equal to original in this case.
writer.WriteLine(new String('!', line.Length));
}
position += line.Length + newLineLength;
}
}
/* as a result, test.txt will be:
1.foo
!!!!!
3.fake
*/
As you can see, both streams can be accessed by StreamReader and StreamWriter at the same time. And you can also manipulate both read/write position as well.
I have lots of code like this:
FileStream fs = File.Open(#"C:\Temp\SNB-RSS.xml", FileMode.Open);
using (XmlTextReader reader = new XmlTextReader(fs))
{
/* Some other code */
}
This gives me the following Code Analysis warning:
CA2000 : Microsoft.Reliability : In method 'SF_Tester.Run()', object 'fs' is not disposed along all exception paths. Call System.IDisposable.Dispose on object 'fs' before all references to it are out of scope.
If I follow the suggestion and I put the File.Open in a using statement, I get this:
CA2202 : Microsoft.Usage : Object 'fs' can be disposed more than once in method 'SF_Tester.Run()'. To avoid generating a System.ObjectDisposedException you should not call Dispose more than one time on an object.: Lines: 39
I'm using VS2010 and I can't help but think I'm doing something wrong but I don't see it.
What am I doing wrong?
Sigh, exhausting isn't it. Avoid all this by using the recommended Create() method:
using (var reader = XmlReader.Create(#"C:\Temp\SNB-RSS.xml")) {
//...
}
As nobody provided a solution that solves this issue yet, I'm writing my working solution down here:
FileStream fs = new FileStream(fileName, FileMode.Truncate, FileAccess.ReadWrite, FileShare.ReadWrite);
try
{
using (var fileWriter = new StreamWriter(fs, encoding))
{
fs = null;
fileWriter.Write(content);
}
}
finally
{
if (fs != null)
fs.Dispose();
}
This removes CA2000.
I am only guessing; don't have time to go through a full analysis now.
Suppose the XmlTextReader constructor 'takes ownership' of the stream passed in, and so disposing the XmlTextReader will also Dispose the underlying stream. That would explain the behavior you see. Perhaps XmlTextReader constructor can throw, and in that instance, the original warning about fs would make sense. However, given that hypothesis, this code
var fs = File.Open(#"C:\Temp\SNB-RSS.xml", FileMode.Open);
XmlTextReader reader = null;
try
{
reader = new XmlTextReader(fs);
}
finally
{
if (reader== null)
{
fs.Dispose();
}
}
if (reader != null)
{
using (reader)
{
/* Some other code */
}
}
is, I think, correct, but still yields a spurious warning. This smells like a nice example that demonstrates the limitations of static analysis tools.
As someone else said, there is another API to directly create the reader from the filename (XmlReader.Create()), which avoids all this (and shows how well-designed scenario-focused APIs are a good thing for a surprising variety of reasons).
It's a known issue
http://connect.microsoft.com/VisualStudio/feedback/details/535118/ca2000-and-ca2202-offer-contradictory-warnings
If you're using a StreamWriter rather than XmlTextReader (as in the solution above) you could use a similar method via the relevant constructor; e.g.
var sw = new StreamWriter("filename.txt");
or
var sw = new StreamWriter("filename.txt", /*append to file = */ false );
It is not clear from the documentation whether the first form of constructor will overwrite or append to a file.
As mentioned in this answer, the only way to work around it correctly is to do as recommended in CA2202 and use an outer try-finally block instead of an outer using block. Inside the inner using, set the outer IDisposable object to null to prevent it from being accessed once the inner using has finished.
Here's a generic wrapper that does it "correctly", i.e. works around the badly designed XmlReader (maybe it should not have taken ownership of the stream it receives? Not sure what the right way to do it would be)
Disclaimer: Not really tested
public static TResult SafeNestedUsing<TOuter, TInner, TResult>(Func<TOuter> createOuterDisposable, Func<TOuter, TInner> createInnerDisposable, Func<TInner, TResult> body)
where TInner : IDisposable
where TOuter : class, IDisposable
{
TOuter outer = null;
try
{
outer = createOuterDisposable();
using (var inner = createInnerDisposable(outer))
{
var result = body(inner);
outer = null;
return result;
}
}
finally
{
if (null != outer)
{
outer.Dispose();
}
}
}
Example usage:
SafeNestedUsing<MemoryStream, XmlReader, XmlDocument>(
() => new MemoryStream(array),
(memStream) => XmlReader.Create(memStream, xmlReaderSettings),
(xmlReader) =>
{
XmlDocument xmlDoc = new XmlDocument();
xmlDoc.Load(xmlReader);
return xmlDoc;
});
This is quite clunky, and you may argue that it's better to repeat the try/set null/finally pattern instead. But for a repeating pattern of nested usings I'd rather do it this way than repeat the full thing each time.
just use 'using' for the filestream
using(FileStream fs = new FileStream(fileName, FileMode.Truncate, FileAccess.ReadWrite, FileShare.ReadWrite))
{
// some codes here
}
Don't modify fs and don't use fs.close() inside using curly braces.
Use the using statement also on the FileStream itself just like on the XmlTextReader.
http://msdn.microsoft.com/en-us/library/system.io.filestream(VS.71).aspx.
Grz, Kris.