我有一堂这样的课:
public class MyFolderRefresher {
public async Task OnRename(RenamedEventArgs e) {
// do something
}
}
public class MyDisposableListOfFileSystemWatchers : IDisposable {
private List<FileSystemWatcher> _mWatchers;
private MyFolderRefresher _mFolderRefresher;
public MyDisposableListOfFileSystemWatchers(List<string> pathsToWatch) {
_mFolderRefresher = new MyFolderRefresher();
_mWatchers = new List<FileSystemWatcher>();
foreach (var path in pathsToWatch) {
var fileSystemWatcher = new FileSystemWatcher(path)
{
NotifyFilter = NotifyFilters.DirectoryName,
IncludeSubdirectories = true,
InternalBufferSize = 64 * 1024
};
// Listen to events in some different class
fileSystemWatcher.Renamed += async (sender, e) => await _mFolderRefresher.OnRename(e);
_mWatchers.Add(fileSystemWatcher);
}
}
public void Dispose() {
Dispose(true);
}
private bool _mDisposed = false;
protected virtual void Dispose(bool disposing) {
if (_mDisposed) {
return;
}
if (disposing) {
foreach(var watcher in _mWatchers) {
watcher.Dispose();
}
_mWatchers.Clear();
}
_mDisposed = true;
}
}
问题是,当调用 MyDisposableListOfFileSystemWatchers 类的
Dispose()
时,我有时会得到 System.InvalidOperationException
。大多数时候,它只是事件查看器中的堆栈跟踪,但是一旦我在调试器中捕获它并能够看到它是System.InvalidOperationException: Collection was modified; enumeration operation may not execute
(不确定两者是否是同一个问题)。
我想知道我的 Dispose() 中是否做错了什么。具体来说:
GC.SuppressFinalize(this)
打电话给 public void Dispose()
吗?我已阅读文档,但无法找到这些问题的明确答案,并且当涉及到
System.InvalidOperationException
时我有点不知所措,因为我不明白如何可能构造函数和 Dispose 可以同时运行(这些是唯一修改 _mWatchers` 的地方)。
我应该在 public void Dispose() 中调用 GC.SuppressFinalize(this) 吗?
不,你没有终结器,没有终结器就没有意义。
问题就变成了,你应该有一个终结器吗?如果有人从您的类派生,并且还拥有一些需要清理的本机资源,则需要这样做。如果这不是您打算做的事情,我建议您将您的课程标记为
sealed
。这允许您简化您的 dispose 方法,并将实现完整 dispose-with-finalizer-pattern 的责任(参见最后一个示例)转移给任何解封您的类的人。
我可以处理正在迭代的对象吗?
是的,完全没问题。
我可以清除集合吗,还是应该将其留给垃圾收集器?
这应该没什么大不了的。清除它应该不会造成伤害,并且在某些情况下可能会有所帮助。
既然我正在处置发布者(FileSystemWatcher),我是否正确地假设我不需要手动取消订阅事件?
也许吧。无论如何,我可能会这样做,但在处理方面我有点偏执。
System.InvalidOperationException:集合被修改;枚举操作可能无法执行
我看不出这可能是由发布的代码引起的。 FileSystemWatcher.Dispose 的文档也没有提到任何异常的可能性。
您没有在文件系统观察器上设置任何
SynchronizingObject
。这意味着事件将在后台线程上引发,使您的程序成为多线程的。这意味着您需要非常小心以确保线程安全。例如,如果重命名事件处理程序以某种方式最终处置了您的对象,则该对象可能会在其构造函数完成之前被处置。这可能会导致列表在迭代时被修改。
如果是这种情况,您可能需要以某种方式消除这种可能性。您还可以在构造函数的末尾设置一个布尔值,并在您的 dispose 方法中添加一个
Debug.Assert(IsFullyConstructed)
来帮助确认或排除这个理论。