Immutable Collections(2)ImmutableList<T>实现原理.(上)

本文详细解析了ImmutableList<T>的内部构造,包括核心字段、属性及其与其他类型的关联,着重介绍了Node类的特性及初始化过程,并讨论了不可变集合的关键特性冻结。通过代码示例,展示了如何通过构造函数和方法实现ImmutableList<T>的创建与操作。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

Immutable Collections2ImmutableList<T>实现原理.(上)

/玄魂

前言

在上一篇文章(Immutable Collections1,我简要说明了不可变集合的基本概念和简单应用。从本篇博文开始,会探讨下几个典型集合类型的内部实现机制。本篇博客主要探讨ImmutableList<T>实现原理。

博文中引用的代码并非是.NET源码,而是反编译得来,不正确之处,还望指教。

2.1 概述

下图是ImmutableList<T>类型包含的核心字段、属性(并非全部),以及和其他类型的关系。这张图是自动生成的,我直接拿过来没有做什么改动,可能会让人云里雾里,下面我做简要的说明。

处于最顶端的ImmutableList静态类,是ImmutableList<T>类型的构造者,它或者直接返回ImmutableList<T>Empty属性,或者在Empty的基础上构造ImmutableList<T>实例,比如下面的代码:

public static ImmutableList<T> Create<T>()
             {
                    return ImmutableList<T>.Empty;
             }
             public static ImmutableList<T> Create<T>(IEqualityComparer<T> equalityComparer)
             {
             return ImmutableList<T>.Empty.WithComparer(equalityComparer);
             }
             public static ImmutableList<T> Create<T>(T item)
             {
                    return ImmutableList<T>.Empty.Add(item);
             }
ImmutableList静态类下面是核心部分——ImmutableList<T>类型。ImmutableList<T>继承自如下接口:
IImmutableList<T>, IReadOnlyList<T>, IReadOnlyCollection<T>, IList<T>, ICollection<T>, IList, ICollection, IOrderedCollection<T>, IEnumerable<T>, IEnumerable, IImmutableListQueries<T>

其中IImmutableList<T>定义如下:

public interface IImmutableList<T> : IReadOnlyList<T>, IReadOnlyCollection<T>, IEnumerable<T>, IEnumerable
      {
             IEqualityComparer<T> ValueComparer
             {
                    get;
             }
             IImmutableList<T> Clear();
             bool Contains(T value);
             int IndexOf(T value);
             IImmutableList<T> Add(T value);
             IImmutableList<T> AddRange(IEnumerable<T> items);
             IImmutableList<T> Insert(int index, T element);
             IImmutableList<T> InsertRange(int index, IEnumerable<T> items);
             IImmutableList<T> Remove(T value);
             IImmutableList<T> RemoveAll(Predicate<T> match);
             IImmutableList<T> RemoveRange(IEnumerable<T> items);
             IImmutableList<T> RemoveRange(int index, int count);
             IImmutableList<T> RemoveAt(int index);
             IImmutableList<T> SetItem(int index, T value);
             IImmutableList<T> Replace(T oldValue, T newValue);
             IImmutableList<T> WithComparer(IEqualityComparer<T> equalityComparer);
      }

IImmutableListQueries<T>定义如下:

      internal interface IImmutableListQueries<T>
      {
             int Count
             {
                    get;
             }
             ImmutableList<TOutput> ConvertAll<TOutput>(Func<T, TOutput> converter);
             void ForEach(Action<T> action);
             ImmutableList<T> GetRange(int index, int count);
             void CopyTo(T[] array);
             void CopyTo(T[] array, int arrayIndex);
             void CopyTo(int index, T[] array, int arrayIndex, int count);
             bool Exists(Predicate<T> match);
             T Find(Predicate<T> match);
             ImmutableList<T> FindAll(Predicate<T> match);
             int FindIndex(Predicate<T> match);
             int FindIndex(int startIndex, Predicate<T> match);
             int FindIndex(int startIndex, int count, Predicate<T> match);
             T FindLast(Predicate<T> match);
             int FindLastIndex(Predicate<T> match);
             int FindLastIndex(int startIndex, Predicate<T> match);
             int FindLastIndex(int startIndex, int count, Predicate<T> match);
             int IndexOf(T item);
             int IndexOf(T item, int index);
             int IndexOf(T item, int index, int count);
             int LastIndexOf(T item);
             int LastIndexOf(T item, int index);
             int LastIndexOf(T item, int index, int count);
             bool TrueForAll(Predicate<T> match);
      }

其他接口,是.NET中原有接口,这里就不列举了。IImmutableList<T> 的核心行为都定义在这两个接口当中。

SyncRootObject类型字段,作为同步锁对象。

Empty直接返回当前集合的单例对象EmptySingleton

ImmutableList<T>构造函数如下:

internal ImmutableList()
             {
                    this.root = ImmutableList<T>.Node.EmptyNode;
                    this.valueComparer = EqualityComparer<T>.Default;
             }
             private ImmutableList(ImmutableList<T>.Node root, IEqualityComparer<T> valueComparer)
             {
                    root.Freeze();
                    this.root = root;
                    this.valueComparer = valueComparer;
             }

在构造函数中我们又发现两个很重要的类型,NodeIEqualityComparerIEqualityComparer这里就不解释了,我们重点关注Node,从字面上理解,这是一个表示节点的类,事实上它是ImmutableList<T>的核心,数据的承载和操作都是对Node类的包装。下面我们来看看Node的庐山真面目。

2.2 Node

Node类继承自三个接口,
internal sealed class Node : IBinaryTree<T>, IEnumerable<T>, IEnumerable

我们主要关注IBinaryTree<T>,定义如下:

interface IBinaryTree<out T>
      {
             int Height
             {
                    get;
             }
             T Value
             {
                    get;
             }
             IBinaryTree<T> Left
             {
                    get;
             }
             IBinaryTree<T> Right
             {
                    get;
             }
             bool IsEmpty
             {
                    get;
             }
             int Count
             {
                    get;
             }
      }

接口很清楚,定义了一个二叉树,但是这棵二叉树的具体特性但从接口上还无从得知。现在我们再看Node类。

ImmutableList<T>EmptySingleton就是返回的上图中最上面的Node类的EmptyNodeEmptyNode定义如下:

      internal static readonly Node EmptyNode = new  Node();

Node类的KeyValue属性是同一个值,就是当前节点的值。在代码中都是以Key为操作对象。下面分析Node的相关行为的时候,会有更清楚的认识。

frozen是一个bool类型的变量,表示是否冻结。冻结可以说是不可变集合的一个关键特性,下面也会对此做详细的分析。

height是以当前节点为根的树的高度。

      this.height = 1 + Math.Max(left.height, right.height);

count以当前节点为根的树的节点个数。

this.count = 1 + left.count + right.count;

IsEmpty判断是否有子节点。

public bool IsEmpty
                    {
                           get
                           {
                                  return this.left == null;
                           }
                    }

rightleft就是左右子树。

2.3 行为

2.3.1    初始化

我们通过下面的代码来观察Node的初始化过程。

  static void Main(string[] args)
        {
 
            var fruitBasket = ImmutableList.Create<string>();
          var ass  = fruitBasket.Add("ddd");
        }

启动程序,首先进入ImmutableList.Create<string>()方法。

Create方法直接返回ImmutableList<T>.EmptyEmpty属性直接返回EmptySingleton

调用EmptySingleton时触发ImmutableList<T>的初始化

接下来在构造函数中调用Node.EmptyNode

Node类的无参构造函数中只初始化了一个变量:

此时Node.EmptyNode实例的各字段值如下图所示:

到此为止第一次初始化结束。

现在测试下带比较器的构造方式,先新建一个TestCompare类:

class TestCompare<T> : IEqualityComparer<T>
    {
        public bool Equals(T x, T y)
        {
            return x.Equals(y);
        }
        public int GetHashCode(T obj)
        {
            return this.GetHashCode();
        }
    }

然后更改Main函数中的代码:

   static void Main(string[] args)
        {
    var fruitBasket = ImmutableList.Create<string>(new TestCompare<string>());
          var ass  = fruitBasket.Add("ddd");
        }

ImmutableList静态方法仍然在ImmutableList<T>.Empty基础上调用了WithCompare方法。

 
WithCompare方法在初始化了valueCompare字段之后调用了构造函数ImmutableList(ImmutableList<T>.Node root, IEqualityComparer<T> valueComparer)
      private ImmutableList(ImmutableList<T>.Node root, IEqualityComparer<T> valueComparer)
             {
                    Requires.NotNull<ImmutableList<T>.Node>(root, "root");
                    Requires.NotNull<IEqualityComparer<T>>(valueComparer, "valueComparer");
                    root.Freeze();
                    this.root = root;
                    this.valueComparer = valueComparer;
             }

上面的构造函数,调用了Freeze()方法,

internal void Freeze()
                    {
                           if (!this.frozen)
                           {
                                  this.left.Freeze();
                                  this.right.Freeze();
                                  this.frozen = true;
                           }
                    }

这段代码,实际上是一个递归调用,设置每个节点为冻结状态。

带初始值的构造函数,实际是调用了Add方法,我将在下一篇博文中单独分析。

本篇博文到此结束,未完,待续。。。。。。

<think>嗯,用户想修改C#中的公共静态可变字段,以提高线程安全或防止意外更改。特别是那些继承自System.Array或实现ICollection<T>接口的类型。我需要先理解这些字段可能面临的问题,然后找到合适的解决方法。 首先,公共静态可变字段本身就有风险,因为任何地方都可以修改它们,可能导致竞态条件或不一致状态。比如,如果有多个线程同时读写这个字段,线程安全问题就出现了。此外,用户可能不小心修改了这些字段,导致程序出错。 接下来,用户提到要保护继承自System.Array或实现ICollection<T>的字段。System.Array本身是可变数组,而ICollection<T>接口包含修改集合的方法,如Add、Remove等。所以,直接暴露这些类型的公共静态字段,确实容易出问题。 我需要考虑几个方法。首先,减少字段的可见性,比如将public改为private,并提供受控的访问方法。这可能有效,但需要确保所有访问都通过这些方法进行。但用户可能已经使用了这些字段,直接修改可见性会影响现有代码,所以可能还需要其他措施。 另一个方法是使用不可变类型。比如,将Array转换为不可变数组,或者使用ImmutableArray<T>。对于ICollection<T>,可以考虑使用System.Collections.Immutable中的不可变集合,如ImmutableList<T>。这样,任何修改操作都会返回新的实例,原实例保持不变,从而避免意外更改。 但用户可能需要保持字段的类型为System.Array或ICollection<T>,所以可能需要包装这些实例。例如,使用ReadOnlyCollection<T>包装List<T>,这样外部代码只能读取,不能修改。或者,在返回集合时,返回一个只读的视图,防止直接修改。 线程安全方面,可能需要使用同步机制,比如lock语句或Concurrent集合。但如果是静态字段,多个线程同时访问时,简单的lock可以确保原子操作。不过,使用Concurrent集合可能更高效,但需要确认是否System.Array或ICollection<T>兼容。 另外,用户可能希望既保持接口兼容性,又增强安全性。例如,如果字段是ICollection<T>类型,可以将其替换为ImmutableList<T>.Wrap(existingList),但需要确认是否可行。或者,在get访问器中返回一个只读包装,这样外部获取到的实例无法修改原始集合。 还需要考虑初始化的问题。使用Lazy<T>来延迟初始化,确保线程安全的初始化。同时,属性封装允许在访问时添加验证或日志,有助于调试。 可能的陷阱包括,即使将字段设为只读,如果内部元素是可变对象,仍然可以修改元素内容。因此,深层次的不可变性可能需要元素本身也是不可变的。 总结步骤应该是:首先降低可见性,然后通过属性封装,返回只读视图或不可变类型,并使用线程安全措施如lock或并发集合。同时,考虑使用Lazy<T>来安全初始化。 最后,生成相关问题时要围绕线程安全、不可变集合、封装方法等方面提问,帮助用户深入理解相关解决方案。</think>### 分步解决方案 #### 1. **降低字段可见性** 将公共静态字段的访问修饰符从`public`改为`private`或`protected`,通过属性封装对外提供受控访问: ```csharp private static int[] _sharedArray = new int[10]; public static IReadOnlyList<int> SharedArray => _sharedArray.AsReadOnly(); ``` 此操作通过`AsReadOnly()`返回只读视图,防止外部直接修改数组内容[^2]。 #### 2. **使用不可变集合类型** 将可变集合替换为不可变类型(如`ImmutableArray<T>`或`ImmutableList<T>`): ```csharp public static ImmutableArray<int> ImmutableData = ImmutableArray.Create(1, 2, 3); ``` 不可变类型的所有修改操作都会返回新实例,原始数据保持稳定[^1]。 #### 3. **包装为只读接口** 对实现`ICollection<T>`的类型,返回`ReadOnlyCollection<T>`或`IReadOnlyList<T>`: ```csharp private static List<string> _internalList = new List<string>(); public static IReadOnlyList<string> PublicList => _internalList.AsReadOnly(); ``` 通过接口限制,隐藏修改方法(如`Add`/`Remove`)。 #### 4. **添加线程同步机制** 对多线程环境中的共享资源,使用`lock`或`ConcurrentBag<T>`等线程安全容器: ```csharp private static object _lockObj = new object(); private static List<int> _threadSafeList = new List<int>(); public static void AddItem(int item) { lock(_lockObj) { _threadSafeList.Add(item); } } ``` 锁机制确保操作的原子性。 #### 5. **深层次不可变性设计** 若集合元素本身可变,需将其替换为不可变类型(如记录类或结构体): ```csharp public record ImmutableItem(int Id, string Name); public static ImmutableArray<ImmutableItem> Items = ImmutableArray.Create<ImmutableItem>(); ``` #### 6. **延迟初始化优化** 使用`Lazy<T>`实现线程安全的延迟初始化: ```csharp private static Lazy<ImmutableArray<int>> _lazyData = new Lazy<ImmutableArray<int>>(() => ImmutableArray.Create(1, 2, 3)); public static ImmutableArray<int> LazyData => _lazyData.Value; ``` ### 关键注意事项 - **类型兼容性**:若必须保持`System.Array`类型,需通过`Array.Copy`返回副本 - **性能权衡**:不可变类型会频繁创建副本,需评估对内存和性能的影响 - **接口适配**:若需要保持`ICollection<T>`接口,可自定义实现只读包装器
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值