Location>code7788 >text

C#|.net core fundamentals - The best way to extend an array with add/drop performance

Popularity:773 ℃/2024-09-20 16:26:13

Today in the coding encountered a problem, need to add new elements to the array variable and delete elements, because the array is a fixed size, so the addition and deletion is not friendly, but sometimes will be used, so I want to target the array to encapsulate the two extension methods: new elements and delete elements, and can reach the following three goals:

1. Excellent performance;

2. Good compatibility;

3. Easy to use;

The most troublesome of these three goals should be excellent performance, compared to the latter two can be achieved through generic methods, extension methods, pass by reference syntax, etc., but excellent performance in the dozen or so implementations to choose the best two implementations. How many implementations can you think of for adding and deleting elements to an array? Here we come together to see that the best performance.

01Comparison of the implementation methods of the new elements

1, through the List method

By turning into a List, and then use the AddRange method to add elements, and then finally turned into an array to return. Code implementation is as follows:

public static int[] AddByList(int[] source, int[] added)
{
    var list = ();
    (added);
    return ();
}

2, through the IEnumerable method

Because the array implements the IEnumerable interface, so you can directly call the Concat method to achieve the two array stitching. The code implementation is as follows:

public static int[] AddByConcat(int[] source, int[] added)
{
    return (added).ToArray();
}

3、Implemented through the Array method

Array has a Copy static method can be realized to copy the array to the target array, so we can first build a large array, and then use the Copy method to copy both arrays to the large array. The code implementation is as follows:

public static int[] AddByCopy(int[] source, int[] added)
 {
     var size = + ;
     var array = new int[size];
     // Duplicate the original array
     (source, array, );
     // Adding new elements
     (added, 0, array, , );
     return array;
 }

4、Actualized by Span method

Span also has an Array-like Copy method with similar functionality, the CopyTo method. The code implementation is as follows:

public static int[] AddBySpan(int[] source, int[] added)
{
    Span<int> sourceSpan = source;
    Span<int> addedSpan = added;
    Span<int> span = new int[ + ];
    // Duplicate the original array
    (span);
    // Adding new elements
    (());
    return ();
}

I can think of 4 ways to accomplish this, so if you have a different method hopefully you can leave me a comment and not be stingy. Then that method is the most efficient? According to my understanding as a first-class citizen in the performance of .net core Span should be the best performance.

We are not blindly guessing, directly to a set of benchmark test comparison. We have four methods, divided into three groups of tests, each group were randomly generated two arrays of 100, 1000, 10000 elements, and then each group and then 10,000 tests.

The test results are as follows:

Overall ranking: AddByCopy > AddByConcat > AddBySpan > AddByList.

It can be found that the best performance is surprisingly the Array's Copy method, which is not only optimal in terms of speed, but also in terms of memory usage.

And Span, which I think has the best performance overall, doesn't perform as well as IEnumerable's Concat method.

In the end Array's Copy method wins.

02Comparison of the implementation methods for deleting elements

1, through the List method

Still the first array to List, and then RemoveAll for deletion, and finally the results to an array to return. Code implementation is as follows:

public static int[] RemoveByList(int[] source, int[] added)
{
    var list = ();
    (x => (x));
    return ();
}

2, through the IEnumerable method

Because the array implements the IEnumerable interface, the Where method can be called directly to filter. The code implementation is as follows:

public static int[] RemoveByWhere(int[] source, int[] added)
{
     return (x => !(x)).ToArray();
}

3、Implemented through the Array method

Array has a FindAll static method can be realized according to the conditions to find the array. Code implementation is as follows:

public static int[] RemoveByArray(int[] source, int[] added)
{
    return (source, x => !(x));
}

4、Implemented through the For+List method

Directly traverse the original array, put the elements that meet the conditions into the List, and then converted to an array to return. Code implementation is as follows:

public static int[] RemoveByForList(int[] source, int[] added)
{
    var list = new List<int>();
    foreach (int item in source)
    {
        if (!(item))
        {
            (item);
        }
    }
    return ();
}

5, through the For + mark + Copy way to realize the

or directly traverse the original array, but we do not create a new set, directly to meet the elements in the original array, because the first element from the original array iteration, if the elements to meet the first element into the first element of its index is automatically increased by 1, if it does not meet the next to meet the elements put into the index remains unchanged, and so on, until all the elements of the completion of the process, and then finally to the original array to meet the requirements of the array Copy the original array to return to the new data. Code implementation is as follows:

public static int[] RemoveByForMarkCopy(int[] source, int[] added)
{
    var idx = 0;
    foreach (var item in source)
    {
        if (!(item))
        {
            // Marking valid elements
            source[idx++] = item;
        }
    }
    // Create a new array and copy the valid elements
    var array = new int[idx];
    (source, array, idx);
    return array;
}

6, through the For + mark + Resize way to achieve

This method and the previous method is basically the same, the main difference in the last step, this method is directly through the Array Resize static method to adjust the original array we want and return. Code implementation is as follows:

public static int[] RemoveByForMarkResize(int[] source, int[] added)
{
    var idx = 0;
    foreach (var item in source)
    {
        if (!(item))
        {
            //Marking valid elements
            source[idx++] = item;
        }
    }
    //Resizing arrays
    (ref source, idx);
    return source;
}

Again we do another set of benchmarks to compare and the results are as follows:

You can see that the performance of the last two methods gets worse as the number of elements in the array increases, while the other four methods are not very different. In this case, we will choose the Array native method FindAll.

03, realize the encapsulation method

The two methods of adding deletions have been identified, and our first goal is solved.

Since the method to be encapsulated as a public, it is necessary to have good compatibility, although our examples are used int type array, but the actual use of the type do not know what type will be encountered, so the best way is to choose the generalized method. So the second goal is solved.

So what about the third goal of ease of use? The first idea is to make a public method, directly to make a helper class, such as ArrayHelper, and then put the two implementations directly to the static method.

However, I prefer to use the extension method for two reasons, one is that you can use the editor to intelligently prompt for the method directly, and the other is that the code is more concise. The two forms are as follows, which one do you prefer?

//Extended Methods
var result = (added);
//Static helper class methods
var result = (source, added);

Now another question, does this method return the final result as a return value? Or does it modify the original array directly? Both ways have their advantages, return to the new array, the original array is unchanged to facilitate chaining calls also to avoid some side effects, directly modify the original array memory efficiency.

Our two methods, Add Element and Delete Element, have semantics that are more closely aligned with manipulating the raw data and having the result act on itself. So I prefer the no-return-value approach.

Now there is an awkward problem, I don't know if you remember our last chapter "C#|.net core Foundation - Value Passing vs Reference Passing" about Value Passing and Reference Passing, here is such a problem, if we want to use the extension method and no return value to modify the original array directly, then we need to extend the method to the first parameter to use the ref qualifier, but the extension method has a But the extension method has a restriction on this [the first parameter must be struct or a generic type constrained to be a struct], and displaying a generic array doesn't satisfy this restriction. Therefore, I can not do in my mind the most ideal way of encapsulation, the following look at the extension method and help class code implementation, you can use it as needed.

public static class ArrayExtensions
{
    public static T[] AddRange<T>(this T[] source, T[] added)
    {
        var size =  + ;
        var array = new T[size];
        (source, array, );
        (added, 0, array, , );
        return array;
    }
    public static T[] RemoveAll<T>(this T[] source, Predicate<T> match)
    {
        return (source, a => !match(a));
    }
}
public static class ArrayHelper
{
    public static void AddRange<T>(ref T[] source, T[] added)
    {
        var size =  + ;
        var array = new T[size];
        (source, array, );
        (added, 0, array, , );
        source = array;
    }
    public static void RemoveAll<T>(ref T[] source, Predicate<T> match)
    {
        source = (source, a => !match(a));
    }
}

classifier for sums of money: The test method code as well as the sample source code have been uploaded to the code repository for those who are interested./hugogoos/Planner