3

Given an array type (type.IsArray == true), how do you determine if a rank 1 array (type.GetArrayRank() == 1) is a vector or multi-dimensaional array?

var vectorArrayType = typeof(string).MakeArrayType();

var multiDimensionalArrayType = typeof(string).MakeArrayType(1);

Is there something better than looking for [] versus [*] in type.Name?

Suraj
  • 35,905
  • 47
  • 139
  • 250
  • How "one-dimensional" is different from "with 1 dimension"? – Alexei Levenkov Mar 26 '20 at 01:49
  • 3
    @AlexeiLevenkov [MakeArrayType](https://docs.microsoft.com/en-us/dotnet/api/system.type.makearraytype) "*Note The common language runtime makes a distinction between vectors (that is, one-dimensional arrays that are always zero-based) and multidimensional arrays. A vector, which always has only one dimension, is not the same as a multidimensional array that happens to have only one dimension. This method overload can only be used to create vector types, and it is the only way to create a vector type. Use the MakeArrayType(Int32) method overload to create multidimensional array types.*" – TheGeneral Mar 26 '20 at 01:54
  • Sorry bad paste, this terminology was all new to me – TheGeneral Mar 26 '20 at 01:55
  • 1
    @MichaelRandall wow... apparently I can't read :) thanks… Since it looks like `MakeArrayType` at returns cached type there is a way to compare `typeof(string).MakeArrayType() == typeof(string).MakeArrayType(1)` (false) - maybe it is the way OP is looking for? – Alexei Levenkov Mar 26 '20 at 02:02
  • @AlexeiLevenkov - I think you are saying that, given a rank 1 array type, that you could construct another type and test equality. So something like `testType == testType.GetElementType().MakeArrayType()` if that's true then it's a vector array, otherwise it's a multi-dimensional array. That's certainly one way to do it! – Suraj Mar 26 '20 at 02:07
  • Wondering if there's a more direct way - like some property or method on Type that I'm not finding. Also generally interested in creative ideas on this. – Suraj Mar 26 '20 at 02:08
  • See https://learn.microsoft.com/en-us/dotnet/csharp/language-reference/operators/type-testing-and-cast – jwdonahue Mar 26 '20 at 02:09
  • 1
    @jwdonahue - I don't see anything in that link that would help. – Suraj Mar 26 '20 at 02:10
  • @SFun28 yes. I believe that should be safe as if `MakeArrayType` would return unique types on every call one would not ever be able to assign resulting arrays to variables... But I don't know for sure. – Alexei Levenkov Mar 26 '20 at 02:10
  • 1
    Is this an [XY problem](https://en.wikipedia.org/wiki/XY_problem)? Do you want to distinguish between array types and vector types? Or 1D verses mulit-D arrays? – jwdonahue Mar 26 '20 at 02:12
  • 1
    @AlexeiLevenkov - I tested it and it works. – Suraj Mar 26 '20 at 02:13
  • @jwdonahue - I think you have the terminology mixed up. In .NET there are vector arrays and multi-dimensional arrays. A multi-dimensional array, confusingly, can have 1 dimension or more. A 1-dimesion, multi-dimensional array doesn't come up often, but it's a real thing in .NET. – Suraj Mar 26 '20 at 02:16
  • Assuming that you can compare e.g. to `typeof(string[])` to determine whether the typeis a vector or multidimensional array, and you've verified that works for you, you should post that as an answer and self-accept it. That said: you should really consider seriously the suggestion above that you're asking an [XY Problem](https://meta.stackexchange.com/questions/66377/what-is-the-xy-problem) question. At the very least, it would help if you provide some context as to _why_ you have code that cares; it doesn't seem like normally one would. – Peter Duniho Mar 26 '20 at 02:47
  • @SFun28, In C#, I am only aware of arrays, such as `int[]` and vectors like `Vector`. Perhaps you can provide an example of a "vector array"? But I do have a couple more decades experience in C++ than C# so I don't doubt that I get some terminology wrong now and then. – jwdonahue Mar 26 '20 at 03:34
  • @jwdonahue please read MSDN except posted by Michael in first comment - while that really does not make a lot of sense for C++/C# people... it better be useful. I.e. if indexes are [1..20] than it had to be "multidimension array of one dimension" as it can't be "vector" (which has indexes [0..19] for same number of elements). I can imagine that serializing such object for example will require extra "low bound" in addition to just length of array... maybe SFun28 have some similar reason (or maybe not :) ) – Alexei Levenkov Mar 26 '20 at 04:02
  • @SFun28, where/how do you get a multidimensional array that is indexed `[1..20]` in C#? CLR is not C# or .NET, though it is a component of their implementations. I wonder if the distinction is for optimization purposes? Perhaps for vector processors? Is that why you care? Well, at least you got your answer and I am a little less foggy. – jwdonahue Mar 26 '20 at 04:47
  • @AlexeiLevenkov - precisely! This is about serializing types. – Suraj Mar 26 '20 at 10:12
  • @jwdonahue - Ah. I think I see the confusion. I didn't say that the multi-dimensaional array was indexed stating at 1. I said it was 1-dimesional. Another way to think about it: a vector array and a multi-dimesional (confusing name) array are actually two different kinds of data structures under the covers. Lets just call them A and B to make it clear they are different things. So you have to decide whether you are creating an A or a B. A can ONLY have rank 1 (1 dimension). B can have rank 1 to 32 (1 to 32 dimensions). – Suraj Mar 26 '20 at 10:22
  • I don't know why/when you would use B with 1 dimension, but it's a real thing. Anyways, there isn't some convenient way to tell whether a Type is A or B (hence the need for this post). `type.IsArray` returns `true` for both. And for a 1-dimesional A and B, `type.GetArrayRank()` returns `1`. So how do you tell the difference? I'll post the answer in a little while based on @AlexeiLevenkov's idea. – Suraj Mar 26 '20 at 10:24
  • There are information provided by the `Type` class though. A vector is assignable to `IList`, whereas 1 dimensional array isn't. Also you could simply check whether the name contains `[*]` as starting from 2 dimensional the symbol type format would be `[,]`. – weichch Mar 26 '20 at 10:33
  • @weichch - of course! Good idea on IList! – Suraj Mar 26 '20 at 10:56
  • However, I wonder the use case, because turns out 1 dimensional array will be converted to vector upon being instantiated. `Activator.CreateInstance(string[*], 7)` creates `string[7]`. So why do you care about it being vector or 1 dimensional? – weichch Mar 26 '20 at 11:10
  • @weichch - for completeness =) Serializing an instance of a type and serializing the type itself are two different concerns. I'm trying to solve serializing the Type itself. – Suraj Mar 26 '20 at 12:20
  • Not sure what you meant by serializing Type itself. If you need to pass the type to another application, I would pass the name, and do `Type.GetType("System.String[*]")`, and I will find 1 dimensional array type :) – weichch Mar 26 '20 at 19:53

1 Answers1

0

Solution

public enum ArrayKind
{
    /// <summary>
    /// Not an array.
    /// </summary>
    None,

    /// <summary>
    /// A vector array.  Can only have a single dimension.
    /// </summary>
    Vector,

    /// <summary>
    /// A multidimensional array.  Can have 1 to 32 dimensions.
    /// </summary>
    MultiDimensional,
}

public static class TypeExtensions
{
    /// <summary>
    /// Determines the kind of array that the specified type is.
    /// </summary>
    /// <param name="type">The type.</param>
    /// <returns>
    /// The kind of array of the specified type.
    /// </returns>
    /// <exception cref="ArgumentNullException"><paramref name="type"/> is null.</exception>
    public static ArrayKind GetArrayKind(
        this Type type)
    {
        if (type == null)
        {
            throw new ArgumentNullException(nameof(type));
        }

        ArrayKind result;

        if (!type.IsArray)
        {
            result = ArrayKind.None;
        }
        else if (type.GetArrayRank() > 1)
        {
            result = ArrayKind.MultiDimensional;
        }
        else if (type == type.GetElementType().MakeArrayType())
        {
            result = ArrayKind.Vector;
        }
        else
        {
            result = ArrayKind.MultiDimensional;
        }

        return result;
    }
}

Tests

Requires xUnit and FluentAssertions

    [Fact]
    public static void GetArrayKind___Should_throw_ArgumentNullException___When_parameter_type_is_null()
    {
        // Arrange, Act
        var actual = Record.Exception(() => TypeExtensions.GetArrayKind(null));

        // Assert
        actual.Should().BeOfType<ArgumentNullException>();
        actual.Message.Should().Contain("type");
    }

    [Fact]
    public static void GetArrayKind___Should_return_ArrayKind_None___When_type_is_not_an_array()
    {
        // Arrange
        var types = new[]
        {
            typeof(object),
            typeof(string),
            typeof(Guid),
            typeof(DateTime),
            typeof(int),
            typeof(Guid?),
            typeof(DateTime?),
            typeof(int?),
            typeof(IReadOnlyCollection<object>),
            typeof(IReadOnlyCollection<string>),
            typeof(IReadOnlyCollection<Guid>),
            typeof(IReadOnlyCollection<DateTime>),
            typeof(IReadOnlyCollection<int>),
            typeof(List<object>),
            typeof(List<string>),
            typeof(List<Guid>),
            typeof(List<DateTime>),
            typeof(List<int>),
        };

        // Act
        var actuals = types.Select(_ => _.GetArrayKind()).ToList();

        // Assert
        actuals.Should().AllBeEquivalentTo(ArrayKind.None);
    }

    [Fact]
    public static void GetArrayKind___Should_return_ArrayKind_Vector___When_type_is_a_vector_array()
    {
        // Arrange
        var types = new[]
        {
            typeof(object[]),
            typeof(string[]),
            typeof(Guid[]),
            typeof(DateTime[]),
            typeof(int[]),
            typeof(Guid?[]),
            typeof(DateTime?[]),
            typeof(int?[]),
            typeof(IReadOnlyCollection<object>[]),
            typeof(IReadOnlyCollection<string>[]),
            typeof(IReadOnlyCollection<Guid>[]),
            typeof(IReadOnlyCollection<DateTime>[]),
            typeof(IReadOnlyCollection<int>[]),
            typeof(List<object>[]),
            typeof(List<string>[]),
            typeof(List<Guid>[]),
            typeof(List<DateTime>[]),
            typeof(List<int>[]),
            typeof(object[][]),
            typeof(string[][]),
            typeof(Guid[][]),
            typeof(DateTime[][]),
            typeof(int[][]),
            typeof(Guid?[][]),
            typeof(DateTime?[][]),
            typeof(int?[][]),
            typeof(IReadOnlyCollection<object>[][]),
            typeof(IReadOnlyCollection<string>[][]),
            typeof(IReadOnlyCollection<Guid>[][]),
            typeof(IReadOnlyCollection<DateTime>[][]),
            typeof(IReadOnlyCollection<int>[][]),
            typeof(List<object>[][]),
            typeof(List<string>[][]),
            typeof(List<Guid>[][]),
            typeof(List<DateTime>[][]),
            typeof(List<int>[][]),
            typeof(object[][,]),
            typeof(string[][,]),
            typeof(Guid[][,]),
            typeof(DateTime[][,]),
            typeof(int[][,]),
            typeof(Guid?[][,]),
            typeof(DateTime?[][,]),
            typeof(int?[][,]),
            typeof(IReadOnlyCollection<object>[][,]),
            typeof(IReadOnlyCollection<string>[][,]),
            typeof(IReadOnlyCollection<Guid>[][,]),
            typeof(IReadOnlyCollection<DateTime>[][,]),
            typeof(IReadOnlyCollection<int>[][,]),
            typeof(List<object>[][,]),
            typeof(List<string>[][,]),
            typeof(List<Guid>[][,]),
            typeof(List<DateTime>[][,]),
            typeof(List<int>[][,]),
        };

        // Act
        var actuals = types.Select(_ => _.GetArrayKind()).ToList();

        // Assert
        actuals.Should().AllBeEquivalentTo(ArrayKind.Vector);
    }

    [Fact]
    public static void GetArrayKind___Should_return_ArrayKind_MultiDimensional___When_type_is_a_multidimensional_array()
    {
        // Arrange
        var types = new[]
        {
            typeof(object[,]),
            typeof(string[,]),
            typeof(Guid[,]),
            typeof(DateTime[,]),
            typeof(int[,]),
            typeof(Guid?[,]),
            typeof(DateTime?[,]),
            typeof(int?[,]),
            typeof(IReadOnlyCollection<object>[,]),
            typeof(IReadOnlyCollection<string>[,]),
            typeof(IReadOnlyCollection<Guid>[,]),
            typeof(IReadOnlyCollection<DateTime>[,]),
            typeof(IReadOnlyCollection<int>[,]),
            typeof(List<object>[,]),
            typeof(List<string>[,]),
            typeof(List<Guid>[,]),
            typeof(List<DateTime>[,]),
            typeof(List<int>[,]),
            typeof(object[,][]),
            typeof(string[,][]),
            typeof(Guid[,][]),
            typeof(DateTime[,][]),
            typeof(int[,][]),
            typeof(Guid?[,][]),
            typeof(DateTime?[,][]),
            typeof(int?[,][]),
            typeof(IReadOnlyCollection<object>[,][]),
            typeof(IReadOnlyCollection<string>[,][]),
            typeof(IReadOnlyCollection<Guid>[,][]),
            typeof(IReadOnlyCollection<DateTime>[,][]),
            typeof(IReadOnlyCollection<int>[,][]),
            typeof(List<object>[,][]),
            typeof(List<string>[,][]),
            typeof(List<Guid>[,][]),
            typeof(List<DateTime>[,][]),
            typeof(List<int>[,][]),
            typeof(object).MakeArrayType(1),
            typeof(string).MakeArrayType(1),
            typeof(Guid).MakeArrayType(1),
            typeof(DateTime).MakeArrayType(1),
            typeof(int).MakeArrayType(1),
            typeof(Guid?).MakeArrayType(1),
            typeof(DateTime?).MakeArrayType(1),
            typeof(int?).MakeArrayType(1),
            typeof(IReadOnlyCollection<object>).MakeArrayType(1),
            typeof(IReadOnlyCollection<string>).MakeArrayType(1),
            typeof(IReadOnlyCollection<Guid>).MakeArrayType(1),
            typeof(IReadOnlyCollection<DateTime>).MakeArrayType(1),
            typeof(IReadOnlyCollection<int>).MakeArrayType(1),
            typeof(List<object>).MakeArrayType(1),
            typeof(List<string>).MakeArrayType(1),
            typeof(List<Guid>).MakeArrayType(1),
            typeof(List<DateTime>).MakeArrayType(1),
            typeof(List<int>).MakeArrayType(1),
            typeof(object[]).MakeArrayType(1),
            typeof(string[]).MakeArrayType(1),
            typeof(Guid[]).MakeArrayType(1),
            typeof(DateTime[]).MakeArrayType(1),
            typeof(int[]).MakeArrayType(1),
            typeof(Guid?[]).MakeArrayType(1),
            typeof(DateTime?[]).MakeArrayType(1),
            typeof(int?[]).MakeArrayType(1),
            typeof(IReadOnlyCollection<object>[]).MakeArrayType(1),
            typeof(IReadOnlyCollection<string>[]).MakeArrayType(1),
            typeof(IReadOnlyCollection<Guid>[]).MakeArrayType(1),
            typeof(IReadOnlyCollection<DateTime>[]).MakeArrayType(1),
            typeof(IReadOnlyCollection<int>[]).MakeArrayType(1),
            typeof(List<object>[]).MakeArrayType(1),
            typeof(List<string>[]).MakeArrayType(1),
            typeof(List<Guid>[]).MakeArrayType(1),
            typeof(List<DateTime>[]).MakeArrayType(1),
            typeof(List<int>[]).MakeArrayType(1),
        };

        // Act
        var actuals = types.Select(_ => _.GetArrayKind()).ToList();

        // Assert
        actuals.Should().AllBeEquivalentTo(ArrayKind.MultiDimensional);
    }
Suraj
  • 35,905
  • 47
  • 139
  • 250