ConvertAll Methods Implementation for Multidimensional Array in C# – follow-up

Posted on

Problem

This is a follow-up question for ConvertAll Methods Implementation for Multidimensional Array in C#. Thanks to aepot’s answer and Olivier’s answer. In order to match the usage of the build-in API Array.ConvertAll, the input /output array types of the implemented methods below are similar to [], [,], [,,] and etc. style.

The experimental implementation

The experimental implementation is as below.

public static TOutput[,] ConvertAll<TInput, TOutput>(TInput[,] array, Converter<TInput, TOutput> converter)
    where TInput : unmanaged
    where TOutput : unmanaged
{
    return (TOutput[,])ConvertArray(array, converter);
}

public static TOutput[,,] ConvertAll<TInput, TOutput>(TInput[,,] array, Converter<TInput, TOutput> converter)
    where TInput : unmanaged
    where TOutput : unmanaged
{
    return (TOutput[,,])ConvertArray(array, converter);
}

public static TOutput[,,,] ConvertAll<TInput, TOutput>(TInput[,,,] array, Converter<TInput, TOutput> converter)
    where TInput : unmanaged
    where TOutput : unmanaged
{
    return (TOutput[,,,])ConvertArray(array, converter);
}

public static TOutput[,,,,] ConvertAll<TInput, TOutput>(TInput[,,,,] array, Converter<TInput, TOutput> converter)
    where TInput : unmanaged
    where TOutput : unmanaged
{
    return (TOutput[,,,,])ConvertArray(array, converter);
}

public static TOutput[,,,,,] ConvertAll<TInput, TOutput>(TInput[,,,,,] array, Converter<TInput, TOutput> converter)
    where TInput : unmanaged
    where TOutput : unmanaged
{
    return (TOutput[,,,,,])ConvertArray(array, converter);
}

public static TOutput[,,,,,,] ConvertAll<TInput, TOutput>(TInput[,,,,,,] array, Converter<TInput, TOutput> converter)
    where TInput : unmanaged
    where TOutput : unmanaged
{
    return (TOutput[,,,,,,])ConvertArray(array, converter);
}

public static TOutput[,,,,,,,] ConvertAll<TInput, TOutput>(TInput[,,,,,,,] array, Converter<TInput, TOutput> converter)
    where TInput : unmanaged
    where TOutput : unmanaged
{
    return (TOutput[,,,,,,,])ConvertArray(array, converter);
}

public static TOutput[,,,,,,,,] ConvertAll<TInput, TOutput>(TInput[,,,,,,,,] array, Converter<TInput, TOutput> converter)
    where TInput : unmanaged
    where TOutput : unmanaged
{
    return (TOutput[,,,,,,,,])ConvertArray(array, converter);
}

public static TOutput[,,,,,,,,,] ConvertAll<TInput, TOutput>(TInput[,,,,,,,,,] array, Converter<TInput, TOutput> converter)
    where TInput : unmanaged
    where TOutput : unmanaged
{
    return (TOutput[,,,,,,,,,])ConvertArray(array, converter);
}

static Array ConvertArray<T, TResult>(Array array, Converter<T, TResult> converter)
    where T : unmanaged
    where TResult : unmanaged
{
    if (array is null)
    {
        throw new ArgumentNullException(nameof(array));
    }

    if (converter is null)
    {
        throw new ArgumentNullException(nameof(converter));
    }

    long[] dimensions = new long[array.Rank];
    for (int i = 0; i < array.Rank; i++)
        dimensions[i] = array.GetLongLength(i);

    Array result = Array.CreateInstance(typeof(TResult), dimensions);
    TResult[] tmp = new TResult[1];
    int offset = 0;
    int itemSize = System.Runtime.InteropServices.Marshal.SizeOf(typeof(TResult));
    foreach (T item in array)
    {
        tmp[0] = converter(item);
        Buffer.BlockCopy(tmp, 0, result, offset * itemSize, itemSize);
        offset++;
    }
    return result;
}

All suggestions are welcome.

The summary information:

Solution

When I see that many recurrences like this then it is a good sign for me that this is a perfect place to introduce meta-programming via T4.

Step #1

Separate the overloads from the core logic:

using System;

namespace Sample.Core
{
    internal static class Converter
    {
        public static Array ConvertArray<T, TResult>(Array array, Converter<T, TResult> converter)
            where T : unmanaged
            where TResult : unmanaged
        {
            if (array is null)
            {
                throw new ArgumentNullException(nameof(array));
            }

            if (converter is null)
            {
                throw new ArgumentNullException(nameof(converter));
            }

            long[] dimensions = new long[array.Rank];
            for (int i = 0; i < array.Rank; i++)
                dimensions[i] = array.GetLongLength(i);

            Array result = Array.CreateInstance(typeof(TResult), dimensions);
            TResult[] tmp = new TResult[1];
            int offset = 0;
            int itemSize = System.Runtime.InteropServices.Marshal.SizeOf(typeof(TResult));
            foreach (T item in array)
            {
                tmp[0] = converter(item);
                Buffer.BlockCopy(tmp, 0, result, offset * itemSize, itemSize);
                offset++;
            }
            return result;
        }
    }
}

Step #2

Introduce a *.tt file with the following content:

<#@ template language="C#" #>
<#@ assembly name="System.Core" #>
<#@ import namespace="System.Linq" #>
<#@ import namespace="System.Text" #>
<#@ import namespace="System.Collections.Generic" #>
using System;
using static Sample.Core.Converter;

namespace Sample.Overloads
{
    public static class ArrayConverter
    {
<#
for(int i = 1; i < 10; i++)
{
var dimensions = new String(',', i);
#>
        public static TOutput[<#=dimensions#>] ConvertAll<TInput, TOutput>(TInput[<#=dimensions#>] array, Converter<TInput, TOutput> converter)
            where TInput : unmanaged
            where TOutput : unmanaged
        {
            return (TOutput[<#=dimensions#>])ConvertArray(array, converter);
        }
<#
}
#>
    }
}

Step #3

Execute the tt file and examine the generated code:

using System;
using static Sample.Core.Converter;

namespace Sample.Overloads
{
    public static class ArrayConverter
    {
        public static TOutput[,] ConvertAll<TInput, TOutput>(TInput[,] array, Converter<TInput, TOutput> converter)
            where TInput : unmanaged
            where TOutput : unmanaged
        {
            return (TOutput[,])ConvertArray(array, converter);
        }
        public static TOutput[,,] ConvertAll<TInput, TOutput>(TInput[,,] array, Converter<TInput, TOutput> converter)
            where TInput : unmanaged
            where TOutput : unmanaged
        {
            return (TOutput[,,])ConvertArray(array, converter);
        }
        public static TOutput[,,,] ConvertAll<TInput, TOutput>(TInput[,,,] array, Converter<TInput, TOutput> converter)
            where TInput : unmanaged
            where TOutput : unmanaged
        {
            return (TOutput[,,,])ConvertArray(array, converter);
        }
        public static TOutput[,,,,] ConvertAll<TInput, TOutput>(TInput[,,,,] array, Converter<TInput, TOutput> converter)
            where TInput : unmanaged
            where TOutput : unmanaged
        {
            return (TOutput[,,,,])ConvertArray(array, converter);
        }
        public static TOutput[,,,,,] ConvertAll<TInput, TOutput>(TInput[,,,,,] array, Converter<TInput, TOutput> converter)
            where TInput : unmanaged
            where TOutput : unmanaged
        {
            return (TOutput[,,,,,])ConvertArray(array, converter);
        }
        public static TOutput[,,,,,,] ConvertAll<TInput, TOutput>(TInput[,,,,,,] array, Converter<TInput, TOutput> converter)
            where TInput : unmanaged
            where TOutput : unmanaged
        {
            return (TOutput[,,,,,,])ConvertArray(array, converter);
        }
        public static TOutput[,,,,,,,] ConvertAll<TInput, TOutput>(TInput[,,,,,,,] array, Converter<TInput, TOutput> converter)
            where TInput : unmanaged
            where TOutput : unmanaged
        {
            return (TOutput[,,,,,,,])ConvertArray(array, converter);
        }
        public static TOutput[,,,,,,,,] ConvertAll<TInput, TOutput>(TInput[,,,,,,,,] array, Converter<TInput, TOutput> converter)
            where TInput : unmanaged
            where TOutput : unmanaged
        {
            return (TOutput[,,,,,,,,])ConvertArray(array, converter);
        }
        public static TOutput[,,,,,,,,,] ConvertAll<TInput, TOutput>(TInput[,,,,,,,,,] array, Converter<TInput, TOutput> converter)
            where TInput : unmanaged
            where TOutput : unmanaged
        {
            return (TOutput[,,,,,,,,,])ConvertArray(array, converter);
        }
    }
}
```

Leave a Reply

Your email address will not be published. Required fields are marked *