C# Linq 源码分析之Distinct方法

概要

Distinct方法是LINQ代码库中的一个重要扩展方法,它可以帮忙我们将数组或其他集合中的重复元素过滤掉。

为了更好的使用该方法,我们从源码角度分析一下该方法,从而更好的了解其是如何将重复元素过滤,如何解决多线程同时读取的问题。

关键源码分析

Distinct方法

 		public static IEnumerable<TSource> Distinct<TSource>(this IEnumerable<TSource> source) => Distinct(source, null);

        public static IEnumerable<TSource> Distinct<TSource>(this IEnumerable<TSource> source, IEqualityComparer<TSource>? comparer)
        {
            if (source == null)
            {
                ThrowHelper.ThrowArgumentNullException(ExceptionArgument.source);
            }
            return new DistinctIterator<TSource>(source, comparer);
        }
  1. LINQ对外提供给用户的Distinct方法有两个
  2. 第一个Distinct方法就是以空比较器作为参数调用第二个Distinct方法,主要用于过滤像数字,字符等系统有默认比较器的数据类型。
  3. 第二个Distinct方法主要用于需要指定比较器的过滤操作, 我们必须告诉程序如何判定这些自定义对象是否是重复的,例如我们可以将对象中的Id一样的数据,视为重复数据,这就需要用户自定义比较器,程序才能知道如何进行过滤。
  4. 两个Distinct方法最后都是返回DistinctIterator类的实例。所以该方法也是不返回具体数据,只返回迭代器,支持延迟加载。
private sealed partial class DistinctIterator<TSource> : Iterator<TSource>
        {
            private readonly IEnumerable<TSource> _source;
            private readonly IEqualityComparer<TSource>? _comparer;
            private HashSet<TSource>? _set;
            private IEnumerator<TSource>? _enumerator;
            public DistinctIterator(IEnumerable<TSource> source, IEqualityComparer<TSource>? comparer)
            {
                Debug.Assert(source != null);
                _source = source;
                _comparer = comparer;
            }

            public override Iterator<TSource> Clone() {
                return new DistinctIterator<TSource>(_source, _comparer);
            }

            public override bool MoveNext()
            {
                switch (_state)
                {
                    case 1:
                        _enumerator = _source.GetEnumerator();
                        if (!_enumerator.MoveNext())
                        {
                            Dispose();
                            return false;
                        }
                        TSource element = _enumerator.Current;
                        _set = new HashSet<TSource>(DefaultInternalSetCapacity, _comparer);
                        _set.Add(element);
                        _current = element;
                        _state = 2;
                        return true;
                    case 2:
                        Debug.Assert(_enumerator != null);
                        Debug.Assert(_set != null);
                        while (_enumerator.MoveNext())
                        {
                            
                            element = _enumerator.Current;
                            if (_set.Add(element))
                            {
                                _current = element;
                                return true;
                            }
                        }

                        break;
                }

                Dispose();
                return false;
            }

            public override void Dispose()
            {
                if (_enumerator != null)
                {
                    _enumerator.Dispose();
                    _enumerator = null;
                    _set = null;
                }

                base.Dispose();
            }
        }
    }
}
  1. DistinctIterator迭代器继承自基类迭代器Iterator,Iterator类是一个抽象类,其中包含了多线程支持和嵌套循环支持的功能,LINQ中的其他迭代器也都继承了该类。Iterator类代见附录。
  2. Iterator是抽象类,可以提前在构造方法中给所有迭代器都用到的栏位赋值,例如_state默认赋值为1

结合DistinctIterator类的代码,如果我们通过foreach循环,逐个读取过滤后的元素,代码如下,其中Student类和比较器的代码见附录:

  	var stuList =  studentList.Distinct(new StudentEqualityComparer());
	foreach(var stu in stuList){
    	Console.WriteLine(stu.Name); 
	} 

执行流程如下:

  1. DistinctIterator构造方法会将要迭代的集合和集合元素比较器初始化。
  2. Distinct方法返回DistinctIterator实例的对象。
  3. 当过滤结果通过foreach循环读取时,会调先调用基类Iterator的GetEnumerator方法,判定是否有多个线程在使用该迭代器,如果不是将返回当前迭代器对象,否则调用DistinctIterator的Clone()方法,克隆一个新的迭代器对象返回。基类Iterator代码见附录。
  4. 在基类Iterator的GetEnumerator方法中,将_state改为1,表示该迭代器处于等待开始迭代的状态。
  5. foreach调用DistinctIterator实例的MoveNext方法,启动迭代。
  6. 进入MoveNext方法,_state值是1,进入case 1子句,如果_enumerator.MoveNext()返回为false,即要进行迭代的集合为空,直接返回。
  7. 实例化HashSet,初始大小是7。
  8. 将集合的第一个元素放入HashSet中,HashSet是一种高效的存储结构,不能包含任何重复元素。如果要添加的是重复元素,则Add方法会返回false。由于是第一次添加,因此不需要进行是否添加成功的检查。
  9. 将_state改为2,表示该迭代器正式开始工作。
  10. 从第二的元素开始,foreach调用DistinctIterator对象的MoveNext方法,
  11. 直接进入case 2,如果元素可以正常添加到HashSet中,则表示该元素在集合中是唯一的,所以返回true,否则返回false。
  12. 当迭代完成,调用Dispose方法,从内存中移除该迭代器对象。

DistinctBy方法

源码验证

 		public static IEnumerable<TSource> DistinctBy<TSource, TKey>(this IEnumerable<TSource> source, Func<TSource, TKey> keySelector) => DistinctBy2(source, keySelector, null);

        public static IEnumerable<TSource> DistinctBy<TSource, TKey>(this IEnumerable<TSource> source, Func<TSource, TKey> keySelector, IEqualityComparer<TKey>? comparer)
        {
            if (source is null)
            {
                ThrowHelper.ThrowArgumentNullException(ExceptionArgument.source);
            }
            if (keySelector is null)
            {
                ThrowHelper.ThrowArgumentNullException(ExceptionArgument.keySelector);
            }

            return DistinctByIterator(source, keySelector, comparer);
        }
  1. LINQ对外提供给用户的DistinctBy方法有两个,该方法让我们根据一个指定的key值进行过滤操作,比较器也是针对指定的key值进行比较。
  2. 第一个DictinctBy方法实际上,就是以空比较器作为参数调用第二个Distinct方法,主要用于过滤像数字,字符等系统有默认比较器的数据类型。
  3. 第二个DistinctBy方法主要用于需要指定比较器的过滤操作。
  4. 在进行了迭代元素集合和获取Key值的委托不能为空的检查后,调用DistinctByIterator方法,真正开始过滤重复的元素。
 private static IEnumerable<TSource> DistinctByIterator<TSource, TKey>(IEnumerable<TSource> source, Func<TSource, TKey> keySelector, IEqualityComparer<TKey>? comparer)
      {
           using IEnumerator<TSource> enumerator = source.GetEnumerator();

           if (enumerator.MoveNext())
           {
               var set = new HashSet<TKey>(DefaultInternalSetCapacity, comparer);
               do
               {
                   TSource element = enumerator.Current;
                   if (set.Add(keySelector(element)))
                   {
                       yield return element;
                   }
               }
               while (enumerator.MoveNext());
           }
       }
  1. DistinctByIterator方法包含三个参数,第一个是扩展方法参数,第二个是获取Key值的系统委托KeySelector,可以是Lambda表达式,最后一个参数是针对Key值的比较器。
  2. 调用基类Iterator的GetEnumerator方法,获取迭代器实例。
  3. 迭代开始后,调用系统委托KeySelector,获取Key值。
  4. 实例化HashSet,初始大小是7。
  5. 将Key值存入HashSet中,使用传入的比较器对Key进行比较。
  6. 如果Key值在HashSet中不存在重复项,则通过yield进行返回。yield作为迭代器实例的语法糖,在实际使用上显然要比定义具体的迭代器类DistinctIterator要简单的多。

结论

Distinct或DistinctBy方法在实现上都是使用HasSet来过滤掉集合中的重复元素,支持延迟加载特性。

附录

Iterator类源码



    using System.Collections;
    using System.Collections.Generic;
    public static partial class Enumerable
    {
        
        internal abstract class Iterator<TSource> : IEnumerable<TSource>, IEnumerator<TSource>
        {
            private readonly int _threadId;
            internal int _state;
            internal TSource _current = default!;

            /// 
            /// Initializes a new instance of the  class.
            /// 
            protected Iterator()
            {
                _threadId = Environment.CurrentManagedThreadId;
            }

            /// 
            /// The item currently yielded by this iterator.
            /// 
            public TSource Current => _current;

            /// 
            /// Makes a shallow copy of this iterator.
            /// 
            /// 
            /// This method is called if  is called more than once.
            /// 
            public abstract Iterator<TSource> Clone();

            /// 
            /// Puts this iterator in a state whereby no further enumeration will take place.
            /// 
            /// 
            /// Derived classes should override this method if necessary to clean up any
            /// mutable state they hold onto (for example, calling Dispose on other enumerators).
            /// 
            public virtual void Dispose()
            {
                _current = default!;
                _state = -1;
            }

            /// 
            /// Gets the enumerator used to yield values from this iterator.
            /// 
            /// 
            /// If  is called for the first time on the same thread
            /// that created this iterator, the result will be this iterator. Otherwise, the result
            /// will be a shallow copy of this iterator.
            /// 
            public IEnumerator<TSource> GetEnumerator()
            {
                Iterator<TSource> enumerator = _state == 0 && _threadId == Environment.CurrentManagedThreadId ? this : Clone();
                enumerator._state = 1;
                return enumerator;
            }

            /// 
            /// Retrieves the next item in this iterator and yields it via .
            /// 
            /// true if there was another value to be yielded; otherwise, false.
            public abstract bool MoveNext();

            /// 
            /// Returns an enumerable that maps each item in this iterator based on a selector.
            /// 
            /// The type of the mapped items.
            /// The selector used to map each item.
            public virtual IEnumerable<TResult> Select<TResult>(Func<TSource, TResult> selector)
            {
                return new SelectEnumerableIterator<TSource, TResult>(this, selector);
            }

            /// 
            /// Returns an enumerable that filters each item in this iterator based on a predicate.
            /// 
            /// The predicate used to filter each item.
            public virtual IEnumerable<TSource> Where(Func<TSource, bool> predicate)
            {
                return new WhereEnumerableIterator<TSource>(this, predicate);
            }

            object? IEnumerator.Current => Current;

            IEnumerator IEnumerable.GetEnumerator() => GetEnumerator();

            void IEnumerator.Reset() => throw new NotSupportedException();
        }
    }

Student类源码

 public class Student : IEnumerator, IEnumerable {
        public string Id { get; set; }
        public string Name { get; set; }
        public string Classroom { get; set; }
        
        public Student(string id, string name, string classroom)
        {
            this.Id = id;
            this.Name = name;
            this.Classroom = classroom;
        }
    }
}

你可能感兴趣的:(.Net,C#基础,.Net,Core,linq,c#,microsoft)