There are new extension methods under enumerable. Let’s have a look

Time:2020-11-20

1: Background

1. Tell a story

Some time ago, we upgraded a project of the company from 4.5 to framework 4.8. During coding, we found that there are three extension methods in enumerable:Append, Prepend, ToHashSetFriends who have played jQuery can see the use of these three methods at a glance. This article will talk about the underlying source code implementation of these three methods and see if there is anything new to dig out.

2: New extension method under enumerable

1. Append

My first impression of this is thatAddUnfortunately, there is no similar method in enumerable. It may be that programmers have become more and more popular in this area, and the C ා development team has made up for this regret.

<1> Addition of single data

Next, I will write a small example to add a piece of data to the end of the set, as shown in the following code:

static void Main(string[] args)
        {
            var arr = new int[2] { 1, 2 };

            var result = Enumerable.Append(arr, 3);

            foreach (var item in result)
            {
                Console.WriteLine(item);
            }
        }

The logic is very clear. Let’s take a look at how the underlying source code is implemented.

public static IEnumerable Append(this IEnumerable source, TSource element)
{
	if (source == null)
	{
		throw Error.ArgumentNull("source");
	}
	AppendPrependIterator appendPrependIterator = source as AppendPrependIterator;
	if (appendPrependIterator != null)
	{
		return appendPrependIterator.Append(element);
	}
	return new AppendPrepend1Iterator(source, element, appending: true);
}


private class AppendPrepend1Iterator : AppendPrependIterator
{
    public AppendPrepend1Iterator(IEnumerable source, TSource item, bool appending) : base(source)
	{
		_item = item;
		_appending = appending;
	}

    public override bool MoveNext()
	{
		switch (state)
		{
		case 1:
			state = 2;
			if (!_appending)
			{
				current = _item;
				return true;
			}
			goto case 2;
		case 2:
			GetSourceEnumerator();
			state = 3;
			goto case 3;
		case 3:
			if (LoadFromEnumerator())
			{
				return true;
			}
			if (_appending)
			{
				current = _item;
				return true;
			}
			break;
		}
		Dispose();
		return false;
	}

}

From the source code above, it is still very complicated. The inheritance relationship is as follows:AppendPrepend1Iterator -> AppendPrependIterator -> IteratorLet’s focus on itMoveNext()The two methods getsourceenumerator() and loadfromenumerator() are shown in the following code:

As you can see, the first method is used to obtain the data source array. The following method is used to traverse the array. After the foreach traversal is completed, execute the case 3 statement, that is, the following if statement, and iterate your additional 3, as shown in the following figure:

<2> Batch data addition

We know that there is addrange in addition to add. Unfortunately, no similar appendrange method is found under enumerable. What should I do if I want to implement appendrange operation? Ha ha, you can only iterate by yourself. The code is as follows:

static void Main(string[] args)
        {
            var arr = new int[2] { 1, 2 };

            var arr2 = new int[3] { 3, 4, 5 };

            IEnumerable collection = arr;

            foreach (var item in arr2)
            {
                collection = collection.Append(item);
            }
            foreach (var item in collection)
            {
                Console.WriteLine(item);
            }
        }

The result is also very simple, because IEnumerable is a non-destructive operation, so you need to use the type to catch after append, and then look for the underlying source code.

public static IEnumerable Append(this IEnumerable source, TSource element)
{
	if (source == null)
	{
		throw Error.ArgumentNull("source");
	}
	AppendPrependIterator appendPrependIterator = source as AppendPrependIterator;
	if (appendPrependIterator != null)
	{
		return appendPrependIterator.Append(element);
	}
	return new AppendPrepend1Iterator(source, element, appending: true);
}

private class AppendPrepend1Iterator : AppendPrependIterator
{
    public override AppendPrependIterator Append(TSource item)
	{
		if (_appending)
		{
			return new AppendPrependN(_source, null, new SingleLinkedNode(_item).Add(item), 0, 2);
		}
		return new AppendPrependN(_source, new SingleLinkedNode(_item), new SingleLinkedNode(item), 1, 1);
	}
}

private class AppendPrependN : AppendPrependIterator
{
	public override AppendPrependIterator Append(TSource item)
	{
		SingleLinkedNode appended = (_appended != null) ? _appended.Add(item) : new SingleLinkedNode(item);
		return new AppendPrependN(_source, _prepended, appended, _prependCount, _appendCount + 1);
	}
}

As can be seen from the above code, when you append multiple times, it is essentially multiple callsAppendPrependN.Append()And during the call process, the elements you added later are appended to theSingleLinkedNodeIn the single linked list, it should be noted that the header insertion method is adopted for add, so the last inserted element will be in the queue head, as shown in the following figure:

If you don’t believe it, I can show it to you in vs debugging.

It seems to be a little wordy. Let’s take a look at itAppendPrependN.MoveNextThat’s OK.

After all that, I think you should understand.

2. Prepend

In essence, prepend and append are a pair. One is inserted at the front and the other is inserted at the back. Don’t think it is crooked. If you are careful, you will find that prepend also uses these three classesAppendPrepend1Iterator,AppendPrependIterator,AppendPrependNAnd single chain listSingleLinkedNodeI’ll leave it to you to study for yourself.

3. ToHashSet

I used to use HashSet frequently in full memory development. After all, its time complexity isO(1)In addition, tolist and todictionary were already available in enumerable. Why didn’t you have tohashset? In the past, you could only plug source into the constructor of HashSet, such as:new HashSet(source)It’s too bad to add spily in batch. What’s worse, I don’t think about how to expand spily in batch.

3: Summary

Generally speaking, these three methods are still very practical. I believe that in the subsequent versions, there will be more and more extension methods under enumerable, which will be more and more humanized. Life is short. I use C ා.

If you have more questions to interact with me, scan below and come in~

图片名称