Agilent Developer Network - White Paper

Agilent Developer Network - White Paper

Agilent Developer Network White Paper Storing Test Data to File Using Microsoft® Visual Basic® .NET December 2002 Keith Hill Agilent Technologies Summary: This white paper introduces the .NET Framework Class Library’s file I/O functionality that is available to Microsoft® Visual Basic .NET programmers. Five examples are presented that demonstrate various ways to save and retrieve test data from a file. In addition, examples are presented to demonstrate reading application configuration files, file and directory manipulation and monitoring files. Contents Introduction Saving Test Data Using Comma Separated Value Text Format Saving Test Data Using Name Value Pair Text Format Saving Data Using BinaryWriter Saving Data Using Binary Serialization Saving Data Using XML Serialization Reading Application Configuration Settings File and Directory Operations Monitoring Files and Directories with FileSystemWatcher Conclusions Introduction Microsoft Visual Basic .NET (VB.NET) is a significant new release and a worthy successor to Visual Basic 6.0.

VB.NET introduces language advances, a new runtime engine and a significantly more powerful runtime library. The language changes introduce full support for object-oriented programming and structured exception handling. The new runtime engine, called the Common Language Runtime (CLR) provides better memory management and support for multiple threads. The .NET Framework Class Library (FCL) provides significantly more functionality than the VB 6 runtime library. In particular, the FCL provides more options for storing test data to file and for manipulating files and directories in general. Before exploring the new file I/O functionality in the FCL, it is worth pointing out that the VB 6 approach to file I/O using VB 6 statements like Open, Print, Input, Close is still available in VB.NET albeit with some syntax changes.

For example, in VB 6 you would open a file, read from it and close it with the code shown in Figure 1.

Agilent Developer Network White Paper 2 of 17 Storing Test Data to File Using Microsoft® Visual Basic® .NET November 2002 Figure 1 Reading a File using VB 6 Dim reading As Double Open "C:\Temp\TestData.txt" For Input As #1 Input #1, reading Close #1 In VB.NET, this code changes slightly as shown in Figure 2. Figure 2 Reading a File using VB 6 Compatibility Functions in VB.NET Dim reading As Double FileOpen(1, "C:\Temp\TestData.txt", OpenMode.Input) Input(1, reading) FileClose(1) The VB 6 file I/O statements have been replaced by a set of compatibility functions including FileOpen, Print, Input and FileClose among others.

For more information see File Access Types, Functions, and Statements. Despite the syntax changes, performing file I/O with these functions is basically the same as it was in VB 6. You can continue to use that approach in VB.NET if you desire. Keep in mind that the VB 6 compatibility methods mentioned above are merely wrappers on top of the FCL’s native file I/O functionality. Besides just bypassing an extra layer of code, learning how to directly use the FCL’s file I/O objects and methods gives you more flexibility, an easier mechanism for saving test data and allows you to read file I/O code in other .NET languages like C#.

The rest of this article explores those native FCL file I/O objects and methods.

The 1.0 version of the FCL contains more than 3000 public types that contribute over 16000 public properties, 33000 public methods and 4000 public events (I told you it contained significantly more functionality than the VB 6 runtime library). Fortunately this functionality is organized into many different namespaces like System, System.Collection, System.Net, System.Text, System.Threading, System.Windows.Forms just to name a few. The file I/O functionality is located in the System.IO namespace. There are a number of file I/O examples used throughout the rest of this article. All of the examples dealing with test data use the data format shown in Figure 3 to illustrate the different ways to save and retrieve test data.

Figure 3 Test Data Elements Name Data Type Description dutId String Device under test identifier timestamp DateTime Time data was acquired stationId Integer Test station identifier data Double() 3 test data values The example code associated with this paper can be downloaded from http://www.agilent.com/find/adnwhitepaperexamples. Visit the Agilent Developer Network at http://www.agilent.com/find/ADN

Agilent Developer Network White Paper 3 of 17 Storing Test Data to File Using Microsoft® Visual Basic® .NET November 2002 Saving Test Data Using Comma Separated Value Text Format One very popular text file format is “comma separated value” or CSV. CSV files can be easily imported by applications like Microsoft Excel. Figure 4 shows how to save data in a text file using CSV format. Figure 4 Writing Data to a CSV Text File ' Example 1a in the sample project ' Create test data Dim dutId As String = "A00100" Dim timestamp As DateTime = DateTime.Now Dim stationId As Integer = 1 Dim data() As Double = New Double() {1.1, 2.2, 3.3} ' Open file for writing text Dim writer As StreamWriter = File.CreateText(filename) ' Write data in csv (comma separated value) format: ' DutIdentifier,Timestamp,StationID,DataUpperBound,Data() writer.Write("{0},{1},{2},{3}", dutId, timestamp, _ stationId, UBound(data)) Dim val As Double For Each val In data writer.Write & val) Next ' Close StreamWriter when done writer.Close() The following line of code from Figure 4 demonstrates the use of the System.IO.File class to open up a file for writing in text mode.

Dim writer As StreamWriter = File.CreateText(filename) One interesting tidbit about the File.CreateText method is that it uses something called UTF-8 encoding for the text written to file. The advantage to UTF-8 encoding is that ASCII characters require only one byte per character in the file. At the same time, Unicode characters can be saved although these characters may occupy two or more bytes per character in the file. There are ways to force exclusive ASCII or Unicode encoded text files. You will see an example of how to do this later. StreamWriter is another class in the System.IO namespace.

It defines a number of overloaded methods for writing various primitive data types in text format. Notice that the data array is written out preceded by the upper bound. Writing the upper bound is not really necessary especially if the array is a fixed size. However, specifying the upper bound does make it easier to read the data from file and it allows you to change the size of your arrays without breaking code that reads your file format. When the code in Figure 4 executes it produces the file shown in Figure 5.

Visit the Agilent Developer Network at http://www.agilent.com/find/ADN

Agilent Developer Network White Paper 4 of 17 Storing Test Data to File Using Microsoft® Visual Basic® .NET November 2002 Figure 5 CSV File Contents A00100,10/11/2002 2:20:35 AM,1,2,1.1,2.2,3.3 The code to read the test data from this file is shown in Figure 6. Figure 6 Reading from a CSV Text File ' Example 1b in the sample project ' Open file for reading as text Dim reader As StreamReader = File.OpenText(filename) ' Read entire file as a string and split the string on commas Dim strings As String() = reader.ReadToEnd().Split(",") ' Parse each individual string Dim dutId As String = strings(0) Dim timestamp As DateTime = DateTime.Parse(strings(1)) Dim stationId As Integer = Integer.Parse(strings(2)) Dim data() As Double = New Double(Integer.Parse(strings(3 { } Dim ndx As Integer For ndx = 4 To strings.Length - 1 data(ndx - 4) = Double.Parse(strings(ndx)) Next ' Close StreamReader when done reader.Close() The following line of code from Figure 6 opens a file for reading in text mode.

Dim reader As StreamReader = File.OpenText(filename) Then the code reads in the entire file using the StreamReader.ReadToEnd method. This approach is fine for smaller files but it can use large amounts of memory for large data files. I will show you a better approach to reading a large text file in the next example. Since the file is in CSV format, this example uses the String.Split method to split the string on commas. The implicit assumption here is that none of our string values contain commas. That leaves an array of strings to parse. Since the dutId variable is of type string no parsing is required.

For timestamp, the DateTime class fortunately provides a shared Parse method that takes a string containing a date and time and creates a new DateTime object. As it turns out, most of the VB.NET primitive types like Integer and Double also provide shared Parse methods that take a string and return a value of the appropriate type. As a result, reading the stationId is straightforward. For the array, this example parses the array size first and allocates the appropriate size array. The following line of code may look a bit unusual to a VB 6 programmer.

Dim data() As Double = New Double(Integer.Parse(strings(3 { } Visit the Agilent Developer Network at http://www.agilent.com/find/ADN

Agilent Developer Network White Paper 5 of 17 Storing Test Data to File Using Microsoft® Visual Basic® .NET November 2002 This line of code allocates an array based on the array size that is read from the file via strings(3) without requiring a ReDim. Then the example iterates through the rest of the strings converting each string into a double value to put into the data array. Saving Test Data Using Name Value Pair Text Format One of the problems with CSV format is that if you want to store strings that contain embedded commas, then parsing the file contents becomes much harder.

Certainly String.Split wouldn’t work very well in this case. Another approach to saving data that doesn’t suffer from problems handling special characters like commas is “name value” format. Furthermore, since each test data element is saved on its own line in the file, it is easier to read the file in smaller chunks versus reading the entire file at once. Figure 7 demonstrates how to save the same test data in name value pair format.

Figure 7 Writing Data to a Name Value Pair Text File ' Example 2a in the sample project ' Create test data Dim dutId As String = "A00100" Dim timestamp As DateTime = DateTime.Now Dim stationId As Integer = 1 Dim data() As Double = New Double() {1.1, 2.2, 3.3} ' Open file for writing text with ASCII encoding Dim writer As StreamWriter = _ New StreamWriter(filename, False, Encoding.ASCII) ' Write data as name=value pairs, one per line writer.WriteLine("DutId=" & dutId) writer.WriteLine("Timestamp=" & timestamp) writer.WriteLine("StationId=" & stationId) writer.Write("Data=") Dim ndx As Integer For ndx = 0 To UBound(data) writer.Write(data(ndx)) If ndx UBound(data) Then writer.Write(",") End If Next ' Close StreamWriter when done writer.Close() Notice in this example that a StreamWriter object is created directly rather than using File.CreateText as shown in this line of code.

Dim writer As StreamWriter = _ New StreamWriter(filename, False, Encoding.ASCII) Directly creating the StreamWriter gives us more flexibility in how the StreamWriter is configured. The second parameter is set to False and indicates to Visit the Agilent Developer Network at http://www.agilent.com/find/ADN

Agilent Developer Network White Paper 6 of 17 Storing Test Data to File Using Microsoft® Visual Basic® .NET November 2002 the StreamWriter constructor that we want to create a new file instead of appending to an existing file. The third parameter (Encoding.ASCII) results in the test data being saved using ASCII encoding.

Saving test data out in name value format is pretty easy. You just write out a name for each element of the test data followed by an “=” sign followed by the value for that element of the test data. The file contents produced by this approach are shown in Figure 8.

Figure 8 Name Value Pair File Contents DutId=A00100 Timestamp=10/11/2002 2:52:26 AM StationId=1 Data=1.1,2.2,3.3 The code to read the test data from this file is shown in Figure 9. Figure 9 Reading from a Name Value Pair Text File ' Example 2b in the sample project Dim dutId As String Dim timestamp As DateTime Dim stationId As Integer Dim data() As Double ' Open file for reading as text Dim reader As StreamReader = New StreamReader(filename) ' Read the file one line at a time Dim line As String While reader.Peek -1 line = reader.ReadLine() ' Extract name/value pairs Dim ndx As Integer = line.IndexOf("=") If ndx -1 Then Dim name As String = line.Substring(0, ndx) Dim value As String = line.Substring(ndx + 1) ' Parse each value - note with this scheme you don't ' have to read the data in the order it was written Select Case name Case "Timestamp" timestamp = DateTime.Parse(value) Case "StationId" stationId = Integer.Parse(value) Case "Data" Dim dataStr() As String = value.Split(",") data = New Double(UBound(dataStr)) {} Dim i As Integer For i = 0 To UBound(dataStr) data(i) = Double.Parse(dataStr(i)) Next Visit the Agilent Developer Network at http://www.agilent.com/find/ADN

Agilent Developer Network White Paper 7 of 17 Storing Test Data to File Using Microsoft® Visual Basic® .NET November 2002 Case "DutId" dutId = value End Select End If End While ' Close StreamReader when done reader.Close() Again, this example directly creates a StreamReader but this time rather than reading in the entire file contents at once using the StreamReader.ReadToEnd method, it reads in the file one line at a time using the StreamReader.ReadLine method. This works nicely because each name value pair was saved on its own line. The example uses a Select statement to execute different parse code depending upon the name value pair that was read from each line of the file.

One minor advantage of this approach is that the data doesn’t have to appear in any particular order in the file. Putting the code in the Case statements is manageable for a small example like this but if the amount of data starts getting larger you might want to put the parsing code for each piece of data in its own function. Saving Data Using BinaryWriter The previous two examples create files that are human readable which has it advantages. However, if you have a large amount of test data, reading in and parsing data stored as text can be slow. If performance is your primary concern then consider saving your test data to a binary file as shown in Figure 10.

Figure 10 Writing to a File Using BinaryWriter ' Example 3a in the sample project ' Create test data Dim dutId As String = "A00102" Dim timestamp As DateTime = DateTime.Now Dim stationId As Integer = 5 Dim data() As Double = New Double() {3.3, 4.4, 5.5} ' Create FileStream for writing binary data ' This locks the file (FileShare.None) until FileStream is closed Dim stream As FileStream = New FileStream(filename, FileMode.Create, _ FileAccess.Write, FileShare.None) ' Create BinaryWriter on top of stream to write data in binary format Dim writer As BinaryWriter = New BinaryWriter(stream) ' Write data in binary format writer.Write(dutId) writer.Write(timestamp) writer.Write(stationId) writer.Write(UBound(data)) Dim ndx As Integer For ndx = 0 To UBound(data) writer.Write(data(ndx)) Next Visit the Agilent Developer Network at http://www.agilent.com/find/ADN

Agilent Developer Network White Paper 8 of 17 Storing Test Data to File Using Microsoft® Visual Basic® .NET November 2002 ' Close BinaryWriter which automatically closes underlying FileStream writer.Close() In this example, a file is opened in yet another way using the System.IO.FileStream class. So far our examples have opened a file using the following classes: File, StreamWriter and now FileStream. What’s going on? The class that actually does file I/O is FileStream. FileStream derives from the base class Stream. Stream defines a common set of methods for reading and writing bytes, determining the length of a stream and setting the seek position.

Stream itself cannot be created but its derived classes can. These include BufferedStream, MemoryStream, NetworkStream, CryptoStream and of course FileStream. Now you could do all of your file I/O using just the FileStream class. The many different FileStream constructors allow you to select Read/Write/ReadWrite mode, Open/Create/Append/Truncate mode, file sharing options, buffer size and asynchronous mode. This gives you a lot of flexibility however reading and writing data to a file in byte format is not the easiest way to save your test data. Microsoft recognized this and provided several different reader and writer classes.

StreamReader and StreamWriter work with any type of stream object including FileStream and provide services for reading and writing data in text format. StreamWriter allows you to select the text encoding you desire; remember it defaults to UTF-8. StreamReader can typically determine the encoding type based on the existence and value of a byte order mark (BOM) in the text file that it is reading. Of course, you can always explicitly choose which encoding that StreamReader should use.

As we saw in Figure 7 and Figure 9, both StreamWriter and StreamReader provide constructors that take a filename. Internally, these classes create a FileStream object given a filename. In Figure 4, the File.CreateText method creates a StreamWriter that internally creates a FileStream object to write onto. You may also notice the StringReader and StringWriter classes, which are very similar in functionality to a StreamReader and StreamWriter. The primary difference is that StringReader and StringWriter operate on a string instead of a Stream.

BinaryReader and BinaryWriter provide services for reading and writing primitive data types in binary format.

As you might notice in Figure 10, writing data to file using the BinaryWriter is very straightforward. One thing to note about all Stream related reader and writer objects is that when you call the Close method on them, they automatically close the stream they are using. The contents of the generated file are shown in Figure 11. Because it is a binary format, characters that cannot be displayed are represented by their 3 digit decimal value.

Figure 11 Binary File Contents 006 A 0 0 1 0 2 022 1 0 / 1 2 / 2 0 0 2 1 1 : 2 5 : 2 0 P M 005 000 000 000 002 000 000 000 f f f f f f 010 @ 017 @ 000 000 000 000 000 000 022 @ Visit the Agilent Developer Network at http://www.agilent.com/find/ADN

Agilent Developer Network White Paper 9 of 17 Storing Test Data to File Using Microsoft® Visual Basic® .NET November 2002 The code to read the test data from this binary file is shown in Figure 12. Figure 12 Reading from a File Using BinaryReader ' Example 3b in the sample project Dim dutId As String Dim timestamp As DateTime Dim stationId As Integer Dim data() As Double ' Open FileStream for reading binary data Dim stream As FileStream = New FileStream(filename, FileMode.Open) ' Create BinaryReader on top of stream to read data in binary format Dim reader As BinaryReader = New BinaryReader(stream) ' Read data in same order it was written dutId = reader.ReadString() timestamp = DateTime.Parse(reader.ReadString()) stationId = reader.ReadInt32() Dim upperBound As Integer = reader.ReadInt32() data = New Double(upperBound) {} Dim i As Integer For i = 0 To upperBound data(i) = reader.ReadDouble() Next ' Close BinaryReader which automatically closes underlying FileStream reader.Close() Reading the test data from the binary file is also pretty straightforward.

After creating a FileStream and a BinaryReader, this example uses BinaryReader methods like ReadString, ReadInt32 and ReadDouble to read the test data. One thing to note is that BinaryReader and BinaryWriter don’t handle DateTime objects directly so you have to store the DateTime object as text and then read it back in as text. Like the first example (Figure 6) you have to read the test data in the same order in which it was saved out.

Saving Data Using Binary Serialization You have seen various different ways to save test data in both text and binary format. One thing in common with all of these approaches is that code had to be written to save each individual element of the test data as well as parse each individual element when reading the data back from file. Fortunately, the FCL provides a mechanism referred to as serialization that does all of this tedious work for us. All you have to do is package the test data in a class and mark that class as serializable as shown in Figure 13. The FCL introduces a new concept called attributes.

Just decorate the TestData class with the SerializableAttribute using the notation “”. This attribute tells the CLR that any objects of type TestData can be serialized.

Visit the Agilent Developer Network at http://www.agilent.com/find/ADN

Agilent Developer Network White Paper 10 of 17 Storing Test Data to File Using Microsoft® Visual Basic® .NET November 2002 Figure 13 TestData Class Definition _ Public Class TestData Public DutId As String Public Timestamp As DateTime Public StationId As Integer Public Data() As Double End Class All you have to do to serialize the test data is create a FileStream object and a BinaryFormatter object (located in the System.Runtime.Serialization.Formatters.Binary namespace) and then tell the BinaryFormatter object to save our test data array to the FileStream object as shown in Figure 14.

Figure 14 Binary Serialization using BinaryFormatter ' Example 4a in the sample project ' Fill in test data array Dim data() As TestData = TestData.GenerateData() ' Create FileStream on which we will serialize our test data and ' create BinaryFormatter that will do the serialization Dim stream As FileStream = New FileStream(filename, FileMode.Create) Dim formatter As BinaryFormatter = New BinaryFormatter() ' This will make the serialized data independent of the ' assembly version number formatter.AssemblyFormat = FormatterAssemblyStyle.Simple ' Serialize the test data formatter.Serialize(stream, data) ' Close the FileStream when done stream.Close() The following line of code calls a shared method in the TestData class that is not shown in the TestData class definition in Figure 13.

Dim data() As TestData = TestData.GenerateData() The sample project associated with this article does contain the GenerateData method. This method simply creates two TestData objects with fake data and returns an array of TestData containing these two objects. Note that the following line is only necessary when you strong name your assemblies. formatter.AssemblyFormat = FormatterAssemblyStyle.Simple When you strong name an assembly that contains a serializable class or structure, the assembly version is saved out to file with a serialized class or structure instance. If you then update the version number on that assembly, you will get an error when Visit the Agilent Developer Network at http://www.agilent.com/find/ADN

Agilent Developer Network White Paper 11 of 17 Storing Test Data to File Using Microsoft® Visual Basic® .NET November 2002 you try to deserialize any existing files because the version numbers will not match. The FormatterAssemblyStyle.Simple setting tells the CLR to ignore version numbers. If you don’t know about strong naming don’t worry too much about it since strong naming is not turned on by default in VB.NET. For more information on strong named assemblies see Strong-Named Assemblies on the MSDN web site. The file contents of the generated file are shown in Figure 15. Again, because it is a binary format, characters that cannot be displayed are represented by their 3 digit decimal value.

Figure 15 Binary Serialized File Contents 000 001 000 000 000 001 000 000 000 000 000 000 000 012 002 000 000 000 006 F i l e I O 007 001 000 000 000 000 001 000 000 000 002 000 000 000 004 015 F i l e I O . T e s t D a t a 002 000 000 000 009 003 000 000 000 009 004 000 000 000 005 003 000 000 000 015 F i l e I O . T e s t D a t a 004 000 000 000 005 D u t I d 009 T i m e s t a m p 009 S t a t i o n I d 004 D a t a 001 000 000 007 013 008 006 002 000 000 000 006 005 000 000 000 006 A 0 0 1 0 0 I c j @ 008 001 000 000 000 009 006 000 000 000 001 004 000 000 000 003 000 000 000 006 007 000 000 000 006 A 0 0 1 0 2 I c j @ 008 005 000 000 000 009 008 000 000 000 015 006 000 000 000 003 000 000 000 006 ? 001 @ f f f f f f 010 @ 015 008 000 000 000 003 000 000 000 006 017 @ 000 000 000 000 000 000 022 @ f f f f f f 026 @ 011 The code to read the data from file, a process referred to as deserialization, is shown in Figure 16.

Figure 16 Binary Deserialization Using BinaryFormatter ' Example 4b in the sample project ' Open FileStream for reading serialized data and ' create BinaryFormatter to do deserialization Dim stream As FileStream = New FileStream(filename, FileMode.Open) Dim formatter As BinaryFormatter = New BinaryFormatter() ' Deserialize data & type cast it from System.Object to correct type Dim data() As TestData = CType(formatter.Deserialize(stream), _ TestData()) ' Close the FileStream when done stream.Close() This code looks remarkably similar to our serialization code. A FileStream object is created but this time using FileMode.Open instead of FileMode.Create mode.

The example uses a BinaryFormatter like before but this time the Deserialize method is called and passed the FileStream object for the data file. The return type of the Deserialize method is Object so the example uses VB.NET’s CType function to cast Visit the Agilent Developer Network at http://www.agilent.com/find/ADN

Agilent Developer Network White Paper 12 of 17 Storing Test Data to File Using Microsoft® Visual Basic® .NET November 2002 the returned object to type TestData array. It is pretty clear that the code required to save and retrieve test data is much simpler using serialization. Saving Data Using XML Serialization Binary serialization looks pretty good. However sometimes when performance isn’t a primary concern there are advantages to saving data in text format. This next example serializes the test data in XML. Besides being a human readable format, there are many tools available for working with and displaying XML data.

Technically, this example could have used our previous definition for TestData to serialize in XML format. However, to demonstrate the ways that the XML output can be customized during serialization this example uses a different test data class called TestDataXml. This class, shown in Figure 17, demonstrates the use of the XmlAttributeAttribute (located in the System.Xml.Serialization namespace) to mark certain public fields so that they appear as XML attributes instead of XML elements in the output. Note also that in the case of the DutId field you can even change the name of the XML attribute to DutIdentifier.

Figure 17 TestDataXml Class Definition Imports System.Xml.Serialization _ Public Class TestDataXml _ Public DutId As String _ Public Timestamp As DateTime Public StationId As Integer Public Data() As Double End Class The code to serialize the TestDataXml array in XML format is shown in Figure 18. Figure 18 XML Serialization Using XmlSerializer ' Example 5a in the sample project ' Fill in test data array Dim data() As TestDataXml = TestDataXml.GenerateData() ' Create StreamWriter on which the test data will be serialized and ' create XmlSerializer that will do the serialization Dim writer As New StreamWriter(filename) Dim serializer As New XmlSerializer(GetType(TestDataXml())) ' Serialize the test data serializer.Serialize(writer, data) ' Close the StreamWriter when done writer.Close() Visit the Agilent Developer Network at http://www.agilent.com/find/ADN

Agilent Developer Network White Paper 13 of 17 Storing Test Data to File Using Microsoft® Visual Basic® .NET November 2002 This code is very similar to the code that was used to serialize in binary format. The primary difference is that this code creates an XmlSerializer object instead of a BinaryFormatter object. When creating the XmlSerializer object you must tell it what type of object that you are serializing, in this case an array of TestDataXml. The XML output is shown in Figure 19. Figure 19 XML Serialized File Contents 1 1.1 2.2 3.3 5 4.4 5.5 6.6 The code to deserialize the data is shown in Figure 20.

As you can see, the code is very similar to what was used in the binary deserialization example. Again, the primary difference is that this code creates an XmlSerializer object instead of a BinaryFormatter. You must tell the XmlSerializer what type of object you need to deserialize and then call the XmlSerializer.Deserialize method to retrieve the data. Figure 20 XML Deserialization Using XmlSerializer ' Example 5b in the sample project ' Open StreamReader for reading serialized data and ' create XmlSerializer to do the deserialization Dim reader As New StreamReader(filename) Dim serializer As New XmlSerializer(GetType(TestDataXml())) ' Deserialize data & type cast it from System.Object to correct type Dim data() As TestDataXml = CType(serializer.Deserialize(reader), _ TestDataXml()) ' Close the StreamReader when done reader.Close() Visit the Agilent Developer Network at http://www.agilent.com/find/ADN

Agilent Developer Network White Paper 14 of 17 Storing Test Data to File Using Microsoft® Visual Basic® .NET November 2002 This concludes our exploration of ways of storing test data in files using the objects of the FCL. For the rest of this article, I will explore a few other areas related to file I/O. Reading Application Configuration Settings One fairly common reason for doing file I/O in an application is to read application configuration data from file. You can easily add an application configuration file to an application by selecting the “Project -> Add New Item” menu item in VS.NET and selecting “Application Configuration File”.

The contents of the application configuration file used in this example are shown in Figure 21. Figure 21 Application Configuration File Contents The code to read in this configuration data is shown in Figure 22. Figure 22 Reading Data from an Application Configuration File ' Example 6 in the sample project Dim message As String = ConfigurationSettings.AppSettings("Message") Dim pi As Double = _ Double.Parse(ConfigurationSettings.AppSettings("PI")) Dim path As String = ConfigurationSettings.AppSettings("Path") ' If the string contains environment variables like %TEMP%, ' expand those to their actual value path = Environment.ExpandEnvironmentVariables(path) Reading this data is relatively trivial.

Just provide the ConfigurationSettings.AppSettings method with the key name of the value you want to retrieve from the app config file. Unfortunately, the current version of the FCL does not support writing configuration data to file in a similar manner. Also, note that the System.Environment class contains many useful methods. One of these methods is ExpandEnvironmentVariables, which takes a string and, if that string contains an environment variable delimited like so “%TEMP%”, it replaces the environment variable with the value associated with the environment variable. Visit the Agilent Developer Network at http://www.agilent.com/find/ADN

Agilent Developer Network White Paper 15 of 17 Storing Test Data to File Using Microsoft® Visual Basic® .NET November 2002 File and Directory Operations The FileSystemObject from the Microsoft Scripting Runtime (scrrun.dll) is still available for you to use in VB.NET. For information on how to use the FileSystemObject in VB.NET see Accessing Files with FileSystemObject on the MSDN web site. However, most of the FileSystemObject’s functionality is natively available in the FCL. This example explores that functionality.

There are two classes related to files in the System.IO namespace.

One is File and the other is FileInfo. File contains only shared methods that allow you to do things like check if a file exists, create a file, move a file, copy a file, delete a file, get file attributes, etc. For all of these methods you only need a filename (or two) to perform the operation. You can do all of this without ever creating a File object. FileInfo contains very similar functionality except that it contains only instance methods which means you have to create a FileInfo object first. You can do this by creating a FileInfo object and providing a filename to the constructor. However, the more common scenario is that you got back a FileInfo object from a call to a method like DirectoryInfo.GetFiles which returns an array of FileInfo objects.

There are also two classes related to directories. Just like the File class, the Directory class has only shared methods that require only a directory name (or two) to do operations like checking if a directory exists, creating a directory, moving a directory, deleting a directory, getting the parent directory and getting and setting the current directory. There are also methods for enumerating the files and directories contained within a directory. The DirectoryInfo class contains functionality that is very similar to the Directory class, but, like the FileInfo class, it contains only instance methods.

In order to use these instance methods, you will need to create a DirectoryInfo object first.

Another useful class in the System.IO namespace is the Path class. It contains shared methods for operations like getting the filename portion of a path, getting the directory portion of a path, getting the extension portion of a path, changing the extension of a filename, getting a temporary filename and getting the temp path on the system. When you need to find the location of a special folder like the DesktopDirectory or the ApplicationData directory, you can use the Environment.GetFolderPath method. It takes a value from the Environment.SpecialFolder enumeration and returns the path to that folder on the system.

The code shown in Figure 23 demonstrates some of the various file and directory operations that can be done with System.IO types. Figure 23 File and Directory Code Sample ' Example 7 in the sample project ' Create a Temp File, write to it and close it Dim filename As String = Path.GetTempFileName() Dim writer As StreamWriter = File.CreateText(filename) writer.WriteLine("Hello World") writer.Close() Visit the Agilent Developer Network at http://www.agilent.com/find/ADN

Agilent Developer Network White Paper 16 of 17 Storing Test Data to File Using Microsoft® Visual Basic® .NET November 2002 ' Copy original file to new file but don't overwrite an existing file Dim copiedFilename As String = Path.ChangeExtension(filename, ".bak") File.Copy(filename, copiedFilename, False) ' Get the current directory Dim curDir As String = Directory.GetCurrentDirectory() ' Create a new directory in the TEMP folder Dim tmpDir As String = Path.GetTempPath() Dim newDir As String = Path.Combine(tmpDir, "FileIoExample") Dim newDirInfo As DirectoryInfo = Directory.CreateDirectory(newDir) ' Move original file to newly created directory Dim oldFilename As String = filename filename = Path.Combine(newDirInfo.FullName, _ Path.GetFileName(filename)) File.Move(oldFilename, filename) ' Create subdirectory under newDir Dim newSubDirInfo As DirectoryInfo = newDirInfo.CreateSubdirectory("Example7") ' Enumerate files and folders under newDir Dim fsInfo() As FileSystemInfo = newDirInfo.GetFileSystemInfos() Dim i As Integer For i = 0 To UBound(fsInfo) Dim info As String If TypeOf (fsInfo(i)) Is FileInfo Then Dim fileInfo As FileInfo = CType(fsInfo(i), FileInfo) info = String.Format(" {0,-20} {1,10}", fileInfo.Name, _ fileInfo.Length) ElseIf TypeOf (fsInfo(i)) Is DirectoryInfo Then Dim dirInfo As DirectoryInfo = CType(fsInfo(i), DirectoryInfo) info = String.Format(" {0,-20} {1,10}", dirInfo.Name, _ "") End If Next ' Delete files and directories that were created If File.Exists(copiedFilename) Then File.Delete(copiedFilename) End If newDirInfo.Delete(True) Monitoring Files and Directories with FileSystemWatcher Occasionally you may have the need to monitor files or directories and perform an operation like reloading a file whenever it has been changed outside of the current application.

Microsoft needed this functionality for ASP.NET and they made it available for everyone else to use. The code in Figure 24 shows how easy it is to set up a FileSystemWatcher to notify a program whenever the specified file is written to.

Visit the Agilent Developer Network at http://www.agilent.com/find/ADN

Agilent Developer Network White Paper 17 of 17 Storing Test Data to File Using Microsoft® Visual Basic® .NET November 2002 Visit the Agilent Developer Network at http://www.agilent.com/find/ADN Figure 24 Monitoring a File Using FileSystemWatcher ' Example 8 in the sample project Private WithEvents m_fileSysWatcher As FileSystemWatcher ' Create and configure a FileSystemWatcher m_fileSysWatcher = New FileSystemWatcher() m_fileSysWatcher.NotifyFilter = NotifyFilters.LastWrite m_fileSysWatcher.Path = Path.GetDirectoryName(filename) m_fileSysWatcher.Filter = Path.GetFileName(filename) m_fileSysWatcher.EnableRaisingEvents = True This code creates a FileSystemWatcher object and configures it to monitor the file specified by filename.

The code specifies that it is only interested in events related to file writes by setting the NotifyFilter property to NotifyFilters.LastWrite. Whenever the file is written to, the event handler associated with the FileSystemWatcher gets fired. You may experience more file write related events being fired than you might expect. It turns out that when you create a file, FileStream’s constructor writes a zero length file which fires the event handler. Then, when the FileStream is closed, the event handler is fired again when the file is flushed out to disk.

Conclusions In this article, we have looked at a simple way to read application configuration files, manipulate files and directories as well as monitor files. We have also explored five different methods for saving and retrieving test data from file in both text and binary formats. The System.IO namespace in the FCL provides low level, flexible methods for creating, opening, writing to and reading from files as well as high level, simple to use methods for doing the same. Even better, the serialization technique provides a very simple approach to saving and retrieving test data in which the FCL does most of the work for you.

While the VB 6 approach to file I/O is still available in VB.NET, it is worthwhile to adopt the new file I/O functionality in the FCL. This functionality is more powerful and more flexible than the VB 6 compatibility functions. Finally, understanding how to do file I/O the FCL way gives you a leg up on other .NET languages like C# because they do file I/O using the same FCL file I/O objects. For more information on file I/O, see the topic Working with I/O in Microsoft’s MSDN Library. Microsoft, Visual Basic, and MSDN are either registered trademarks or trademarks of Microsoft Corporation in the United States and/or other countries.

Agilent T&M Software and Connectivity Agilent’s Test and Measurement software and connectivity products, solutions and Agilent Developer Network allow you to take time out of connecting your instrument to your computer with tools based on PC standards, so you can focus on your tasks, not on your connections. Visit: http://www.agilent.com/find/connectivity Product specifications and descriptions in this document subject to change without notice © 2002 Agilent Technologies, Inc. All rights reserved.

Published: November 2002