使 using 语句可用于多个一次性 objects
Make using statement usable for multiple disposable objects
我在一个文件夹中有一堆文本文件,它们应该完全相同headers。换句话说,所有文件的前 100 行应该是相同的。所以我写了一个函数来检查这个条件:
private static bool CheckHeaders(string folderPath, int headersCount)
{
var enumerators = Directory.EnumerateFiles(folderPath)
.Select(f => File.ReadLines(f).GetEnumerator())
.ToArray();
//using (enumerators)
//{
for (int i = 0; i < headersCount; i++)
{
foreach (var e in enumerators)
{
if (!e.MoveNext()) return false;
}
var values = enumerators.Select(e => e.Current);
if (values.Distinct().Count() > 1) return false;
}
return true;
//}
}
我使用枚举器的原因是内存效率。我没有将所有文件内容加载到内存中,而是同时枚举文件 line-by-line 直到发现不匹配,或者检查了所有 headers。
注释的代码行很明显我的问题。我想利用 using
块来安全地处理所有枚举器,但不幸的是 using (enumerators)
无法编译。显然 using
只能处理一个一次性 object。我知道我可以通过将整个东西包装在 try-finally
块中手动处理枚举器,并且 运行 最后在循环中处理逻辑,但这看起来很尴尬。在这种情况下,我可以采用任何机制使 using
语句成为可行的选择吗?
更新
我刚刚意识到我的函数有一个严重的缺陷。枚举器的构造不稳健。锁定的文件可能会导致异常,而一些枚举器已经创建。这些枚举器不会被释放。这是我要解决的问题。我在想这样的事情:
var enumerators = Directory.EnumerateFiles(folderPath)
.ToDisposables(f => File.ReadLines(f).GetEnumerator());
扩展方法 ToDisposables
应确保在出现异常情况下不会遗留任何一次性物品。
我将建议一种使用递归调用 Zip
的方法,以允许对正常 IEnumerable<string>
进行并行枚举,而无需求助于使用 IEnumerator<string>
。
bool Zipper(IEnumerable<IEnumerable<string>> sources, int take)
{
IEnumerable<string> ZipperImpl(IEnumerable<IEnumerable<string>> ss)
=> (!ss.Skip(1).Any())
? ss.First().Take(take)
: ss.First().Take(take).Zip(
ZipperImpl(ss.Skip(1)),
(x, y) => (x == null || y == null || x != y) ? null : x);
var matching_lines = ZipperImpl(sources).TakeWhile(x => x != null).ToArray();
return matching_lines.Length == take;
}
现在建立你的 enumerables
:
IEnumerable<string>[] enumerables =
Directory
.EnumerateFiles(folderPath)
.Select(f => File.ReadLines(f))
.ToArray();
现在调用很简单了:
bool headers_match = Zipper(enumerables, 100);
这里是 运行 此代码针对三个超过 4 行的文件的踪迹:
Ben Petering at 5:28 PM ACST
Ben Petering at 5:28 PM ACST
Ben Petering at 5:28 PM ACST
From a call 2019-05-23, James mentioned he’d like the ability to edit the current shipping price rules (eg in shipping_rules.xml) via the admin.
From a call 2019-05-23, James mentioned he’d like the ability to edit the current shipping price rules (eg in shipping_rules.xml) via the admin.
From a call 2019-05-23, James mentioned he’d like the ability to edit the current shipping price rules (eg in shipping_rules.xml) via the admin.
He also mentioned he’d like to be able to set different shipping price rules for a given time window, e.g. Jan 1 to Jan 30.
He also mentioned he’d like to be able to set different shipping price rules for a given time window, e.g. Jan 1 to Jan 30.
He also mentioned he’d like to be able to set different shipping price rules for a given time window, e.g. Jan 1 to Jan 30.
These storyishes should be considered when choosing the appropriate module to use.
These storyishes should be considered when choosing the appropriate module to use.X
These storyishes should be considered when choosing the appropriate module to use.
请注意,当在第二个文件的第 4 行遇到不匹配 header 时,枚举将停止。然后所有枚举都停止了。
您可以在 enumerators
上创建一个 disposable-wrapper:
class DisposableEnumerable : IDisposable
{
private IEnumerable<IDisposable> items;
public event UnhandledExceptionEventHandler DisposalFailed;
public DisposableEnumerable(IEnumerable<IDisposable> items) => this.items = items;
public void Dispose()
{
foreach (var item in items)
{
try
{
item.Dispose();
}
catch (Exception e)
{
var tmp = DisposalFailed;
tmp?.Invoke(this, new UnhandledExceptionEventArgs(e, false));
}
}
}
}
并以对代码影响最小的方式使用它:
private static bool CheckHeaders(string folderPath, int headersCount)
{
var enumerators = Directory.EnumerateFiles(folderPath)
.Select(f => File.ReadLines(f).GetEnumerator())
.ToArray();
using (var disposable = new DisposableEnumerable(enumerators))
{
for (int i = 0; i < headersCount; i++)
{
foreach (var e in enumerators)
{
if (!e.MoveNext()) return false;
}
var values = enumerators.Select(e => e.Current);
if (values.Distinct().Count() > 1) return false;
}
return true;
}
}
问题是你无论如何都必须一个一个地单独处理这些对象。但这取决于您在哪里封装该逻辑。我建议的代码没有手册 try-finally
,)
按照@Alex 的建议创建IDisposable
包装器是正确的。它只需要一个逻辑来处理已经打开的文件,如果它们中的一些是 locked 并且可能是一些错误状态的逻辑。也许是这样的(错误状态逻辑很简单):
public class HeaderChecker : IDisposable
{
private readonly string _folderPath;
private readonly int _headersCount;
private string _lockedFile;
private readonly List<IEnumerator<string>> _files = new List<IEnumerator<string>>();
public HeaderChecker(string folderPath, int headersCount)
{
_folderPath = folderPath;
_headersCount = headersCount;
}
public string LockedFile => _lockedFile;
public bool CheckFiles()
{
_lockedFile = null;
if (!TryOpenFiles())
{
return false;
}
if (_files.Count == 0)
{
return true; // Not sure what to return here.
}
for (int i = 0; i < _headersCount; i++)
{
if (!_files[0].MoveNext()) return false;
string currentLine = _files[0].Current;
for (int fileIndex = 1; fileIndex < _files.Count; fileIndex++)
{
if (!_files[fileIndex].MoveNext()) return false;
if (_files[fileIndex].Current != currentLine) return false;
}
}
return true;
}
private bool TryOpenFiles()
{
bool result = true;
foreach (string file in Directory.EnumerateFiles(_folderPath))
{
try
{
_files.Add(File.ReadLines(file).GetEnumerator());
}
catch
{
_lockedFile = file;
result = false;
break;
}
}
if (!result)
{
DisposeCore(); // Close already opened files.
}
return result;
}
private void DisposeCore()
{
foreach (var item in _files)
{
try
{
item.Dispose();
}
catch
{
}
}
_files.Clear();
}
public void Dispose()
{
DisposeCore();
}
}
// Usage
using (var checker = new HeaderChecker(folderPath, headersCount))
{
if (!checker.CheckFiles())
{
if (checker.LockedFile is null)
{
// Error while opening files.
}
else
{
// Headers do not match.
}
}
}
我在检查行时也删除了 .Select()
和 .Distinct()
。第一个只是遍历 enumerators
数组 - 与它上面的 foreach
相同,所以你要枚举这个数组两次。然后创建一个新的行列表,.Distinct()
枚举它。
到问题的第二部分。如果我没看错,这应该足够了:
static class DisposableHelper
{
public static IEnumerable<TResult> ToDisposable<TSource, TResult>(this IEnumerable<TSource> source,
Func<TSource, TResult> selector) where TResult : IDisposable
{
var exceptions = new List<Exception>();
var result = new List<TResult>();
foreach (var i in source)
{
try { result.Add(selector(i)); }
catch (Exception e) { exceptions.Add(e); }
}
if (exceptions.Count == 0)
return result;
foreach (var i in result)
{
try { i.Dispose(); }
catch (Exception e) { exceptions.Add(e); }
}
throw new AggregateException(exceptions);
}
}
用法:
private static bool CheckHeaders(string folderPath, int headersCount)
{
var enumerators = Directory.EnumerateFiles(folderPath)
.ToDisposable(f => File.ReadLines(f).GetEnumerator())
.ToArray();
using (new DisposableEnumerable(enumerators))
{
for (int i = 0; i < headersCount; i++)
{
foreach (var e in enumerators)
{
if (!e.MoveNext()) return false;
}
var values = enumerators.Select(e => e.Current);
if (values.Distinct().Count() > 1) return false;
}
return true;
}
}
和
try
{
CheckHeaders(folderPath, headersCount);
}
catch(AggregateException e)
{
// Prompt to fix errors and try again
}
我在一个文件夹中有一堆文本文件,它们应该完全相同headers。换句话说,所有文件的前 100 行应该是相同的。所以我写了一个函数来检查这个条件:
private static bool CheckHeaders(string folderPath, int headersCount)
{
var enumerators = Directory.EnumerateFiles(folderPath)
.Select(f => File.ReadLines(f).GetEnumerator())
.ToArray();
//using (enumerators)
//{
for (int i = 0; i < headersCount; i++)
{
foreach (var e in enumerators)
{
if (!e.MoveNext()) return false;
}
var values = enumerators.Select(e => e.Current);
if (values.Distinct().Count() > 1) return false;
}
return true;
//}
}
我使用枚举器的原因是内存效率。我没有将所有文件内容加载到内存中,而是同时枚举文件 line-by-line 直到发现不匹配,或者检查了所有 headers。
注释的代码行很明显我的问题。我想利用 using
块来安全地处理所有枚举器,但不幸的是 using (enumerators)
无法编译。显然 using
只能处理一个一次性 object。我知道我可以通过将整个东西包装在 try-finally
块中手动处理枚举器,并且 运行 最后在循环中处理逻辑,但这看起来很尴尬。在这种情况下,我可以采用任何机制使 using
语句成为可行的选择吗?
更新
我刚刚意识到我的函数有一个严重的缺陷。枚举器的构造不稳健。锁定的文件可能会导致异常,而一些枚举器已经创建。这些枚举器不会被释放。这是我要解决的问题。我在想这样的事情:
var enumerators = Directory.EnumerateFiles(folderPath)
.ToDisposables(f => File.ReadLines(f).GetEnumerator());
扩展方法 ToDisposables
应确保在出现异常情况下不会遗留任何一次性物品。
我将建议一种使用递归调用 Zip
的方法,以允许对正常 IEnumerable<string>
进行并行枚举,而无需求助于使用 IEnumerator<string>
。
bool Zipper(IEnumerable<IEnumerable<string>> sources, int take)
{
IEnumerable<string> ZipperImpl(IEnumerable<IEnumerable<string>> ss)
=> (!ss.Skip(1).Any())
? ss.First().Take(take)
: ss.First().Take(take).Zip(
ZipperImpl(ss.Skip(1)),
(x, y) => (x == null || y == null || x != y) ? null : x);
var matching_lines = ZipperImpl(sources).TakeWhile(x => x != null).ToArray();
return matching_lines.Length == take;
}
现在建立你的 enumerables
:
IEnumerable<string>[] enumerables =
Directory
.EnumerateFiles(folderPath)
.Select(f => File.ReadLines(f))
.ToArray();
现在调用很简单了:
bool headers_match = Zipper(enumerables, 100);
这里是 运行 此代码针对三个超过 4 行的文件的踪迹:
Ben Petering at 5:28 PM ACST Ben Petering at 5:28 PM ACST Ben Petering at 5:28 PM ACST From a call 2019-05-23, James mentioned he’d like the ability to edit the current shipping price rules (eg in shipping_rules.xml) via the admin. From a call 2019-05-23, James mentioned he’d like the ability to edit the current shipping price rules (eg in shipping_rules.xml) via the admin. From a call 2019-05-23, James mentioned he’d like the ability to edit the current shipping price rules (eg in shipping_rules.xml) via the admin. He also mentioned he’d like to be able to set different shipping price rules for a given time window, e.g. Jan 1 to Jan 30. He also mentioned he’d like to be able to set different shipping price rules for a given time window, e.g. Jan 1 to Jan 30. He also mentioned he’d like to be able to set different shipping price rules for a given time window, e.g. Jan 1 to Jan 30. These storyishes should be considered when choosing the appropriate module to use. These storyishes should be considered when choosing the appropriate module to use.X These storyishes should be considered when choosing the appropriate module to use.
请注意,当在第二个文件的第 4 行遇到不匹配 header 时,枚举将停止。然后所有枚举都停止了。
您可以在 enumerators
上创建一个 disposable-wrapper:
class DisposableEnumerable : IDisposable
{
private IEnumerable<IDisposable> items;
public event UnhandledExceptionEventHandler DisposalFailed;
public DisposableEnumerable(IEnumerable<IDisposable> items) => this.items = items;
public void Dispose()
{
foreach (var item in items)
{
try
{
item.Dispose();
}
catch (Exception e)
{
var tmp = DisposalFailed;
tmp?.Invoke(this, new UnhandledExceptionEventArgs(e, false));
}
}
}
}
并以对代码影响最小的方式使用它:
private static bool CheckHeaders(string folderPath, int headersCount)
{
var enumerators = Directory.EnumerateFiles(folderPath)
.Select(f => File.ReadLines(f).GetEnumerator())
.ToArray();
using (var disposable = new DisposableEnumerable(enumerators))
{
for (int i = 0; i < headersCount; i++)
{
foreach (var e in enumerators)
{
if (!e.MoveNext()) return false;
}
var values = enumerators.Select(e => e.Current);
if (values.Distinct().Count() > 1) return false;
}
return true;
}
}
问题是你无论如何都必须一个一个地单独处理这些对象。但这取决于您在哪里封装该逻辑。我建议的代码没有手册 try-finally
,)
按照@Alex 的建议创建IDisposable
包装器是正确的。它只需要一个逻辑来处理已经打开的文件,如果它们中的一些是 locked 并且可能是一些错误状态的逻辑。也许是这样的(错误状态逻辑很简单):
public class HeaderChecker : IDisposable
{
private readonly string _folderPath;
private readonly int _headersCount;
private string _lockedFile;
private readonly List<IEnumerator<string>> _files = new List<IEnumerator<string>>();
public HeaderChecker(string folderPath, int headersCount)
{
_folderPath = folderPath;
_headersCount = headersCount;
}
public string LockedFile => _lockedFile;
public bool CheckFiles()
{
_lockedFile = null;
if (!TryOpenFiles())
{
return false;
}
if (_files.Count == 0)
{
return true; // Not sure what to return here.
}
for (int i = 0; i < _headersCount; i++)
{
if (!_files[0].MoveNext()) return false;
string currentLine = _files[0].Current;
for (int fileIndex = 1; fileIndex < _files.Count; fileIndex++)
{
if (!_files[fileIndex].MoveNext()) return false;
if (_files[fileIndex].Current != currentLine) return false;
}
}
return true;
}
private bool TryOpenFiles()
{
bool result = true;
foreach (string file in Directory.EnumerateFiles(_folderPath))
{
try
{
_files.Add(File.ReadLines(file).GetEnumerator());
}
catch
{
_lockedFile = file;
result = false;
break;
}
}
if (!result)
{
DisposeCore(); // Close already opened files.
}
return result;
}
private void DisposeCore()
{
foreach (var item in _files)
{
try
{
item.Dispose();
}
catch
{
}
}
_files.Clear();
}
public void Dispose()
{
DisposeCore();
}
}
// Usage
using (var checker = new HeaderChecker(folderPath, headersCount))
{
if (!checker.CheckFiles())
{
if (checker.LockedFile is null)
{
// Error while opening files.
}
else
{
// Headers do not match.
}
}
}
我在检查行时也删除了 .Select()
和 .Distinct()
。第一个只是遍历 enumerators
数组 - 与它上面的 foreach
相同,所以你要枚举这个数组两次。然后创建一个新的行列表,.Distinct()
枚举它。
到问题的第二部分。如果我没看错,这应该足够了:
static class DisposableHelper
{
public static IEnumerable<TResult> ToDisposable<TSource, TResult>(this IEnumerable<TSource> source,
Func<TSource, TResult> selector) where TResult : IDisposable
{
var exceptions = new List<Exception>();
var result = new List<TResult>();
foreach (var i in source)
{
try { result.Add(selector(i)); }
catch (Exception e) { exceptions.Add(e); }
}
if (exceptions.Count == 0)
return result;
foreach (var i in result)
{
try { i.Dispose(); }
catch (Exception e) { exceptions.Add(e); }
}
throw new AggregateException(exceptions);
}
}
用法:
private static bool CheckHeaders(string folderPath, int headersCount)
{
var enumerators = Directory.EnumerateFiles(folderPath)
.ToDisposable(f => File.ReadLines(f).GetEnumerator())
.ToArray();
using (new DisposableEnumerable(enumerators))
{
for (int i = 0; i < headersCount; i++)
{
foreach (var e in enumerators)
{
if (!e.MoveNext()) return false;
}
var values = enumerators.Select(e => e.Current);
if (values.Distinct().Count() > 1) return false;
}
return true;
}
}
和
try
{
CheckHeaders(folderPath, headersCount);
}
catch(AggregateException e)
{
// Prompt to fix errors and try again
}