I am downloading .tgz file from the remote server to a folder locally and then unzipping it out. After that I read all those json/txt files in memory. Below is my code which does that:
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
};
}
}
Use Case:
- If I am not able to download files successfully (
isDownloadSuccessis false) then I want to return empty IEnumerable back. - If I am not able to unzip files successfully (
isUnzipSuccessis false) then I want to return empty IEnumerable back as well. - If I am not able to get list of files successfully (
fileslist is empty) then I want to return empty IEnumerable back as well. - If I had some processing issues in the for loop then I want to return empty IEnumerable back as well.
- Otherwise just return readonly IEnumerable back to the caller with data in it.
Problem I am having with above approach is – I cannot do empty check in the cases when it returns yield return default and also I am confuse on what happens if processing fails in for loop, will it return empty IEnumerable as well back to the caller?
IEnumerable<DataHolder> dataHolders = GetFiles(fileName);
// below check doesn't work on negative cases
if (dataHolders == null || !dataHolders.Any())
return false;
//....
So is this the right way to use IEnumerable here or I can use any other data structure which can provide read only list to the caller along with empty list (for negative cases) which I can easily check for null or empty.
Question:
My goal is just to return read only list back to the user with data in it (for positive cases). And for all negative cases, I need to return back empty read only list to the user.
Answers:
Thank you for visiting the Q&A section on Magenaut. Please note that all the answers may not help you solve the issue immediately. So please treat them as advisements. If you found the post helpful (or not), leave a comment & I’ll get back to you as soon as possible.
Method 1
We talked in chat, but will reiterate.
Yield doesn’t really work here as we don’t really need those semantics for any specific reason. You want to get a list of files to use later for comparing against other lists of files (they all have to be read in to memory eventually, may as well do it now):
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();
}
We are now just returning a read-only list of all your items, which allows you to do checks if any items exist, such as:
if(lst?.Count > 0){ /* There are items to process */ }
Also, this doesn’t break your pattern as IReadOnlyList implements IEnumerable, so it will fit in quite nicely.
Method 2
Since you download and unzip all files at once, I understand that you’re not concerned about this implementation being an actual iteratable (as a foreach would wait until everything is done before being able to iterate).
Keeping that in mind, the easiest you can do is to get rid of yields and return arrays.
Sample implementation (might need some spell check):
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>();
}
}
For consuming this, you would, for example:
var files = GetFiles("somename.txt");
if (!files.Any()) // do not check for files being null
{
return;
}
Side note, I would change the first few lines into this, so you don’t do sync-over-async which can cause deadlocks:
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>(); }
...
}
All methods was sourced from stackoverflow.com or stackexchange.com, is licensed under cc by-sa 2.5, cc by-sa 3.0 and cc by-sa 4.0