- Home /
Passing a generic Array with unknow dimension
Hello, I want to create an extension method that go throuth all elements of any multidimentional generic array. But I completely new to the generic concept. Is it possible to passing a generic array with unknow dimension as parameter throuth a function?
It's all I have for the moment:
public static class ExtensionMethods
{
public static void DebugArrayDimension<T>(T arrayUnknownDimension)
{
Debug.Log(arrayUnknownDimension.Rank); //Error
}
}
Answer by Zodiarc · Feb 22, 2018 at 11:00 AM
Instead of using arrays, maybe try using ArrayLists. An ArrayList functions the same way as an array, but has some convenience methods and is dynamic in size. They're also generic by design.
Answer by Chmyke · Feb 22, 2018 at 12:37 PM
Thank for your replies but I 'm not really satisfied by them.
Maybe I didn't understand them well or maybe I wasn't clear enough in my question.
What I want is to merge this methods in one more generic:
public static void Debug1DArrayDimension<T>(T[] array)
{
Debug.Log(array.Rank); // print 1
}
public static void Debug2DArrayDimension<T>(T[,] array)
{
Debug.Log(array.Rank); // print 2
}
public static void Debug3DArrayDimension<T>(T[,,] array)
{
Debug.Log(array.Rank); // print 3
}
I don't know if this would work iteratively but you can probably achieve this by diong a recursive method (untested):
public void DebugArray<T>(T[] array) {
foreach(T element in array) {
if(element.GetType().IsArray) {
DebugArray<T>(element);
} else {
// do your stuff here
}
}
}
I give no guarantee that it will work immediatelly (or at all), but maybe that's a lead. But after thinking further into this, I think what you try to do is not doable. Even if you would use collections. You cannot declare an array or collection to take more than one type and int is not the same type as int[].
That might work for an array of arrays, but the OP is asking about multidimensional arrays.
Note that...
1) Each element of a T[,]
is a T
, not a T[]
.
2) You wouldn't be able to pass a T[,]
as the parameter to your function.
Thank for the lead, I will deep that and come back if I find the answer! :)
What's the use case? Seems to me that in order for it be useful, you'd need to have a array variable where you didn't know what rank it had. But there's a natural limit to how much you can do with such a variable (without casting it to an array of the appropriate rank, which of course you can only do if you know what the rank is).
If it's just syntactic simplicity that you're after, you could turns those 3 functions into extension methods, then they could share a name at least (Edit - you could of course give them the same name anyway since they have different parameter types).
Another approach would be to write your own wrapper class to represent an array of arbitrary rank.
Or switching to jagged arrays might make things easier (then you could use an approach along the lines of what Zodiarc posted in a comment above).
But I think the use case matters here - I suspect that none of the the above is actually going to be worthwhile (unless the wrapper class approach brings other benefits).
Yes of course. He could simply overload the method and theoretically for each lower dimension call the method which handles the lower dimension.
Great, I can indeed just overload the method cause I only wanted syntactic simplicity. So thank you my problem is solved! What I'm trying to to achieve is an extension methods that check all elements of my array no matter the dimension.
public static void DebugArrayDimension(T[] array)
{
Debug.Log(array.Rank); // print 1
}
public static void DebugArrayDimension<T>(T[,] array)
{
Debug.Log(array.Rank); // print 2
}
public static void DebugArrayDimension<T>(T[,,] array)
{
Debug.Log(array.Rank); // print 3
}
Answer by tekxpert · Feb 22, 2018 at 05:00 AM
Creation of array whose component type is either a type parameter, a concrete parameterized type or a bounded wildcard parameterized type, is type-unsafe.
public T[] getArray(int size) { T[] arr = new T[size]; // Suppose this was allowed for the time being. return arr; }
The type of T is not known at runtime, the array created is actually an Object[]. So the above method at runtime will look like:
public Object[] getArray(int size) { Object[] arr = new Object[size]; return arr; }
Sorry but this is really confusing. Is there any relevance to the "iTunes Error" link (since removed)?
What do you mean by "the type of T is not known at runtime"?
And what is the answer to the OP's question?
Answer by Bunny83 · Feb 22, 2018 at 04:09 PM
Well, this doesn't work with generics. They way generic code and generic type parameters work is that any code that involves variables of the given generic type can only access things that all possible type parameters have in common. Since you don't have any constraint on your type parameter "T" it could be literally any type. you could pass an "int" , a string, an array or an object. The thing with generics is that the code is compiled at compile time and has to work consistently while the actual type is bound at runtime.
So a generic type parameter without constraints would only provide the things defined in System.Object which is just "ToString()", "GetType()", "GetHashCode()" and "Equals()". So for generics to actually be useful you have to use a type constraint. The point of generics is to provide type safe code on similar objects. Though while different arrays are in deed similar they are not the same. Native arrays are implemented though actual IL opcode iistructions. The common base class System.Array is not a true base class. It's not possible to use that pseudo-base-cass System.Array as generic constraint. The compiler won't allow this. The Array base class is similar to the ValueType or Enum base class. It's not a true base class.
However every native array type can be assigned to a variable of type System.Array. In the end you have to realize that multidimensional arrays do not really exist ^^. Every array is essentially just a chunk of memory and the elements are laid out linearly one after another. The multidimensional array support consists of special get and set methods that just handle the index mapping for you. Unfortunately if you have a multidimensional array you don't get direct access to the actual 1d array. This is mainly due to the fact that how multidimensional arrays are implemented is an implementation details of the CLR which could even change. Though what you do get is a linear read-only access to all elements since every array implements the IEnumerable interface which gives you read access to all elements as a linear sequence. So the easiest way to "debug" / iterate through all elements of any kind of array is to use a method like this:
public void DebugArray(System.Array aArray)
{
foreach(object o in aArray)
{
Debug.Log(o.ToString());
}
}
This just ignores the dimensions. If you want / need to know the actual indices you have to actually iterate through them:
public void DebugArray(System.Array aArray)
{
if (aArray == null || aArray.Length == 0)
return;
var indices = new int[aArray.Rank];
var maxIndex = new int[aArray.Rank];
for(int i = 0; i < maxIndex.Length; i++)
maxIndex[i] = aArray.GetLength(i);
bool done = false;
while (!done)
{
object val = aArray.GetValue(indices);
Debug.Log("["+indices.Aggregate("",(a,v)=>a+","+v.ToString())+"] "+ val.ToString());
for(int i = indices.Length-1; i >=0; i--)
{
if (++indices[i] < maxIndex[i])
break;
else if (i > 0)
indices[i] = 0;
else
done = true;
}
}
}
The while loop + the inner for loop allows to handle (almost) infinite nested for loops. The two int arrays hold the loop variable (indices) and the element count (maxIndex) for each dimension. The for loop does the incrementing of all variables and does handle the "carrying" between the dimensions as well as handling the exit condition once all elements have been processed.
Note: This method only works with arrays which have a starting index at 0. .NET allows arrays to have a lower bound different from 0. In that case the code would become a bit more complicated since we can't simply use GetLength but we have to use GetLowerBound and GetUpperBound to get the min and max values and adjust the incrementing code accordingly. Though almost nobody uses them as they have a slight performance penalty and the C# language doesn't have a direct syntax to create them. You can only create them through "Array.CreateInstance". Anyways if you want to be on the safe side, here's a version that works with multidimensional arrays of any rank and with any bounds:
public void DebugArray(System.Array aArray)
{
if (aArray == null || aArray.Length == 0)
return;
var indices = new int[aArray.Rank];
var minIndex = new int[aArray.Rank];
var maxIndex = new int[aArray.Rank];
for (int i = 0; i < maxIndex.Length; i++)
{
indices[i] = minIndex[i] = aArray.GetLowerBound(i);
maxIndex[i] = aArray.GetUpperBound(i);
}
bool done = false;
while (!done)
{
object val = aArray.GetValue(indices);
Debug.Log("["+indices.Aggregate("",(a,v)=>a+","+v.ToString())+"] "+ val.ToString());
for(int i = indices.Length-1; i >=0; i--)
{
if (++indices[i] <= maxIndex[i])
break;
else if (i > 0)
indices[i] = minIndex[i];
else
done = true;
}
}
}
ps: Just to make it clear: The "Aggregate" extension method is a Linq extension. So you need a using System.Linq;
at the top. It's just a quick and dirty way to convert our indices array into a comma seperated string. If the leading comma bugs you, add a .SubString(1)
right after the Aggregate call.
Your answer
