迭代时修改 Collection 底层的 ReadOnlyCollection 属性

Posted

技术标签:

【中文标题】迭代时修改 Collection 底层的 ReadOnlyCollection 属性【英文标题】:ReadOnlyCollection property underlying Collection is modified while iterating 【发布时间】:2018-01-23 02:46:45 【问题描述】:

我有以下属性:

private static Collection<Assembly> _loadedAssemblies = new Collection<Assembly>();
    internal static ReadOnlyCollection<Assembly> LoadedAssemblies
    
        get  return new ReadOnlyCollection<Assembly>(_loadedAssemblies); 
    

在另一个类中我循环通过LoadedAssemblies

foreach (Assembly assembly in ResourceLoader.LoadedAssemblies)

在遍历程序集时,底层集合 (_loadedAssemblies) 有时会发生变化,从而产生 System.InvalidOperationException。使LoadedAssemblies 安全的首选方法是什么?我无法重现该问题,所以我无法尝试。

可以吗?

internal static ReadOnlyCollection<Assembly> LoadedAssemblies
    
        get  return new ReadOnlyCollection<Assembly>(_loadedAssemblies.ToList()); 
    

编辑

    public static void Initialize()
    

        foreach (Assembly assembly in AppDomain.CurrentDomain.GetAssemblies())
        
            AddAssembly(assembly);
        
        AppDomain currentDomain = AppDomain.CurrentDomain;
        currentDomain.AssemblyLoad += OnAssemblyLoad;
    

    private static void OnAssemblyLoad(object sender, AssemblyLoadEventArgs args)
    
        AddAssembly(args.LoadedAssembly);
    

    private static void AddAssembly(Assembly assembly)
    
        AssemblyName assemblyName = new AssemblyName(assembly.FullName);
        string moduleName = assemblyName.Name;

        if (!_doesNotEndWith.Exists(x => moduleName.EndsWith(x, StringComparison.OrdinalIgnoreCase)) &&
            _startsWith.Exists(x => moduleName.StartsWith(x, StringComparison.OrdinalIgnoreCase)))
        
            if (!_loadedAssemblies.Contains(assembly))
            
                _loadedAssemblies.Add(assembly);
            
        
    

【问题讨论】:

为什么有时会发生变化?那就是导致异常。您不能在 foreach 中修改集合。 @TimSchmelter 看起来底层集合已被修改,导致迭代时出现异常,如何解决? 枚举时不要修改,显示修改它的代码 这是一种选择,但我想我必须做一些影响很大的改变,还有其他选择吗? 编辑了问题 【参考方案1】:

您在问题中提出的建议将起作用。那就是:

internal static ReadOnlyCollection<Assembly> LoadedAssemblies

    get  return new ReadOnlyCollection<Assembly>(_loadedAssemblies.ToList()); 

原因是您的问题来自 _loadedAssemblies 在您枚举它时被更改。这当然会发生,因为 ReadOnlyCollection 只是 _loadedAssemblies 的一个包装器,它使用基本集合的枚举器。

如果您执行_loadedAssemblies.ToList(),那么这将创建一个新列表,该列表是原始_loadedAssemblies 的副本。它在创建时将具有所有相同的元素,但永远不会再次更新(因为您甚至没有对新集合的引用,因此您无法修改它)。这意味着,当_loadedAssemblies 更新时,ReadOnlyCollection 中的新列表完全没有意识到这一变化,因此您的枚举将毫无问题地持续到最后。

【讨论】:

【参考方案2】:

ReadOnlyCollection&lt;T&gt; 只是原始集合的包装器,可防止直接修改。但是,如果您在某处公开它,它不会阻止修改底层集合。

由于ReadOnlyCollection&lt;T&gt;.GetEnumerator 只是为基础集合返回一个枚举器,因此它具有相同的规则。枚举时不能修改,即不能添加或删除项目。

没有看到你的代码就没有简单的解决方法,防止多个线程同时访问底层集合,f.e.带有lock。 如果你在foreach中修改它可能更容易修复,但是我们需要看到那个代码。

【讨论】:

你能看一下吗?

以上是关于迭代时修改 Collection 底层的 ReadOnlyCollection 属性的主要内容,如果未能解决你的问题,请参考以下文章

Collection接口都是通过Iterator()(即迭代器)来对Set和List遍历

详解 迭代器 —— Iterator接口 ListIterator接口 与 并发修改异常

00078_迭代器

Iterator迭代器源码简略分析

List集合遍历时修改元素出现并发修改异常总结

Java Review (二十集合----- Iterator接口)