Array to Text File: Detailed Overview of ArrayIOExtensionsLib Class

Introduction

In modern software development, efficient data management is essential. While relational database management systems like SQL servers are often the go-to solution, simpler scenarios sometimes require a more lightweight approach. For example, applications that do not demand the complexity of relational databases can benefit from using text files for data storage. This is the case with our Phone Book application, which manages data using a simple 2D table. To streamline the process of saving and retrieving data, we developed the ArrayIOExtensionsLib, a library that provides easy-to-use methods for handling data stored in text files.

Overview of ArrayIOExtensionsLib

ArrayIOExtensionsLib is a .NET library designed to simplify working with arrays stored in text files. It provides extension methods for saving and loading both single and multi-dimensional arrays of simple types (such as strings and primitives) to and from text files. This library is ideal for applications that do not require a relational database but still need reliable and efficient data storage and retrieval.

Key Features

  • Save and Load Multi-dimensional Arrays: Save arrays of any rank (1D, 2D, etc.) to text files, where each element is stored as a separate line.
  • Support only for Simple Types: Works with arrays of strings, primitives (int, double, etc.), and custom object types that include only simple properties.
  • Fixed-length Arrays: The library works with fixed-length arrays. The programmer must anticipate the maximum number of records the array will hold at runtime, as arrays cannot dynamically resize. If the array's size needs to be expanded, a new, larger array must be created, and existing data should be copied over to the new array. This can be done by manually resizing the array and transferring data or by implementing logic to periodically check if the array is full and perform the resize operation as needed.
  • Null Handling: null values are serialized as "null" in the text file and correctly deserialized back into the array.
  • Customizable Encoding: Allows specifying encoding when saving and loading files (defaults to UTF-8).

Important Notice. All array fields must be properly initialized before using the extension methods in this library. Uninitialized arrays or null values may lead to unexpected behavior during serialization and deserialization.

Installation

To use this library in your project.

  1. Download ArrayIOExtensionsLib open-source archive
  2. Build the project to generate the .dll file.
  3. Reference the generated .dll in your own project.
  4. In Visual Studio, Right-click your project > Add > Reference > Browse to select the generated .dll.
  5. In .NET CLI, Run the following command.
    dotnet add reference /path/to/ArrayIOExtensions.dll

Usage

Save an Array to a Text File.

int[,] myArray = new int[,] 
{ 
    { 1, 2 }, 
    { 3, 4 }, 
    { 5, 6 } 
};

myArray.SaveToTextFile("output.txt");

Load an Array from a Text File.

int[,] myArray = new int[3, 2];
myArray.LoadFromTextFile("output.txt");

Saving Custom Types.

public class Customer
{
    public string? Name { get; set; }
    public string? Address { get; set; }
}

Customer[] customers = new Customer[2];
customers[0] = new Customer 
{ 
    Name = "John", 
    Address = "123 Street" 
};

customers[1] = new Customer 
{ 
    Name = "Jane", 
    Address = "456 Avenue" 
};

customers.SaveToTextFile("customers.txt");

Loading Custom Types.

Customer[] customers = new Customer[2];
customers.LoadFromTextFile("customers.txt");

Code Breakdown

Here is a detailed explanation of the ArrayIOExtensions class code, method by method.

using System.IO;
using System.Text;
using System;

namespace ArrayIOExtensionsLib
{
    /// <summary>
    /// Provides extension methods for saving and loading 
    /// single or multi-dimensional arrays to and from text files.
    /// IMPORTANT: Values in the array must be properly initialized
    /// before calling the save methods, especially when working with
    /// arrays of complex or custom types. If not properly initialized, 
    /// null values in objects may lead to incorrect written values 
    /// in the text file, as the object will not be serialized with 
    /// all its properties.
    /// </summary>
    public static class ArrayIOExtensions
    {
        #region Properties

        /// <summary>
        /// Represents the default encoding used for saving arrays to text files.
        /// </summary>
        private static readonly Encoding _defaultEncoding = Encoding.UTF8;

        /// <summary>
        /// Represents the default word used for null value.
        /// </summary>
        private static readonly string _nullRepresentation = "null";

        #endregion
    }
}

Properties

The ArrayIOExtensionsLib library relies on a couple of key properties to ensure smooth operation during file I/O processes:

  • _defaultEncoding: Specifies UTF-8 as the default encoding format for saving and reading text files.
  • _nullRepresentation: Defines how null values are represented in the text file. By default, the string "null" is used.

Saving Arrays to Text Files

The primary method for saving arrays is SaveToTextFile.

Important. Values in the array must be properly initialized before calling the save methods, especially when working with arrays of complex or custom types. If not properly initialized, null values in objects may lead to incorrect written values in the text file, as the object will not be serialized with all its properties.

#region Save To Text File

/// <summary>
/// Saves a single or multi-dimensional array to a specified text file.
/// Each element of the array is written to a new line in the file.
/// IMPORTANT: Values in the array must be properly initialized before
/// calling the save methods, especially when working with arrays
/// of complex or custom types. If not properly initialized, 
/// null values in objects may lead to incorrect written values
/// in the text file, as the object will not be serialized 
/// with all its properties.
/// </summary>
/// <param name="array">The single or multi-dimensional array to save.</param>
/// <param name="filePath">The path of the file where the array will be saved.</param>
/// <param name="encoding">Encoding used for saving arrays to text files.</param>
/// <exception cref="ArgumentNullException">Thrown when the array is null.</exception>
/// <exception cref="ArgumentException">Thrown when the file path is null or whitespace.</exception>
public static void SaveToTextFile(this Array array,
                                  string filePath,
                                  Encoding? encoding = null)
{
    // Validate the array and file path before proceeding.
    ValidateArrayAndFilePath(array, filePath);

    try
    {
        // Check encoding and set the default encoding if not specified.
        encoding ??= _defaultEncoding;

        // Create a StreamWriter to write to the specified file using UTF-8 encoding.
        using var writer = new StreamWriter(filePath, false, encoding);

        // Start the process of saving the array to the text file.
        SaveArrayToTextFile(array, writer);
    }
    catch (Exception ex)
    {
        // Throw an exception if an error occurs during file writing.
        throw new ArgumentException(
            $"An error occurred while saving to file: {ex.Message}");
    }
}

SaveToTextFile Method

  • This method saves an array (either single or multi-dimensional) to a specified text file.
  • It first validates the input array and file path using the ValidateArrayAndFilePath method.
  • It then checks the specified encoding, defaulting to UTF-8 if not provided.
  • A StreamWriter is used to write each element of the array to a new line in the text file by calling the SaveArrayToTextFile method.

Internal Method for Saving

/// <summary>
/// Initializes the dimensions array and starts the recursive saving of the array.
/// </summary>
/// <param name="array">The single or multi-dimensional array to save.</param>
/// <param name="writer">The StreamWriter used for writing to the file.</param>
private static void SaveArrayToTextFile(Array array, StreamWriter writer)
{
    // Create an array to hold the length
    // of each dimension of the array.
    int[] dimensions = new int[array.Rank];
    for (int i = 0; i < array.Rank; i++)
    {
        dimensions[i] = array.GetLength(i);
    }

    // Get the element type of the array.
    var elementType = array.GetType().GetElementType();

    // Begin the recursive method to save
    // the array elements to the text file.
    SaveArrayToTextFileRecursive(array,
                                 writer,
                                 dimensions,
                                 new int[array.Rank],
                                 0,
                                 elementType!);
}

SaveArrayToTextFile Method

  • This method initializes an array to hold the lengths of each dimension of the input array.
  • It then calls the SaveArrayToTextFileRecursive method to handle the recursive saving of the array elements.

Recursive Saving Logic

/// <summary>
/// Recursively writes the contents of 
/// the single or multi-dimensional array to the StreamWriter.
/// </summary>
/// <param name="array">The single or multi-dimensional array to save.</param>
/// <param name="writer">The StreamWriter used for writing to the file.</param>
/// <param name="dimensions">The lengths of each dimension of the array.</param>
/// <param name="indices">The current indices in the array being processed.</param>
/// <param name="currentIndex">The current dimension index being processed.</param>
/// <param name="elementType">The type of the object to serialize.</param>
private static void SaveArrayToTextFileRecursive(Array array,
                                                 StreamWriter writer,
                                                 int[] dimensions,
                                                 int[] indices,
                                                 int currentIndex,
                                                 Type elementType)
{
    // Shortcut to handle single-dimensional arrays directly
    if (array.Rank == 1)
    {
        for (int i = 0; i < array.Length; i++)
        {
            // Get the value of the single-dimensional array
            var value = array.GetValue(i);

            // Serialize the value
            SerializeObject(writer, value, elementType);
        }
        return; 
        // Exit early since we've already processed the entire array
    }

    // If we are at the last dimension, write the values to the file.
    if (currentIndex == array.Rank - 1)
    {
        for (int i = 0; i < dimensions[currentIndex]; i++)
        {
            indices[currentIndex] = i; // Set the index for the last dimension.
            var value = array.GetValue(indices);

            SerializeObject(writer, value, elementType);
        }
    }
    else
    {
        // Iterate through the current dimension
        // and recurse into the next dimension.
        for (int i = 0; i < dimensions[currentIndex]; i++)
        {
            indices[currentIndex] = i;
            SaveArrayToTextFileRecursive(array,
                                         writer,
                                         dimensions,
                                         indices,
                                         currentIndex + 1,
                                         elementType);
        }
    }
}

#endregion

SaveArrayToTextFileRecursive Method

  • This method handles the recursive writing of array elements to the StreamWriter.
  • It first checks if the array is single-dimensional, saving values directly if so.
  • For multi-dimensional arrays, it writes values from the last dimension and recurses for previous dimensions, building up the index as it goes.

Serialization

/// <summary>
/// Serializes an object to the StreamWriter.
/// </summary>
/// <param name="writer">The StreamWriter used for writing to the file.</param>
/// <param name="obj">The object to serialize.</param>
/// <param name="elementType">The type of the object to serialize.</param>
private static void SerializeObject(StreamWriter writer,
                                    object? obj,
                                    Type elementType)
{
    // Check if the object is null
    if (obj == null)
    {
        if (elementType == typeof(string) || elementType.IsPrimitive)
        {
            writer.WriteLine(_nullRepresentation);
            return;
        }

        // Attempt to create a new instance if it is a complex type
        // Assuming you know the type or have a way to get it
        obj = Activator.CreateInstance(elementType);
    }

    // Now serialize the object
    if (obj is string || obj!.GetType().IsPrimitive)
    {
        writer.WriteLine(obj.ToString());
    }
    else
    {
        // Write each property on a new line
        var properties = obj.GetType().GetProperties();
        foreach (var property in properties)
        {
            var value = 
                property.GetValue(obj)?.ToString() ?? _nullRepresentation;
            writer.WriteLine(value);
        }
    }
}

SerializeObject Method

  • This method handles the serialization of individual objects, writing their properties to the text file.
  • If the object is null, it writes "null"; otherwise, it writes the value or each property of the object on a new line.

Important. Values in the array must be properly initialized before calling the save methods, especially when working with arrays of complex or custom types. If not properly initialized, null values in objects may lead to incorrect written values in the text file, as the object will not be serialized with all its properties.

Loading Arrays from Text Files

The method for loading arrays is LoadFromTextFile.

#region Load From Text File

/// <summary>
/// Loads data from a specified text file and populates the single or multi-dimensional array.
/// Each line in the file corresponds to a single element of the array.
/// </summary>
/// <param name="array">The single or multi-dimensional array to populate.</param>
/// <param name="filePath">The path of the file from which to load the array.</param>
/// <param name="encoding">Encoding used for reading the text file.</param>
/// <exception cref="ArgumentNullException">Thrown when the array is null.</exception>
/// <exception cref="ArgumentException">Thrown when the file path is null or whitespace.</exception>
public static void LoadFromTextFile(this Array array,
                                    string filePath,
                                    Encoding? encoding = null)
{
    // Validate the array and file path before proceeding.
    ValidateArrayAndFilePath(array, filePath);

    try
    {
        // Start the process of loading the array from the text file.
        LoadArrayFromTextFile(array, filePath, encoding);
    }
    catch (Exception ex)
    {
        // Throw an exception if an error occurs during file reading.
        throw new ArgumentException(
            $"An error occurred while loading from file: {ex.Message}");
    }
}

LoadFromTextFile Method

  • This method loads data from a text file and populates the specified array.
  • Similar to SaveToTextFile, it validates the input and then calls the LoadArrayFromTextFile method to perform the loading.

Internal Method for Loading

 /// <summary>
 /// Loads the single or multi-dimensional array from the specified file.
 /// Each line in the file corresponds to a single element of the array.
 /// </summary>
 /// <param name="array">The single or multi-dimensional array to populate.</param>
 /// <param name="filePath">The path of the file from which to load the array.</param>
 /// <param name="encoding">Encoding used for reading the text file.</param>
 private static void LoadArrayFromTextFile(Array array,
                                           string filePath,
                                           Encoding? encoding)
 {
     try
     {
         // Check encoding and set the default encoding if not specified.
         // Default encoding is UTF-8.
         encoding ??= Encoding.UTF8;

         // Read all lines from the specified file using the specified encoding.
         var lines = File.ReadAllLines(filePath, encoding);

         // Get the element type of the array.
         var elementType = array.GetType().GetElementType();

         // Validate that the number of lines read matches
         // the total number of elements in the array.
         // This also accounts for complex types with multiple properties.
         ValidateArrayAndFileLength(array.Length, lines.Length, elementType!);

         // Fill the array with the values from the lines.
         var lineIndex = 0;
         FillArray(array,
                   lines,
                   ref lineIndex,
                   new int[array.Rank],
                   0,
                   elementType!);
     }
     catch (Exception ex)
     {
         // Throw an exception if an error occurs during file reading.
         throw new ArgumentException(
             $"An error occurred while loading from file: {ex.Message}");
     }
 }

LoadArrayFromTextFile Method

  • This method reads all lines from the specified text file and validates the number of lines against the total number of elements in the array.
  • It retrieves the element type of the array and calls the FillArray method to populate the array using the lines read from the file.

Filling the Array with Loaded Values

 /// <summary>
 /// Fills the single or multi-dimensional array with values from the lines.
 /// </summary>
 /// <param name="array">The single or multi-dimensional array to populate.</param>
 /// <param name="lines">The lines from the file.</param>
 /// <param name="lineIndex">The current line index being processed.</param>
 /// <param name="indices">Current indices for the single or multi-dimensional array.</param>
 /// <param name="currentDimension">The current dimension being processed.</param>
 /// <param name="elementType">The type of elements in the array.</param>
 private static void FillArray(Array array,
                               string[] lines,
                               ref int lineIndex,  // Pass lineIndex by reference
                               int[] indices,
                               int currentDimension,
                               Type elementType)
 {
     // If we reach the last dimension, fill the values directly.
     if (currentDimension == array.Rank - 1)
     {
         for (int i = 0; i < array.GetLength(currentDimension); i++)
         {
             indices[currentDimension] = i;
             if (lineIndex < lines.Length)
             {
                 // Deserialize object from consecutive lines
                 object? deserializedValue = DeserializeObject(lines,
                                                               ref lineIndex,
                                                               elementType);

                 array.SetValue(deserializedValue, indices);
             }
         }
     }
     else
     {
         // Recur for the next dimension.
         for (int i = 0; i < array.GetLength(currentDimension); i++)
         {
             indices[currentDimension] = i;
             FillArray(array,
                       lines,
                       ref lineIndex,  // Pass lineIndex by reference
                       indices,
                       currentDimension + 1,
                       elementType);
         }
     }
 }

 #endregion

FillArray Method

  • This method fills the array with values from the lines read from the text file.
  • It checks if the current dimension is the last one. If so, it iterates over its length, using the DeserializeObject method to convert the corresponding line into the appropriate type and fill each index with the resulting value., handling the null representation appropriately.
  • If not at the last dimension, it recursively calls itself for the next dimension, updating the indices accordingly.

Deserialization

/// <summary>
/// Deserializes an object from the lines.
/// </summary>
/// <param name="lines">The lines from the file.</param>
/// <param name="lineIndex">The current line index being processed.</param>
/// <param name="targetType">The type of the object to deserialize.</param>
/// <returns>The deserialized object.</returns>
private static object? DeserializeObject(string[] lines,
                                         ref int lineIndex,
                                         Type targetType)
{
    if (lineIndex >= lines.Length)
    {
        return null; 
        // or throw an exception, based on your design choice
    }

    if (targetType == typeof(string))
    {
        // Check if the current line is "null", and return null if it is
        if (lines[lineIndex] == _nullRepresentation)
        {
            lineIndex++; // Move to the next line
            return null;
        }

        return lines[lineIndex++];
    }

    if (targetType.IsPrimitive)
    {
        // Check if the current line is "null", and return null if it is
        if (lines[lineIndex] == _nullRepresentation)
        {
            lineIndex++; // Move to the next line
            return null;
        }

        return Convert.ChangeType(lines[lineIndex++], targetType);
    }

    // Create an instance of the target object
    var obj = Activator.CreateInstance(targetType);
    var properties = targetType.GetProperties();

    foreach (var property in properties)
    {
        if (lineIndex < lines.Length)
        {
            // Read the current line
            string lineValue = lines[lineIndex++];

            // If the value is represented as 'null',
            // assign null to the property
            if (lineValue == _nullRepresentation)
            {
                property.SetValue(obj, null);
            }
            else
            {
                // Get the property type
                Type propertyType = property.PropertyType;

                // Check if the property type is nullable
                bool isNullable = Nullable.GetUnderlyingType(propertyType) != null;

                // Handle non-null values
                Type? targetTypeToConvert = isNullable
                    ? Nullable.GetUnderlyingType(propertyType)
                    : propertyType;

                // Convert the value to the correct type
                var convertedValue = Convert.ChangeType(lineValue, targetTypeToConvert!);

                // Set the property value
                property.SetValue(obj, convertedValue);
            }
        }
        else
        {
            throw new IndexOutOfRangeException(
                $"Not enough lines to deserialize all properties.");
        }
    }

    return obj;
}

DeserializeObject Method

The DeserializeObject method plays a critical role in converting lines from the text file back into the appropriate types for the array.

  • It first checks if the current line represents a null value (i.e., if it equals the _nullRepresentation string).
  • Depending on the type of the target element (string, primitive, or custom type), it appropriately converts and returns the value.
  • For custom types, it uses reflection to create an instance and set each property value based on the corresponding lines from the text file.

Validation

Two validation methods ensure data consistency.

  • ValidateArrayAndFilePath: Ensures the array and file path are not null or invalid.
  • ValidateArrayAndFileLength: Confirms the number of lines in the file matches the number of expected elements in the array, accounting for custom objects with multiple properties.

Validation of Array Length

Before populating the array, the method ValidateArrayAndFileLength ensures that the number of lines read matches the total expected elements in the array. This validation is crucial for preventing data corruption, especially when dealing with complex objects that may have multiple properties.

/// <summary>
/// Validates the number of lines read from the file 
/// against the total elements in the array, 
/// accounting for custom object properties.
/// </summary>
/// <param name="totalElements">Total number of elements in the array.</param>
/// <param name="lines">Number of lines read from the file.</param>
/// <param name="elementType">The type of elements in the array.</param>
/// <exception cref="ArgumentException">
/// Thrown when the number of lines does not match the number of elements in the array.
/// </exception>
private static void ValidateArrayAndFileLength(int totalElements,
                                               int lines,
                                               Type elementType)
{
    int expectedLines = totalElements;

    // Check if the element type is a custom class (i.e., not primitive or string).
    if (!elementType.IsPrimitive && elementType != typeof(string))
    {
        // For custom types, calculate the expected number of lines
        // as the total number of elements multiplied by the number of properties.
        int propertyCount = elementType.GetProperties().Length;
        expectedLines *= propertyCount;
    }

    // Compare the number of lines in the file with the expected number of lines.
    if (lines != expectedLines)
    {
        throw new ArgumentException(
            $"The number of lines in the file ({lines}) " +
            $"does not match the expected number of lines " +
            $"({expectedLines}) for the array.");
    }
}

Validation of Array and File Path

Before populating or saving the array, the method ValidateArrayAndFilePath ensures that the array and file path are not null.

/// <summary>
/// Validates the input array and file path for null or invalid values.
/// </summary>
/// <param name="array">The array to validate.</param>
/// <param name="filePath">The file path to validate.</param>
/// <exception cref="ArgumentNullException">Thrown when the array is null.</exception>
/// <exception cref="ArgumentException">Thrown when the file path is null or whitespace.</exception>
private static void ValidateArrayAndFilePath(Array array, string filePath)
{
    if (array == null)
    {
        throw new ArgumentNullException(
            nameof(array),
            "Array cannot be null.");
    }

    if (string.IsNullOrWhiteSpace(filePath))
    {
        throw new ArgumentException(
            "File path cannot be null or whitespace.",
            nameof(filePath));
    }
}

Using ArrayIOExtensionsLib in Phone Book

Download PhoneBook Open Source Solution

The PhoneBook application effectively demonstrates how to utilize the ArrayIOExtensionsLib to manage a simple phone book using a file-based approach. By organizing data in a 2D array and handling persistence through text files, the application remains lightweight and straightforward, avoiding the complexities of database management. This design allows users to easily edit and save contact information while ensuring data integrity and accessibility.

Database management

The code implements a Windows Forms application for managing a phone book, utilizing a 2D string array for in-memory storage and a text file for persistent data management, leveraging ArrayIOExtensionsLib for efficient serialization.

using ArrayIOExtensionsLib;

namespace PhoneBook
{
    public partial class Form1 : Form
    {
        #region Properties

        /// <summary>
        /// Path to the phone book file where data is stored.
        /// </summary>
        private const string PhoneBookFilePath = "PhoneBook.txt";

        /// <summary>
        /// Maximum number of entries allowed in the phone book.
        /// </summary>
        private const int MaxEntries = 10000;

        /// <summary>
        /// Number of columns representing the data fields: 
        /// FirstName, LastName, PhoneNumber, and Address.
        /// </summary>
        private const int ColumnCount = 4;

        /// <summary>
        /// 2D array to hold phone book data temporarily.
        /// </summary>
        private string?[,] PhoneBookData = new string?[MaxEntries, ColumnCount];

        #endregion

        /// <summary>
        /// Initializes the form 
        /// and loads the phone book data into the DataGridView.
        /// </summary>
        public Form1()
        {
            InitializeComponent();
            InitializePhoneBook();
        }

        /// <summary>
        /// Loads the phone book data from a file into the DataGridView. 
        /// If the file does not exist, a new file is created from
        /// the 2D array with default values.
        /// </summary>
        private void InitializePhoneBook()
        {
            // Initialize the array
            // with correct initial or default values
            for (int i = 0; i < MaxEntries; i++)
            {
                for (int j = 0; j < ColumnCount; j++)
                {
                    PhoneBookData[i, j] = default!;
                }
            }

            Table_DataGridView.Rows.Clear();
            Table_DataGridView.RowCount = MaxEntries;

            if (File.Exists(PhoneBookFilePath))
            {
                LoadPhoneBookData();
            }
            else
            {
                // If no file exists,
                // initialize a new phone book file
                SavePhoneBookData();
            }

            DisplayDataInGridView();
        }

        /// <summary>
        /// Saves the phone book data to the text file.
        /// </summary>
        private void SavePhoneBookData()
        {
            PhoneBookData.SaveToTextFile(filePath: PhoneBookFilePath);
        }

        /// <summary>
        /// Loads the phone book data from text file.
        /// </summary>
        private void LoadPhoneBookData()
        {
            PhoneBookData.LoadFromTextFile(filePath: PhoneBookFilePath);
        }

        /// <summary>
        /// Updates the 2D array with data from the DataGridView 
        /// and saves the updated data to the file.
        /// Triggered when a user finishes editing a cell in the DataGridView.
        /// </summary>
        private void Table_DataGridView_CellEndEdit(object sender, DataGridViewCellEventArgs e)
        {
            // Save the edited cell value to the array
            PhoneBookData[e.RowIndex, e.ColumnIndex] =
                Table_DataGridView.Rows[e.RowIndex].Cells[e.ColumnIndex].Value?.ToString();

            SavePhoneBookData();
        }

        /// <summary>
        /// Copies the data from the 2D array into the DataGridView for display.
        /// </summary>
        private void DisplayDataInGridView()
        {
            for (int row = 0; row < MaxEntries; row++)
            {
                for (int col = 0; col < ColumnCount; col++)
                {
                    Table_DataGridView.Rows[row].Cells[col].Value = PhoneBookData[row, col];
                }
            }
        }
    }
}

Moving from 2D String Arrays to Object-Oriented Data Management

In the above version of our Phone Book application, we relied on a simple 2D string array to store and manipulate Phone Book data. Each entry (row) in the array represented a record with values for FirstName, LastName, PhoneNumber, and Address. This approach worked but had limitations, particularly when it came to maintaining code readability, flexibility, and scalability.

In the updated version, we have transitioned to using a PhoneBookEntry class, which represents a single entry in the phone book as an object. This shift provides several key benefits:

  1. Code Readability: With named properties like FirstName, LastName, PhoneNumber, and Address, it's much easier to understand what each field represents. There's no need to remember which column index refers to which piece of data, as was the case with the 2D array.
  2. Improved Flexibility: By using a class, we can easily add new fields (e.g., an email address or birthdate) without having to modify array-based logic across the entire application. This makes the solution more adaptable for future needs.
  3. Better Data Handling: Managing individual entries as objects allows for more sophisticated operations, like validation, sorting, or filtering, which would have been cumbersome with raw string arrays.
  4. IMPORTANT: Values in the array must be properly initialized before calling the save methods, especially when working with arrays of complex or custom types. If not properly initialized, null values in objects may lead to incorrect written values in the text file, as the object will not be serialized with all its properties.

Here is the definition of the new PhoneBookEntry class.

namespace PhoneBook
{
    public class PhoneBookEntry
    {
        public string? FirstName { get; set; }
        public string? LastName { get; set; }
        public string? PhoneNumber { get; set; }
        public string? Address { get; set; }
    }
}

And updated code for the main class.

using ArrayIOExtensionsLib;

namespace PhoneBook
{
    public partial class Form1 : Form
    {
        #region Properties

        /// <summary>
        /// Path to the phone book file where data is stored.
        /// </summary>
        private const string PhoneBookFilePath = "PhoneBook.txt";

        /// <summary>
        /// Maximum number of entries allowed in the phone book.
        /// </summary>
        private const int MaxEntries = 10000;

        /// <summary>
        /// 1D array to hold phone book data temporarily.
        /// </summary>
        private PhoneBookEntry[] PhoneBookData = new PhoneBookEntry[MaxEntries];

        #endregion

        /// <summary>
        /// Initializes the form 
        /// and loads the phone book data into the DataGridView.
        /// </summary>
        public Form1()
        {
            InitializeComponent();
            InitializePhoneBook();
        }

        /// <summary>
        /// Loads the phone book data from a file into the DataGridView. 
        /// If the file does not exist, a new file is created.
        /// </summary>
        private void InitializePhoneBook()
        {
            // Initialize the 1D array with empty PhoneBookEntry objects
            for (int i = 0; i < MaxEntries; i++)
            {
                PhoneBookData[i] = new PhoneBookEntry();
            }

            Table_DataGridView.Rows.Clear();
            Table_DataGridView.RowCount = MaxEntries;

            if (File.Exists(PhoneBookFilePath))
            {
                LoadPhoneBookData();
            }
            else
            {
                // If no file exists,
                // initialize a new phone book file
                SavePhoneBookData();
            }

            DisplayDataInGridView();
        }

        /// <summary>
        /// Saves the phone book data to the text file.
        /// </summary>
        private void SavePhoneBookData()
        {
            PhoneBookData.SaveToTextFile(filePath: PhoneBookFilePath);
        }

        /// <summary>
        /// Loads the phone book data from the text file.
        /// </summary>
        private void LoadPhoneBookData()
        {
            PhoneBookData.LoadFromTextFile(filePath: PhoneBookFilePath);
        }

        /// <summary>
        /// Updates the 1D array with data from the DataGridView 
        /// and saves the updated data to the file.
        /// Triggered when a user finishes editing a cell in the DataGridView.
        /// </summary>
        private void Table_DataGridView_CellEndEdit(object sender, DataGridViewCellEventArgs e)
        {
            // Save the edited cell value to the array
            switch (e.ColumnIndex)
            {
                case 0:
                    PhoneBookData[e.RowIndex].FirstName = 
                        Table_DataGridView[e.ColumnIndex, e.RowIndex].Value?.ToString();
                    break;
                case 1:
                    PhoneBookData[e.RowIndex].LastName = 
                        Table_DataGridView[e.ColumnIndex, e.RowIndex].Value?.ToString();
                    break;
                case 2:
                    PhoneBookData[e.RowIndex].PhoneNumber = 
                        Table_DataGridView[e.ColumnIndex, e.RowIndex].Value?.ToString();
                    break;
                case 3:
                    PhoneBookData[e.RowIndex].Address = 
                        Table_DataGridView[e.ColumnIndex, e.RowIndex].Value?.ToString();
                    break;
            }

            SavePhoneBookData();
        }

        /// <summary>
        /// Copies the data from the 2D array into the DataGridView for display.
        /// </summary>
        private void DisplayDataInGridView()
        {
            for (int row = 0; row < MaxEntries; row++)
            {
                Table_DataGridView[0, row].Value = PhoneBookData[row].FirstName;
                Table_DataGridView[1, row].Value = PhoneBookData[row].LastName;
                Table_DataGridView[2, row].Value = PhoneBookData[row].PhoneNumber;
                Table_DataGridView[3, row].Value = PhoneBookData[row].Address;
            }
        }
    }
}

By encapsulating the data for each entry in this class, we now have a more robust and maintainable codebase. This is especially useful for saving and loading the phone book data, where each entry can be serialized or deserialized as an object rather than handling each piece of data individually as a string.

This update is a key step toward improving the overall structure and maintainability of the Phone Book application, setting the stage for future enhancements.

Conclusion

The ArrayIOExtensionsLib class provides a robust and efficient way to manage data in applications like the Phone Book by simplifying the saving and loading of single and multi-dimensional arrays. This approach eliminates the need for complex database setups while still allowing for effective data storage and retrieval.

With its straightforward extension methods, the library ensures that developers can easily handle array data, including the management of null values, through a simple and intuitive API. By leveraging this library, applications can maintain data integrity and provide a seamless user experience.


Similar Articles