数组到文本文件:ArrayIOExtensionsLib 类的详细概述

发布:2024-10-25 15:01 阅读:10 点赞:0

介绍

在现代软件开发中,高效的数据管理至关重要。虽然关系数据库管理系统(如 SQL 服务器)通常是首选解决方案,但更简单的场景有时需要更轻量级的方法。例如,不需要关系数据库复杂性的应用程序可以从使用文本文件进行数据存储中受益。我们的电话簿应用程序就是这种情况,它使用简单的 2D 表管理数据。为了简化保存和检索数据的过程,我们开发了 ArrayIOExtensionsLib,这是一个提供易于使用的方法来处理存储在文本文件中的数据的库。

ArrayIOExtensionsLib 概述

ArrayIOExtensionsLib 是一个 .NET 库,旨在简化文本文件中存储的数组的处理。它提供了扩展方法,用于将简单类型(如字符串和基元)的一维和多维数组保存到文本文件或从文本文件加载。此库非常适合不需要关系数据库但仍需要可靠且高效的数据存储和检索的应用程序。

主要特点

  • 保存和加载多维数组:将任意等级(一维、二维等)的数组保存到文本文件中,其中每个元素都存储为单独的行。
  • 仅支持简单类型:适用于字符串数组、基元(int、double 等)以及仅包含简单属性的自定义对象类型。
  • 固定长度数组:该库使用固定长度数组。程序员必须预测数组在运行时将容纳的最大记录数,因为数组无法动态调整大小。如果需要扩展数组的大小,则必须创建一个新的更大的数组,并将现有数据复制到新数组。这可以通过手动调整数组大小并传输数据来完成,也可以通过实现逻辑来定期检查数组是否已满并根据需要执行调整大小操作。
  • 空处理:空值在文本文件中序列化为“null”,并正确地反序列化回数组。
  • 可定制的编码:允许在保存和加载文件时指定编码(默认为 UTF-8)。

重要提示。使用此库中的扩展方法之前,必须正确初始化所有数组字段。未初始化的数组或空值可能会导致序列化和反序列化期间出现意外行为。

安装

在您的项目中使用这个库。

  1. 下载 ArrayIOExtensionsLib 开源档案
  2. 构建项目以生成.dll 文件。
  3. 在您自己的项目中引用生成的.dll。
  4. 在 Visual Studio 中,右键单击您的项目 > 添加 > 引用 > 浏览以选择生成的 .dll。
  5. 在 .NET CLI 中,运行以下命令。
    dotnet add reference /path/to/ArrayIOExtensions.dll

用法

将数组保存到文本文件。

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

myArray.SaveToTextFile("output.txt");

从文本文件加载数组。

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

保存自定义类型。

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");

正在加载自定义类型。

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

代码解析

这里对ArrayIOExtensions类代码逐个方法进行了详细的解释。

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
    }
}

特性

ArrayIOExtensionsLib 库依赖于几个关键属性来确保文件 I/O 过程中的顺利运行:

  • _defaultEncoding:指定 UTF-8 作为保存和读取文本文件的默认编码格式。
  • _nullRepresentation:定义文本文件中如何表示空值。默认情况下,使用字符串“null”。

将数组保存到文本文件

保存数组的主要方法是SaveToTextFile。

重要提示。在调用保存方法之前,必须正确初始化数组中的值,尤其是在处理复杂或自定义类型的数组时。如果没有正确初始化,对象中的空值可能会导致文本文件中写入的值不正确,因为对象不会与其所有属性一起序列化。

#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 方法

  • 此方法将数组(单维或多维)保存到指定的文本文件。
  • 它首先使用 ValidateArrayAndFilePath 方法验证输入数组和文件路径。
  • 然后检查指定的编码,如果未提供,则默认为 UTF-8。
  • StreamWriter 用于通过调用 SaveArrayToTextFile 方法将数组的每个元素写入文本文件的新行。

内部储蓄方法

/// <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 方法

  • 此方法初始化一个数组来保存输入数组各个维度的长度。
  • 然后调用 SaveArrayToTextFileRecursive 方法来处理数组元素的递归保存。

递归保存逻辑

/// <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 方法

  • 此方法处理将数组元素递归写入 StreamWriter。
  • 它首先检查数组是否是单维的,如果是,则直接保存值。
  • 对于多维数组,它从最后一维写入值并递归到前几个维度,从而建立索引。

序列化

/// <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 方法

  • 此方法处理单个对象的序列化,并将其属性写入文本文件。
  • 如果对象为空,则写入“null”;否则,它将对象的值或每个属性写入新行。

重要提示。在调用保存方法之前,必须正确初始化数组中的值,尤其是在处理复杂或自定义类型的数组时。如果没有正确初始化,对象中的空值可能会导致文本文件中写入的值不正确,因为对象不会与其所有属性一起序列化。

从文本文件加载数组

加载数组的方法是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 方法

  • 此方法从文本文件加载数据并填充指定的数组。
  • 与SaveToTextFile类似,它验证输入,然后调用LoadArrayFromTextFile方法执行加载。

内部加载方法

/// <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 方法

  • 此方法从指定的文本文件读取所有行,并根据数组中元素的总数验证行数。
  • 它检索数组的元素类型并调用 FillArray 方法使用从文件读取的行来填充数组。

使用加载的值填充数组

/// <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 方法

  • 此方法使用从文本文件读取的行中的值填充数组。
  • 检查当前维度是否为最后一个维度。如果是,则遍历其长度,使用 DeserializeObject 方法将相应行转换为适当的类型,并用结果值填充每个索引,并适当处理空表示。
  • 如果不在最后一个维度,它会递归调用自身到下一个维度,并相应地更新索引。

反序列化

/// <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 方法

DeserializeObject 方法在将文本文件中的行转换回数组的适当类型时起着关键作用。

  • 它首先检查当前行是否代表空值(即,它是否等于 _nullRepresentation 字符串)。
  • 根据目标元素的类型(字符串、原始或自定义类型),它会适当地转换并返回值。
  • 对于自定义类型,它使用反射来创建实例并根据文本文件中的相应行设置每个属性值。

验证

两种验证方法确保数据的一致性。

  • ValidateArrayAndFilePath:确保数组和文件路径不为空或无效。
  • ValidateArrayAndFileLength:确认文件中的行数与数组中预期元素的数量相匹配,并考虑具有多个属性的自定义对象。

数组长度验证

在填充数组之前,方法 ValidateArrayAndFileLength 确保读取的行数与数组中预期的元素总数相匹配。此验证对于防止数据损坏至关重要,尤其是在处理可能具有多个属性的复杂对象时。

/// <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.");
    }
}

数组和文件路径的验证

在填充或保存数组之前,方法 ValidateArrayAndFilePath 确保数组和文件路径不为空。

/// <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));
    }
}

在电话簿中使用 ArrayIOExtensionsLib

下载 PhoneBook 开源解决方案

PhoneBook 应用程序有效地演示了如何利用 ArrayIOExtensionsLib 通过基于文件的方法管理简单的电话簿。通过在二维数组中组织数据并通过文本文件处理持久性,该应用程序保持轻量级和简单,避免了数据库管理的复杂性。这种设计允许用户轻松编辑和保存联系信息,同时确保数据完整性和可访问性。

数据库管理

该代码实现了一个用于管理电话簿的 Windows 窗体应用程序,利用二维字符串数组进行内存存储,利用文本文件进行持久数据管理,利用 ArrayIOExtensionsLib 进行高效序列化。

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];
                }
            }
        }
    }
}

从二维字符串数组转向面向对象的数据管理

在我们电话簿应用程序的上述版本中,我们依靠一个简单的 2D 字符串数组来存储和操作电话簿数据。数组中的每个条目(行)代表一条记录,其中包含 FirstName、LastName、PhoneNumber 和 Address 的值。这种方法有效,但有局限性,特别是在保持代码的可读性、灵活性和可扩展性方面。

在更新版本中,我们已过渡到使用 PhoneBookEntry 类,该类将电话簿中的单个条目表示为对象。此转变提供了几个主要好处:

  1. 代码可读性:有了 FirstName、LastName、PhoneNumber 和 Address 等命名属性,可以更轻松地理解每个字段代表什么。无需记住哪个列索引指向哪个数据,就像 2D 数组的情况一样。
  2. 提高灵活性:通过使用类,我们可以轻松添加新字段(例如电子邮件地址或出生日期),而无需修改整个应用程序中基于数组的逻辑。这使解决方案更能适应未来的需求。
  3. 更好的数据处理:将单个条目作为对象进行管理可以实现更复杂的操作,比如验证、排序或过滤,而这些操作如果使用原始字符串数组则会非常麻烦。
  4. 重要提示:在调用保存方法之前,必须正确初始化数组中的值,尤其是在处理复杂或自定义类型的数组时。如果未正确初始化,对象中的空值可能会导致文本文件中写入的值不正确,因为对象不会与其所有属性一起序列化。

这是新 PhoneBookEntry 类的定义。

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; }
    }
}

并更新了主类的代码。

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;
            }
        }
    }
}

通过将每个条目的数据封装在这个类中,我们现在拥有了更强大且更易于维护的代码库。这对于保存和加载电话簿数据特别有用,其中每个条目都可以作为对象进行序列化或反序列化,而不是将每条数据单独作为字符串处理。

此次更新是改善电话簿应用程序整体结构和可维护性的关键一步,为未来的增强奠定了基础。

结论

ArrayIOExtensionsLib 类通过简化单维和多维数组的保存和加载,提供了一种强大而有效的方法来管理电话簿等应用程序中的数据。这种方法无需复杂的数据库设置,同时仍允许有效的数据存储和检索。

该库具有简单的扩展方法,可确保开发人员通过简单直观的 API 轻松处理数组数据,包括管理空值。通过利用此库,应用程序可以保持数据完整性并提供无缝的用户体验。