In This Chapter
• Manipulating Directories and Pathnames
• Handling Expressions for Directories and Pathnames
Manipulating files, directories, drives, and pathnames has always been one of the most common requirements for every role in IT for home users. Since MS-DOS, you have probably had this necessity tons of times. In application development for the .NET Framework with Visual Basic 2012, manipulating files is even more straightforward because you have two opportunities: accessing system resources with the usual ease due to self-explanatory classes and members and because of the Common Language Runtime (CLR) as a supervisor. In this chapter, you learn how to manipulate files, directories, and drives by using specific .NET classes. Moreover, you learn about transporting such simplicity into more general data exchange objects, known as streams.
The .NET Framework makes it easier to work with directories and pathnames, providing the System.IO.Directory
and System.IO.Path
classes. These classes offer shared methods for accessing directories and directory names, enabling deep manipulation of folders and names. To be honest, System.IO.Path
also provides members for working against filenames, and due to its nature, it is included in this section. In some situations, you need to work against single directories as instances of .NET objects, and this is where the System.IO.DirectoryInfo
class comes in. In this section, you learn to get the most from these classes for directory manipulation.
All the code examples provided in this chapter require an Imports System.IO
directive to shorten lines of code.
Often you need to work with pathnames, directory names, and filenames. The .NET Framework provides the System.IO.Path
class, offering shared members that enable you to manipulate pathnames. For example, you might want to extract the filename from a pathname. This is accomplished by invoking the GetFileName
method as follows:
'Returns "TextFile.txt"
Dim fileName As String = Path.GetFileName("C:TextFile.txt")
The GetExtension
method returns instead only the file extension and is used as follows:
'Returns ".txt"
Dim extension As String = Path.GetExtension("C:TextFile.txt")
To ensure that a filename has an extension, you can also invoke the HasExtension
method that returns True
if the filename has one. In some situations you need to extract the filename without considering its extension. The GetFileNameWithoutExtension
accomplishes this:
'Returns "TextFile"
Dim noExtension As String = Path.
GetFileNameWithoutExtension("C:TextFile.txt")
Another common situation is retrieving the directory name from a full pathname that includes a filename, too. The GetDirectoryName
method enables you to perform this:
'Returns "C:UsersAlessandroMy Documents"
Dim dirName As String =
Path. GetDirectoryName("C:UsersAlessandroMy DocumentsDocument.txt")
When working with filenames, you might want to replace the extension. This is accomplished by invoking the ChangeExtension
method as follows:
'Returns "MyFile.Doc"
Dim extReplaced As String = Path.ChangeExtension("MyFile.Txt", ".doc")
Notice that such a method returns a string that contains the required modification but does not rename the file on disk (which is covered later). Path is also useful when you need to create temporary files or to store files in the Windows temporary folder. You invoke the GetTempFileName
method to get a unique temporary file:
Dim temporaryFile As String = Path.GetTempFileName
The method returns the filename of the temporary file so that you can easily access it and treat it like any other file, being sure that its name is unique. If you instead need to access Windows temporary folder, you can invoke the GetTempPath
method as follows:
Dim temporaryWinFolder As String = Path.GetTempPath
You could combine both temporary folder name and temporary filename to create a temporary file in the temporary folder. Combining pathnames is accomplished by invoking the Combine
method, which takes two arguments such as the first pathname and the second one, or a parameter array containing strings representing pathnames. You can also generate a random filename invoking the GetRandomFileName
method:
Dim randomFile As String = Path.GetRandomFileName
The difference with GetTempFileName
is that this one also creates a physical file on disk, returning the full path. System.IO.Path
offers two other interesting methods: GetInvalidFileNameChars
and GetInvalidPathChars
. These both return an array of Char
storing unaccepted characters within filenames and within directory names, respectively. They are useful if you generate a string that will then be used as a filename or folder name.
To access directories, you use the System.IO.Directory
class that offers shared members that enable you to perform common operations on folders. All members should be self-explanatory. For example, you can check whether a directory already exists and, if not, create a new one as follows:
If Not Directory.Exists("C:Test") Then
Directory.CreateDirectory("C:Test")
End If
The Move
method enables you to move a directory from one location to another; it takes two arguments, the source directory name and the target name:
Directory.Move("C:Test", "C:Demo")
If the target directory already exists, an IOException
is thrown. You can easily get or set attribute information for directories by invoking special methods. For example, you can get the directory creation time by invoking the GetCreationTime
method that returns a Date
type or the SetCreationTime
that requires a date specification, to modify the creation time:
Dim createdDate As Date = Directory.GetCreationTime("C:Demo")
Directory.SetCreationTime("C:Demo", New Date(2009, 5, 10))
Table 18.1 summarizes members for getting/setting attributes.
You can easily get other information, such as the list of files available within the desired directory. You can get the list of files from a directory by invoking the GetFiles
method, returning an array of string (each one is a filename), which works like this:
'Second argument is optional, specifies a pattern for search
Dim filesArray() As String = Directory.GetFiles("C:", "*.exe")
As an alternative, you can invoke the EnumerateFiles
that returns an IEnumerable(Of String)
and that works like this:
'get files
Dim filesEnumerable As IEnumerable(Of String) = _
Directory.EnumerateFiles("C:", "*.exe")
For Each item In filesEnumerable
Console.WriteLine("File name: {0}", item)
Next
This difference probably does not make much sense at this particular point of the book, but you learn later that IEnumerable
objects are LINQ-enabled; therefore, you can write LINQ queries against sequences of this type. Similarly to GetFiles
and EnumerateFiles
, you invoke GetDirectories
and EnumerateDirectories
to retrieve a list of all subdirectories’ names within the specified directory. Next, the GetFiles
and EnumerateFiles
return a list of all filenames and subdirectory names within the specified directory. Actually there is another difference between GetXXX
and EnumerateXXX
methods, which is about performance: The latter starts enumerating as the Framework is still gathering files/directories, making things faster and more efficient.
To delete a directory, you invoke the Delete
method. It works only if a directory is empty and requires the directory name:
'Must be empty
Directory.Delete("C:Demo")
If the folder is not empty, an IOException
is thrown. Delete
has an overload that accepts a Boolean value if you want to delete empty subdirectories, too. The Directory
class also provides the capability of retrieving a list of available drives on your machine. This is accomplished by invoking the GetLogicalDrives
method that returns an array of String
, which you can then iterate:
Dim drivesOnMyMachine() As String = Directory.
GetLogicalDrives
For Each drive In drivesOnMyMachine
Console.WriteLine(drive)
Next
On my machine the preceding code produces the output shown in Figure 18.1.
The last example is about retrieving the current directory, which is accomplished by invoking the GetCurrentDirectory
method:
Dim currentFolder As String = Directory.GetCurrentDirectory
You can also set the current folder by invoking the shared SetCurrentDirectory
method, passing the folder name as an argument. Accessing directories via the Directory
class is straightforward, but in some circumstances you have no access to specific information. For this, a more flexible class that enables you to work on specific directories is DirectoryInfo
.
The System.IO.DirectoryInfo
class represents a single directory. More precisely, an instance of the DirectoryInfo
class handles information about the specified directory. It inherits from System.IO.FileSystemInfo
, which is a base class that provides the basic infrastructure for representing directories or files. You create an instance of the DirectoryInfo
class by passing the desired directory name as an argument to the constructor:
Dim di As New DirectoryInfo("C:Demo")
You have the same members that you already learned about for the Directory
class, with some differences. First, now members are instance members and not shared. Second, methods summarized in Table 18.1 are now properties. Third, members are invoked directly on the instance that represents the directory; therefore, you do not need to pass the directory name as an argument. For example, you remove an empty directory by invoking the instance method Delete
as follows:
di.Delete()
An interesting property is DirectoryInfo.Attributes
, which enables you to specify values from the System.IO.FileAttributes
enumeration and that determine directory behavior. For example, you can make a directory hidden and read-only setting bitwise flags as follows:
di.Attributes = FileAttributes.Hidden Or FileAttributes.ReadOnly
When you specify values from such enumeration, IntelliSense can help you understand what the value is about; it’s worth mentioning that such values are self-explanatory. Sometimes you do not programmatically create instances of DirectoryInfo
; instead you receive an instance from some other objects. For example, the Directory.CreateDirectory
shared method returns a DirectoryInfo
object. In such cases, you can get further information as the directory name invoking the FullName
property, which returns the full pathname of the folder, or the Name
property that just returns the name without path. Both work like this:
Dim directoryFullName As String = di.FullName
Dim directoryName As String = di.Name
Use the DirectoryInfo
class each time you need to store information for specific directories—for example, within collections.
Similarly to System.IO.DirectoryInfo
, System.IO.DriveInfo
provides access to drives information. Using this class is straightforward; it provides information on the disk type, disk space (free and total), volume label, and other self-explanatory properties that you can discover with IntelliSense. The following example shows how you can create an instance of the class and retrieve information on the specified drive:
Sub DriveInfoDemo()
Dim dr As New DriveInfo("C:")
Console.WriteLine("Drive type: {0}", dr.DriveType.ToString)
Console.WriteLine("Volume label: {0}", dr.VolumeLabel)
Console.WriteLine("Total disk space: {0}", dr.TotalSize.ToString)
Console.WriteLine("Available space: {0}",
dr.AvailableFreeSpace.ToString)
dr = Nothing
End Sub
When working with directories and pathnames, encountering exceptions is not so uncommon. Table 18.2 summarizes directory-related exceptions.
It is important to implement Try..Catch
blocks for handling the previously described exceptions and provide the user the ability to escape from such situations.
Manipulating files is a daily task for every developer. Luckily, the .NET Framework provides an easy infrastructure for working with files. In this section, you learn about the System.IO.File
and System.IO.FileInfo
classes that also represent some important concepts before you go into streams.
The System.IO.File
class provides access to files on disk exposing special shared members. For example, you can easily create a text file invoking two methods: WriteAllText
and WriteAllLines
. Both create a new text file, put into the file the given text, and then close the file; however, the second one enables you to write the content of an array of strings into multiple lines. The following code provides an example:
File.WriteAllText("C:TempOneFile.txt", "Test message")
Dim lines() As String = {"First", "Second", "Third"}
File.WriteAllLines("C:TempOneFile.txt", lines)
Such methods are useful because they avoid the need to manually close files on disk when you perform the writing operation. You can also easily create binary files by invoking the WriteAllBytes
method that works like the previous ones but requires the specification of an array of byte instead of text. The following is a small example:
File.WriteAllBytes("C:TempOneFile.bin", New Byte() {1, 2, 3, 4})
Reading files’ content is also easy. There are reading counterparts of the previously described method. ReadAllText
and ReadAllLines
enable you to retrieve content from a text file; the first one returns all content as a String
, whereas the second one returns the content line-by-line by putting in an array of String
. This is an example:
Dim text As String = File.ReadAllText("C:TempOneFile.txt")
Dim fileLines() As String = File.ReadAllLines("C:TempOneFile.txt")
Similarly you can read data from binary files invoking ReadAllBytes
, which returns an array of Byte
, as follows:
Dim bytes() As Byte = File.ReadAllBytes("C:TempOneFile.bin")
For text files, you can also append text to an existing file. You accomplish this by invoking AppendAllText
if you want to put an entire string or AppendAllLines
if you have a sequence of strings. This is an example:
Dim lines As IEnumerable(Of String) = _
New String() {"First", "Second", "Third"}.AsEnumerable
File.AppendAllLines("C:TemporaryTest.txt", lines)
File.AppendAllText("C:TemporaryText.txt",
"All text is stored within a string")
Notice how an array of strings is converted into an IEnumerable(Of String)
invoking the AsEnumerable
extension method, which is discussed in Chapters 20, “Advanced Language Features,” and 23, “LINQ to Objects.” AppendAllLines
takes an IEnumerable(Of String)
as a parameter, but you can also pass an array of strings because arrays are actually enumerable. After reading and writing, copying is also important. The Copy
method enables you to create copies of files, accepting two arguments: the source file and the target file. This is an example:
File.Copy("C:OneFolderSource.txt", "C:AnotherFolderTarget.txt")
You can also move a file from a location to another by invoking Move
. Such a method is also used to rename a file and can be used as follows:
File.Move("C:OneFolderSource.txt", "C:AnotherFolderTarget.txt")
Another useful method is Replace
. It enables you to replace the content of a file with the content of another file, making a backup of the first file. You use it as follows:
File.Replace("C:Source.Txt", "C:Target.txt", "C:Backup.txt")
You are not limited to text files. The File
class offers two important methods that provide a basic encryption service, Encrypt
and Decrypt
. Encrypt
makes a file accessible only by the user who is currently logged into Windows. You invoke it as follows:
File.Encrypt("C:TempOneFile.txt")
If you try to log off from the system and then log on with another user profile, the encrypted file will not be accessible. You need to log on again with the user profile that encrypted the file. To reverse the result, invoke Decrypt
:
File.Decrypt("C:TempOneFile.txt")
Finally, you can easily delete a file from disk. This is accomplished with the simple Delete
method:
File.Delete("C:TempOneFile.txt")
The File
class also has members similar to the Directory
class. Consider the summarization made in Table 18.1 about the Directory
class’s members. The File
class exposes the same members with the same meaning; the only difference is that such members now affect files. Those members are not covered again because they behave the same on files.
Members Returning Streams
The System.IO.File
class exposes methods that return or require streams, such as Create
and Open
. Such members are not covered here for two reasons: The first one is that the streams discussion will be offered later in this chapter; the second one is that streams provide their own members for working against files that do the same as file members and therefore a more appropriate discussion is related to streams.
Similarly to what I explained about the System.IO.DirectoryInfo
class, there is also a System.IO.FileInfo
counterpart for the System.IO.File
class. An instance of the FileInfo
class is therefore a representation of a single file, providing members that enable you to perform operations on that particular file or get/set information. Because FileInfo
inherits from System.IO.FileSystemInfo
like DirectoryInfo
, you can find the same members. You create an instance of the FileInfo
class by passing the filename to the constructor, as demonstrated here:
Dim fi As New FileInfo("C:MyFile.txt")
You can set attributes for the specified file assigning the Attributes
property, which receives a value from the System.IO.FileAttributes
enumeration:
fi.Attributes = FileAttributes.System Or FileAttributes.Hidden
You can still perform operations by invoking instance members that do not require the filename specification, such as CopyTo
, Delete
, Encrypt
, Decrypt
, or MoveTo
: the FileInfo
, such as Length
(of type Long
) that returns the file size in bytes; Name
(of type String
) that returns the filename and that is useful when you receive a FileInfo
instance from somewhere else; FullName
that is the same as Name
but also includes the full path; Exists
that determines if the file exists; and IsReadOnly
that determines if the file is read-only. Using FileInfo
can be useful if you need to create collections of objects, each representing a file on disk. Consider the following custom collections that stores series of FileInfo
objects:
Class MyFileList
Inherits List(Of FileInfo)
End Class
Now consider the following code that retrieves the list of executable filenames in the specified folder and creates an instance of the FileInfo
class for each file, pushing it into the collection:
Module FileInfoDemo
Sub FileInfoDemo()
'An instance of the collection
Dim customList As New MyFileList
'Create a FileInfo for each .exe file
'in the specified directory
For Each itemName As String In _
Directory.EnumerateFiles("C:", "*.exe")
Dim fileReference As New FileInfo(itemName)
customList.Add(fileReference)
Next
'Iterate the collection
For Each item In customList
Console.WriteLine("File: {0}, length: {1}, created on: {2}",
item.Name, item.Length, item.CreationTime)
Next
End Sub
End Module
In this particular case enclosing the code within a module is just for demonstration purposes. Notice how you can access properties for each file that you could not know in advance. Just like DirectoryInfo
, FileInfo
also exposes properties that are counterparts for methods summarized in Table 18.1 and that this time are related to files. Refer to that table for further information.
Refer to Table 18.2 for exceptions that can occur when working with files. Other than those exceptions, you may encounter a FileNotFoundException
if the specified file does not exist.
The .NET Framework provides a high-level security mechanism over system resources, so it can happen that you attempt to access, in both reading or writing, directories or files but you do not have the required rights. To prevent your code from failing at runtime, you can check whether you have permissions. When working with files and directories, you need to check the availability of the System.Security.FileIOPermission
object. For example, the following code asks the system (Demand
method) if it has permissions to read local files:
Dim fp As New FileIOPermission(PermissionState.None)
fp.AllLocalFiles = FileIOPermissionAccess.Read
Try
fp.Demand()
Catch ex As Security.SecurityException
Console.WriteLine("You have no permission for local files")
Catch ex As Exception
End Try
If your code has no sufficient permissions, a SecurityException
is thrown. Checking for permission is absolutely a best practice and should be applied where possible. In Chapter 45, “Working with Assemblies,” you get some more information about the security model in the .NET Framework.
Streams are sequences of bytes exchanged with some kind of sources, such as files, memory, and network. A stream is represented by the abstract System.IO.Stream
class that is the base class for different kinds of streams and that implements the IDisposable
interface. The Stream
class exposes some common members that you find in all other streams. Table 18.3 summarizes the most important common members.
Now that you have a summarization of common members, you are ready to discover specific kinds of streams that inherit from Stream
.
You create text files by instantiating the StreamWriter
class, which is a specific stream implementation for writing to text files. The following code, which will be explained, provides an example:
Dim ts As New StreamWriter("C:TemporaryOneFile.txt",
False, System.Text.Encoding.UTF8)
ts.WriteLine("This is a text file")
ts.WriteLine("with multi-line example")
ts.Close()
The constructor provides several overloads; the one used in the code receives the file name to be created—a Boolean value indicated whether the text must be appended if the file already exists and how the text is encoded. WriteLine
is a method that writes a string and then puts a line terminator character. When you are done, you must close the stream invoking Close
. You can also invoke Write
to put in just one character. The reading counterpart is the StreamReader
that works in a similar way, as demonstrated here:
Dim rf As New StreamReader("C:TemporaryOneFile.txt",
System.Text.Encoding.UTF8)
Dim readALine As String = rf.ReadLine
Dim allContent As String = rf.ReadToEnd
rf.Close()
StreamReader
provides the ability to read one line (ReadLine
method), one character per time (Read
method), or all the content of the stream (ReadToEnd
method) putting such content into a variable of type String
. In both StreamWriter
and StreamReader
, the constructor can receive an existing stream instead of a string. This is exemplified by the following code:
Dim fs As New FileStream("C:TemporaryOneFile.txt", FileMode.Create)
Dim ts As New StreamWriter(fs)
'Work on your file here..
ts.Close()
fs.Close()
First, you need an instance of the FileStream
class, which enables you to open a communication with the specified file and with the mode specified by a value of the FileMode
enumeration (such as Create
, Append
, CreateNew
, Open
, and OpenOrTruncate
). This class provides support for both synchronous and asynchronous operations. Then you point to the FileStream
instance in the constructor of the StreamWriter
/StreamReader
class. Remember to close both streams when you are done.
You can read and write data to binary files using the BinaryReader
and BinaryWriter
classes. Both require a FileStream
instance and enable you to read and write arrays of bytes. The following is an example of creating a binary stream:
Dim fs As New FileStream("C:TemporaryOneFile.bin", FileMode.CreateNew)
Dim bs As New BinaryWriter(fs)
Dim bytesToWrite() As Byte = New Byte() {128, 64, 32, 16}
bs.Write(bytesToWrite)
bs.Close()
fs.Close()
The Write
method enables you to write information as binary, but it also accepts base .NET types such as integers and strings, all written as binary. It provides several overloads so that you can also specify the offset and the number of bytes to be written. To read a binary file, you instantiate the BinaryReader
class. The following example retrieves information from a file utilizing a Using..End Using
block to ensure that resources are correctly freed up when no longer necessary:
fs = New FileStream("C:TemporaryOneFile.bin", FileMode.Open)
Using br As New BinaryReader(fs)
If fs IsNot Nothing AndAlso fs.Length > 0 Then
Dim buffer() As Byte = br.ReadBytes(CInt(fs.Length))
End If
End Using
fs.Close()
In this case the ReadBytes
method, which is used to retrieve data, reads a number of bytes corresponding to the file length. Because binary data can have different forms, ReadBytes
is just one of a series of methods for reading .NET types such as ReadChar
, ReadInt32
, ReadString
, ReadDouble
, and so on.
Asynchronous Programming
The .NET Framework 4.5 introduces a new pattern for asynchronous programming techniques based on the Async/Await keywords. This pattern has some new features for working with streams asynchronously as well. Because you first need to know how this pattern works, examples on asynchronous operations over streams will be provided in Chapter 44, “Asynchronous Programming.”
Memory streams are special objects that act like file streams but that work in memory, providing the ability to manipulate binary data. The following code creates a MemoryStream
with 2 Kbytes capacity and puts in a string:
Dim ms As New MemoryStream(2048)
Dim bs As New BinaryWriter(ms)
bs.Write("Some text written as binary")
bs.Close()
ms.Close()
To retrieve data, you use a BinaryReader
pointing to the MemoryStream
as you saw in the paragraph for binary files. So, in this example, you can invoke ReadString
as follows:
'The stream must be still open
Using br As New BinaryReader(ms)
If ms IsNot Nothing AndAlso ms.Length > 0 Then
Dim data As String = br.ReadString
End If
End Using
ms.Close()
Although not often utilized, you can use StringReader
and StringWriter
for manipulating strings. The following example generates a new StringBuilder
and associates it to a new StringWriter
. Then it retrieves the list of filenames in the C: directory and puts each string into the writer. You notice that because of the association between the two objects, changes are reflected to the StringBuilder
. Try this:
Dim sBuilder As New Text.StringBuilder
Dim sWriter As New StringWriter(sBuilder)
For Each name As String In Directory.GetFiles("C:")
sWriter.WriteLine(name)
Next
sWriter.Close()
Console.WriteLine(sBuilder.ToString)
To read strings, you can use the StringReader
object, whose constructor requires a string to be read. To continue with the example, we can read the previously created StringBuilder
line-by-line:
Dim sReader As New StringReader(sBuilder.ToString)
Do Until sReader.Peek = -1
Console.WriteLine(sReader.ReadLine)
Loop
You notice lots of similarities between string streams and StreamWriter
/StreamReader
because both work with text.
One of the most interesting features of streams is the ability to compress and decompress data utilizing the GZipStream
and DeflateStream
objects. Both are exposed by the System.IO.Compression
namespace, and they both compress data using the GZip algorithm. The only difference is that the GZipStream
writes a small header to compressed data. The interesting thing is that they work similarly to other streams, and when you write or read data into the stream, data is automatically compressed or decompressed by the runtime. The good news is that you are not limited to compressing files, but any other kind of stream. Compressing and decompressing data is quite a simple task. In some situations you need more attention according to the kind of data you need to access for files. To make comprehension easier, take a look at the code example provided in Listing 18.1 that contains comments that explain how such streams work. The purpose of the example is to provide the ability to compress and decompress files that is a common requirement in applications. You are encouraged to read comments within the code that can help you get started with the GZipStream
.
Imports System.IO
Imports System.IO.Compression
Module Compression
Sub TestCompress()
Try
Compress("C:TempSource.Txt",
"C:TempCompressed.gzp")
Catch ex As FileNotFoundException
Console.WriteLine("File not found!")
Catch ex As IOException
Console.WriteLine("An input/output error has occurred:")
Console.WriteLine(ex.Message)
Catch ex As Exception
Console.WriteLine(ex.Message)
End Try
End Sub
Sub TestDecompress()
Try
Decompress("C:TempCompressed.gzp",
"C:TempOriginal.txt")
Catch ex As FileNotFoundException
Console.WriteLine("File not found!")
Catch ex As IOException
Console.WriteLine("An input/output error has occurred:")
Console.WriteLine(ex.Message)
Catch ex As Exception
Console.WriteLine(ex.Message)
End Try
End Sub
Public Sub Compress(ByVal inputName As String, ByVal outputName As String)
'Instantiates a new FileStream
Dim infile As FileStream
Try
'The Stream points to the specified input file
infile = New FileStream(inputName, FileMode.Open, FileAccess.Read,
FileShare.Read)
'Stores the file length in a buffer
Dim buffer(CInt(infile.Length - 1)) As Byte
'Checks if the file can be read and assigns to the "count"
'variable the result of reading the file
Dim count As Integer = infile.Read(buffer, 0, buffer.Length)
'If the number of read byte is different from the file length
'throws an exception
If count <> buffer.Length Then
infile.Close()
Throw New IOException
End If
'closes the stream
infile.Close()
infile = Nothing
'Creates a new stream pointing to the output file
Dim ms As New FileStream(outputName, FileMode.CreateNew,
FileAccess.Write)
'Creates a new GZipStream for compressing, pointing to
'the output stream above leaving it open
Dim compressedzipStream As New GZipStream(ms,
CompressionMode.Compress,
True)
'Puts the buffer into the new stream, which is
'automatically compressed
compressedzipStream.Write(buffer, 0, buffer.Length)
compressedzipStream.Close()
ms.Close()
Exit Sub
Catch ex As IO.FileNotFoundException
Throw
Catch ex As IOException
Throw
Catch ex As Exception
Throw
End Try
End Sub
Public Sub Decompress(ByVal fileName As String, ByVal originalName As String)
Dim inputFile As FileStream
'Defining the stream for decompression
Dim compressedZipStream As GZipStream
'Defining a variable for storing compressed file size
Dim compressedFileSize As Integer
Try
'Reads the input file
inputFile = New FileStream(fileName,
FileMode.Open,
FileAccess.Read,
FileShare.Read)
'Reads input file's size
compressedFileSize = CInt(inputFile.Length)
'Creates a new GZipStream in Decompress mode
compressedZipStream = New GZipStream(inputFile,
CompressionMode.Decompress)
'In compressed data the first 100 bytes store the original
'data size, so let's get it
Dim offset As Integer = 0
Dim totalBytes As Integer = 0
Dim SmallBuffer(100) As Byte
'Reads until there are available bytes in the first 100
'and increments variables that we'll need for sizing
'the buffer that will store the decompressed file
Do While True
Dim bytesRead As Integer = compressedZipStream.
Read(SmallBuffer, 0, 100)
If bytesRead = 0 Then
Exit Do
End If
offset += bytesRead
totalBytes += bytesRead
Loop
compressedZipStream.Close()
compressedZipStream = Nothing
'Creates a new FileStream for reading the input file
inputFile = New FileStream(fileName,
FileMode.Open,
FileAccess.Read,
FileShare.Read)
'and decompress its content
compressedZipStream = New GZipStream(inputFile,
CompressionMode.Decompress)
'Declares the buffer that will store uncompressed data
Dim buffer(totalBytes) As Byte
'Reads from the source file the number of bytes
'representing the buffer length, taking advantage
'of the original size
compressedZipStream.Read(buffer, 0, totalBytes)
compressedZipStream.Close()
compressedZipStream = Nothing
'Creates a new file for putting uncompressed
'data
Dim ms As New FileStream(originalName,
FileMode.Create,
FileAccess.Write)
'Writes uncompressed data to file
ms.Write(buffer, 0, buffer.Length)
ms.Close()
ms = Nothing
Exit Sub
'General IO error
Catch ex As IOException
Throw
Catch ex As Exception
Throw
Exit Try
End Try
End Sub
End Module
Tip
You use GZipStream
and DeflateStream
the identical way. The only difference is about the header in the compressed stream. If you need further information on the difference, here is the official MSDN page: http://msdn.microsoft.com/en-us/library/system.io.compression.deflatestream(VS.110).aspx.
Notice how, at a higher level, you just instantiate the stream the same way in both compression and decompression tasks. The difference is the CompressionMode
enumeration value that determines whether a stream is for compression or decompression. With this technique, you can invoke just the two custom methods for compressing and decompressing files, meaning that you could apply it to other kinds of data, too.
The .NET Framework 4.5 introduces new features to the System.IO.Compression
namespace, which provide an opportunity of working with classic Zip archives. Until this version of the Framework, it was not possible to work directly with the famous Zip algorithm because only the GZip one was supported. Technically speaking, in .NET 4.5 applications you add a reference to the System.IO.Compression.FileSystem.dll assembly. This extends the System.IO.Compression
namespace with new types that will be described shortly. The real thing you have to take care about is that the new features offer basic ways for working with Zip archives and so there could be some situations in which you might need more advanced libraries, such as the famous DotNetZip (http://dotnetzip.codeplex.com) or SharpZipLib (http://sharpziplib.com) available for free on the Internet. For example, currently the new classes do not enable you to enter passwords when extracting archives or specify one to protect archives. Also, full compatibility is not always guaranteed. Notice that these new types are not available for Metro-style apps in Windows 8, where you will still use streams described previously. In the .NET Framework 4.5, you can now use the ZipFile
class, which exposes two shared methods, CreateFromDirectory
and ExtractToDirectory
. The first method creates a Zip archive containing all files in the specified directory, whereas the second one extracts the content of a Zip archive into the specified directory. The following example demonstrates how to create and extract a Zip archive:
ZipFile.CreateFromDirectory("C: emp", "C:ippedTemp.zip",
CompressionLevel.Optimal, True)
ZipFile.ExtractToDirectory("C:ippedDemo.zip", "C: emp")
In the previous example, CreateFromDirectory
generates a compressed archive called ZippedTemp.zip, which stores the content of a folder called C:Temp. The third argument (called compressionLevel
) represents the compression level and requires one value from the CompressionLevel
enumeration. You choose among Optimal
, NoCompression
, and Fastest
. The fourth argument (called includeBaseDirectory
) is instead a Boolean value that, when True
, enables you to include the base directory in the archive. Using ExtractToDirectory
is even simpler because you just specify the archive to decompress and the target directory. Both methods have an overload that provides an argument called entryNameEncoding
of type System.Text.Encoding
which enables you to specify the encoding to use with file entries for compatibility with Zip archives that do not support UTF-8. The System.IO.Compression namespace also offers the ZipArchive
and ZipArchiveEntry
classes, representing a Zip archive and one entry inside the compressed archive, respectively. These classes provide better control over Zip files. The following code demonstrates how to show the list of files inside a Zip archive:
Using zipArc As ZipArchive = ZipFile.Open("C: empNorthwind.zip", ZipArchiveMode.Read)
For Each item As ZipArchiveEntry In zipArc.Entries
Console.WriteLine("{0}, Compressed size: {1}, ",
item.FullName,
item.CompressedLength.ToString)
Next
End Using
Because the ZipArchive
class implements the IDisposable
interface, it can be convenient to enclose the code within a Using..End Using
block. Notice that the ZipArchive
instance receives the result of another method from the ZipFile
class, Open
. Every item in the archive is an instance of the ZipArchiveEntry
class; a collection called Entries
, of type ReadOnlyCollection(Of ZipArchiveEntry)
, can be iterated to get the list of items that are stored inside the Zip archive. It is also easy to create a new Zip file with ZipArchive
and ZipFile.Open
. The following code demonstrates this:
Using zipArc As ZipArchive = ZipFile.Open("C:TempNewZipped.zip",
ZipArchiveMode.Create)
zipArc.CreateEntry("C:TempNorthwind.sdf", CompressionLevel.NoCompression)
End Using
Here you use the ZipArchiveMode.Create
mode for opening the Zip file, since you want to create a new one. Then you use the CreateEntry
instance method of the ZipArchive
class to add entries to the zipped archive. Notice that the entry is actually added to the archive when the Using
block gets finalized. You can also update an existing archive by adding or deleting entries. The following code demonstrates how to add an entry to an existing archive and how to remove an existing entry:
Using zipArc As ZipArchive = ZipFile.Open("C:TempNewZipped.zip",
ZipArchiveMode.Update)
zipArc.CreateEntry("C:TempAnotherFile.txt", CompressionLevel.Fastest)
'Get the specified entry
Dim entry As ZipArchiveEntry = zipArc.GetEntry("Northwind.sdf")
'Delete the entry from the archive
entry.Delete()
End Using
You use GetEntry
to get the instance of the ZipArchiveEntry
that you want to remove, by specifying the name. Then you invoke the Delete
method on the retrieved instance.
The .NET Framework provides functionalities for data exchange through networks using streams; in particular, it exposes the System.Net.Sockets.NetworkStream
class. Reading and writing data via a NetworkStream
instance passes through a System.Net.Sockets.TcpClient
class’s instance. Code in Listing 18.2 shows how you can both write and read data in such a scenario. See comments in code for explanations.
Imports System.Net.Sockets
Imports System.Text
Module Network
Sub NetStreamDemo()
'Instantiating TcpClient and NetworkStream
Dim customTcpClient As New TcpClient()
Dim customNetworkStream As NetworkStream
Try
'Attempt to connect to socket
'127.0.0.1 is the local machine address
customTcpClient.Connect("127.0.0.1", 587) 'Port
'Gets the instance of the stream for
'data exchange
customNetworkStream = customTcpClient.GetStream()
'The port is not available
Catch ex As ArgumentOutOfRangeException
Console.WriteLine(ex.Message)
'Connection problem
Catch ex As SocketException
Console.WriteLine(ex.Message)
End Try
'Gets an array of byte from a value, which is
'encoded via System.Text.Encoding.Ascii.GetBytes
Dim bytesToWrite() As Byte = _
Encoding.ASCII.GetBytes("Something to exchange via TCP")
'Gets the stream instance
customNetworkStream = customTcpClient.GetStream()
'Writes the bytes to the stream; this
'means sending data to the network
customNetworkStream.Write(bytesToWrite, 0,
bytesToWrite.Length)
'Establishes the buffer size for receiving data
Dim bufferSize As Integer = customTcpClient.
ReceiveBufferSize
Dim bufferForReceivedBytes(bufferSize) As Byte
'Gets data from the stream, meaning by the network
customNetworkStream.Read(bufferForReceivedBytes, 0,
bufferSize)
Dim result As String = Encoding.ASCII.GetString(bufferForReceivedBytes,
0, bufferSize)
End Sub
End Module
There are several ways for data exchange, and this is probably one of the most basic ones in the era of Windows Communication Foundation. This topic is related to streams, so an overview was necessary.
Working with files, directories, and drives is a common requirement for each application. The .NET Framework provides two main classes for working with directories: System.IO.Directory
and System.IO.Path
. The first one enables you to perform operations such as creating, moving, renaming, and investigating for filenames. The second one is about directory and filename manipulation other than gaining access to Windows temporary folder. Similar to Directory
, the System.IO.DirectoryInfo
class provides access to directory operations and information, but the difference is that an instance of such a class represents a single directory. If you instead need to get information on physical drives on your machine, create an instance of the System.IO.DriveInfo
class. Similar to Directory
and DirectoryInfo
, the System.IO.File
and System.IO.FileInfo
classes provide access to files on disk, and their members are the same as for directory classes except that they enable you to work with files. The last part of the chapter is about streams. Streams are sequences of bytes that enable you to exchange different kinds of data. All stream classes inherit from System.IO.Stream
. StreamReader
and StreamWriter
enable you to read and write text files. BinaryReader
and BinaryWriter
enable you to read and write binary files. MemoryStream
enables you to read and write in-memory binary data. StringReader
and StringWriter
enable you to manage in-memory strings. GZipStream
and DeflateStream
enable you to compress data according to the GZip
algorithm (you saw new features in .NET 4.5 about Zip compression). NetworkStream
enables you to exchange data through a network. Writing and reading data can often require several lines of code. Luckily the Visual Basic language offers an important alternative known as the My
namespace that is covered in the next chapter.