Two things related to array type name came to my mind while writing the previous post.

Given an one-dimensional non-zero based double array x, x.GetType().ToString() returns "System.Double[*]"; we can use Type.GetType("System.Double[*]") to get hold of such type easily. To get an instance of such type, instead of calling Array.CreateInstance, we can find ConstructorInfo first, and call ConstructorInfo.Invoke. Believe it or not, this type has 2 constructors, one is to create zero-based array, the other one for non-zero-based array.

Type t1 = Type.GetType("System.Double[*]");
ConstructorInfo[] ctors = t1.GetConstructors();
foreach (ConstructorInfo ci in ctors)
    Console.WriteLine(ci); // print "Void .ctor(Int32)" and "Void .ctor(Int32, Int32)"

Array a1 = (Array)ctors[0].Invoke(new object[] { 10 });
Console.WriteLine(a1.GetType()); // print "System.Double[]"

Array a2 = (Array)ctors[1].Invoke(new object[] { -5, 10 }); // lowerbound: -5
Console.WriteLine(a2.GetType()); // print "System.Double[*]"

Type.GetType("System.Int32[,][]") returns a jagged array, more precisely, a one-dimensional array whose elements are two-dimensional arrays, This is different from the type "int[,][]" declared in C#, which is a two-dimensional array of one-dimensional arrays. The type name grammar used in Type.GetType is more close to how IL describes such array: you can see a3's type as "int32[0...,0...][]" in ildasm.

Type t2 = Type.GetType("System.Int32[,][]");
int[][,] a3 = new int[2][,] { new int[,] { { 1, 2 } }, new int[,] { { 3, 4, 5 }, { 6, 7, 8 } } };
int[,][] a4 = new int[1, 2][] { { new int[] { 1, 2 }, new int[] { 3, 4, 5 } } };
Console.WriteLine(a3.GetType() == t2); // True
Console.WriteLine(a4.GetType() == t2); // False
.locals init (
   [6] int32[0...,0...][] a3, 
   [7] int32[][0...,0...] a4,