使用 IEnumerable 作为只读列表是否更好?

Is it better to use IEnumerable as read only list?

我正在从远程服务器下载 .tgz 文件到本地文件夹,然后将其解压缩。之后,我读取了内存中的所有 json/txt 个文件。下面是我的代码:

public IEnumerable<DataHolder> GetFiles(string fileName)
{
    // this will download files to a directory
    var isDownloadSuccess = DownloadFiles(_url, fileName, _directoryToDownload);
    if (!isDownloadSuccess.Result) { yield return default; }

    // this will unzip files in same directory
    var isUnzipSuccess = UnzipTgzFile(_directoryToDownload, fileName);
    if (!isUnzipSuccess) { yield return default; }

    // this will get list of all files in same directory
    IList<string> files = GetListOfFiles(_directoryToDownload);
    if (files == null || files.Count == 0) { yield return default; }

    // total files will be 500 max
    for (int i = 0; i < files.Count; i++)
    {
        var cfgPath = files[i];
        if (!File.Exists(cfgPath)) { continue; }
        var fileDate = File.GetLastWriteTimeUtc(cfgPath);
        var fileContent = File.ReadAllText(cfgPath);
        var pathPieces = cfgPath.Split(System.IO.Path.DirectorySeparatorChar, StringSplitOptions.RemoveEmptyEntries);
        var fileName = pathPieces[pathPieces.Length - 1];
        var md5Hash = CheckMD5(cfgPath);
        yield return new DataHolder
        {
            FileName = fileName,
            FileDate = fileDate,
            FileContent = fileContent,
            FileMD5HashValue = md5Hash
        };
    }
}

用例:

我在使用上述方法时遇到的问题是 - 我无法在 returns yield return default 的情况下进行空检查,而且我对 for 循环中处理失败会发生什么感到困惑, return 清空 IEnumerable 也会返回给调用者吗?

IEnumerable<DataHolder> dataHolders = GetFiles(fileName);
// below  check doesn't work on negative cases
if (dataHolders == null || !dataHolders.Any())
    return false;

//....

那么这是在这里使用 IEnumerable 的正确方法吗?或者我可以使用任何其他数据结构,它可以向调用者提供 只读列表 以及空列表(对于负面情况)我可以轻松检查是否为 null 或空。

问题:

我的目标只是 return read only list 将数据返回给用户(对于正面案例)。对于所有负面情况,我需要 return 将空的只读列表返回给用户。

我们在聊天中谈过,但会重申。

Yield 在这里不起作用,因为出于任何特定原因我们并不真正需要这些语义。您想获得一个文件列表,以便稍后用于与其他文件列表进行比较(它们最终都必须读入内存,现在也可以这样做):

public IReadOnlyList<DataHolder> GetFiles(string fileName)
{
    // this will download files to a directory
    var isDownloadSuccess = DownloadFiles(_url, fileName, _directoryToDownload);
    if (!isDownloadSuccess.Result) { return Array.Empty<DataHolder>(); }

    // this will unzip files in same directory
    var isUnzipSuccess = UnzipTgzFile(_directoryToDownload, fileName);
    if (!isUnzipSuccess) { return Array.Empty<DataHolder>(); }

    // this will get list of all files in same directory
    IList<string> files = GetListOfFiles(_directoryToDownload);
    if (files == null || files.Count == 0) { return Array.Empty<DataHolder>(); }

    var lst = new List<DataHolder>(files.Count);

    for (int i = 0; i < files.Count; i++)
    {
        var cfgPath = files[i];
        if (!File.Exists(cfgPath)) { continue; }
        var fileDate = File.GetLastWriteTimeUtc(cfgPath);
        var fileContent = File.ReadAllText(cfgPath);
        var pathPieces = cfgPath.Split(System.IO.Path.DirectorySeparatorChar, StringSplitOptions.RemoveEmptyEntries);
        var fileName = pathPieces[pathPieces.Length - 1];
        var md5Hash = CheckMD5(cfgPath);
        lst.Add(new DataHolder
        {
            FileName = fileName,
            FileDate = fileDate,
            FileContent = fileContent,
            FileMD5HashValue = md5Hash
        });
    }
    return lst.AsReadOnly();
}

我们现在只返回一个包含您所有项目的 read-only 列表,这样您就可以检查是否存在任何项目,例如:

if(lst?.Count > 0){ /* There are items to process */ }

此外,这不会破坏您的模式,因为 IReadOnlyList 实现了 IEnumerable,因此它会非常适合。

由于您一次下载并解压缩了所有文件,我知道您并不担心此实现是一个实际可迭代的(因为 foreach 会等到一切都完成才能迭代) .
记住这一点,最简单的方法就是去掉 yields 和 return 数组。

实施示例(可能需要一些拼写检查):

public IEnumerable<DataHolder> GetFiles(string fileName)
{
    // this will download files to a directory
    var isDownloadSuccess = DownloadFiles(_url, fileName, _directoryToDownload);
    if (!isDownloadSuccess.Result) { return Array.Empty<DataHolder>(); }

    // this will unzip files in same directory
    var isUnzipSuccess = UnzipTgzFile(_directoryToDownload, fileName);
    if (!isUnzipSuccess) { return Array.Empty<DataHolder>(); }

    // this will get list of all files in same directory
    IList<string> files = GetListOfFiles(_directoryToDownload);
    if (files == null || files.Count == 0) { return Array.Empty<DataHolder>(); }

    var data = new DataHolder[files.Count];

    try
    {
        for (int i = 0; i < files.Count; i++)
        {
            var cfgPath = files[i];
            if (!File.Exists(cfgPath)) { continue; }
            var fileDate = File.GetLastWriteTimeUtc(cfgPath);
            var fileContent = File.ReadAllText(cfgPath);
            var pathPieces = cfgPath.Split(System.IO.Path.DirectorySeparatorChar, StringSplitOptions.RemoveEmptyEntries);
            var fileName = pathPieces[pathPieces.Length - 1];
            var md5Hash = CheckMD5(cfgPath);

            data[i] = new DataHolder
            {
                FileName = fileName,
                FileDate = fileDate,
                FileContent = fileContent,
                FileMD5HashValue = md5Hash
            };
        }

        return data;
    }
    catch (Exception ex)
    {
        return Array.Empty<DataHolder>();
    }
}

为了消费这个,你会,例如:

var files = GetFiles("somename.txt");
if (!files.Any()) // do not check for files being null
{
    return;
}

旁注,我会把前几行改成这样,所以你不要做 sync-over-async 这会导致死锁:

public async Task<IEnumerable<DataHolder>> GetFiles(string fileName)
{
    // this will download files to a directory
    var isDownloadSuccess = await DownloadFiles(_url, fileName, _directoryToDownload);
    if (!isDownloadSuccess) { return Array.Empty<DataHolder>(); }
    ...
}