How to cast System.Object[*] to System.Object[]
Asked Answered
T

4

7

When I Tried to return an Array in VFP9 language COM/DLL to my .NET C# project I receive a System.Object[*] array and I can not cast to System.Object[] (Without asterisk).

Twice answered 16/9, 2010 at 22:20 Comment(10)
What is an Object[*]? Do you mean an Object* as in an unmanaged pointer to an Object array?Melioration
@Callum: No, it is a System.Array with a non-zero starting index.Phallic
Yes @Callum, so How I can work with this?Twice
@Timwi: Yikes, you can have arrays (as in real System.Array s) that don't start at zero? As in they would give an IndexOutOfRangeException if you tried to access the zeroth element? How is this useful?Melioration
@Cheva: So it is a pointer problem then... do you know the size of the array or is it null terminated?Melioration
@Callum: Yes, although the only way to access such an array in C# is to use Array.GetValue(), and yes, you would get the IndexOutOfRangeException. The reason the CLR supports such arrays is because of VB.NET.Phallic
(Also, I doubt Cheva knew what they were saying when they answered your question with “yes”. I don’t think it’s a pointer problem.)Phallic
@Timwi: That's why I haven't answered. I'm guessing this VFP language uses arrays starting at 1 or some other value.Melioration
@Callum Rogers, hopefully it isn't "some other value"... An array that starts at 42 would be really weird ;) (although you can create such an array in C#...)Carborundum
(I have once had a reasonable usecase for an array that starts at −1.)Phallic
C
9

Timwi's solution should work fine. You can do something a bit simpler using Linq:

object[] newArray = sourceArray.Cast<object>().ToArray();

In case you need to recreate a System.Object[*] to pass it back to VFP, you can use this overload of the Array.CreateInstance method:

public static Array CreateInstance(
    Type elementType,
    int[] lengths,
    int[] lowerBounds
)

You can use it as follows:

object[] normalArray = ...

// create array with lower bound of 1
Array arrayStartingAt1 =
    Array.CreateInstance(
        typeof(object),
        new[] { normalArray.Length },
        new[] { 1 });

Array.Copy(normalArray, 0, arrayStartingAt1, 1, normalArray.Length);
Carborundum answered 16/9, 2010 at 22:33 Comment(7)
Somehow I never think of the solutions which are shorter but much slower. I realise that in most cases the performance penalty is insignificant...Phallic
Does this work because the Cast can simply iterate over the array using an IEnumerator instead of worrying where the array starts? If so couldn't you just use Select(x => x).ToArray() (or even justToArray() ) instead? It would remove the need for a cast from object to object.Melioration
Indeed, it will be a bit slower. When the source sequence implements ICollection<T>, ToArray takes advantage of that, but arrays with non-zero bounds only implements the non-generic ICollection...Carborundum
@Callum Rogers, yes, exactly. Array implements IEnumerableCarborundum
You MASTER!. Thank you very much!Twice
@Myself: You can't just use ToArray() or Select() because Array only implements the non-generic IEnumerable and they both require IEnumerable<T>. +1 for this answer for cleverness.Melioration
What happens if I have a method like public int ReadFPGA( out object[*] param, ...) in a dll, where the run-time version is 4.0.x? The project and all other dll is 4.5.2., only this one is 4.0.x.Seymore
P
6

Unfortunately, you cannot cast it directly. You can, however, create a new array of type object[] and copy the data over. Something like...

Array sourceArray = ...;

if (sourceArray.Rank != 1)
    throw new InvalidOperationException("Expected a single-rank array.");

object[] newArray = new object[sourceArray.Length];
Array.Copy(sourceArray, sourceArray.GetLowerBound(0),
           newArray, 0, sourceArray.Length);
Phallic answered 16/9, 2010 at 22:29 Comment(2)
Great, this works, but if you have thousands of items, you think? 'll Keep testing with queries or grades.Twice
googling I find a VFP instruction for this from VFP source code: the function ComArray(This.newArray, 10) that pass to based zero. the inverse methodTwice
P
6

I had a similar issue. Got an array as a dynamic object from an interop assembly, also starting from index one. When I tried to convert this into an Array object, I got the same error message.
Doing as the other answers suggest did not work. For a strange reason, even reading the Length property raised the exception.
I found this answer, and it worked.
Apparently, if you use C# 4.0, you have to cast the dynamic to object first, then you can convert it to Array. In prior versions of .NET you can cast directly.
Here is an Explanation why.

Publicist answered 19/12, 2016 at 16:43 Comment(2)
Thanks for posting this! All the other stuff doesn't work with a dynamic.Macula
Up-voted. Very good information which I also discovered now myself. See so-called thread II for more.Congregational
L
0

I've encountered this issue when reading arrays dynamically from an API which returns data from a non-.NET platform which supports arbitrary ranges for array indexes (IEC 61131-3 PLC programming language arrays). This platform also supports multi-dimensional arrays with different lower bounds for each dimension!

Here's the code I wrote to convert the arrays. It works for both 1d and multi-dimensional arrays with arbitrary lower bounds, converting them to a .NET array of the same underlying type, rank, and shape.

public static Array ConvertToZeroBaseIndexes(this Array arrayValue)
{
    // If it's already a zero-based array, just return it instead of doing a pointless array copy
    if ((arrayValue.Rank == 1 && arrayValue.GetLowerBound(0) == 0) ||
        Enumerable.Range(0, arrayValue.Rank).All(dimension => arrayValue.GetLowerBound(dimension) == 0))
    {
        return arrayValue;
    }

    Type? arrayElementType = arrayValue.GetType().GetElementType();
    if (arrayElementType == null) throw new InvalidOperationException("Cannot convert array of unknown type");

    var dimensionSizes = Enumerable.Range(0, arrayValue.Rank)
        .Select(dimension => arrayValue.GetUpperBound(dimension) - arrayValue.GetLowerBound(dimension) + 1)
        .ToArray();

    Int32 elementCount = dimensionSizes.Aggregate((product, dimensionSize) => product * dimensionSize);

    var target = Array.CreateInstance(arrayElementType, dimensionSizes);
    Array.Copy(arrayValue, target, elementCount);

    return target;
}
Livelihood answered 11/1 at 7:42 Comment(0)

© 2022 - 2024 — McMap. All rights reserved.