Search Unity

  1. Welcome to the Unity Forums! Please take the time to read our Code of Conduct to familiarize yourself with the forum rules and how to post constructively.
  2. Dismiss Notice

Cannot cast Object[] to Texture2D[] in c#?

Discussion in 'Scripting' started by techmage, May 13, 2011.

  1. techmage

    techmage

    Joined:
    Oct 31, 2009
    Posts:
    2,133
    I am trying to do this:



    gallery.galleryImages = (Texture2D) Resources.LoadAll("Images/" + galleryAttributes.nameOfGalleries, typeof(Texture2D));

    galleryImages is an array itself, and it is initialized, and this gives me no compile errors

    however when I run, I get this:
    InvalidCastException: Cannot cast from source type to destination type.

    Why can I not cast an Object Array to a Texture2D array?

    This code does work when I remove the cast and make galleryImages into an Object[], so the error is all dependent just on the carting of Object[] to Texture2d[] in that single line

    Does anyone know why this does this?
     
  2. Jessy

    Jessy

    Joined:
    Jun 7, 2007
    Posts:
    7,325
    You forgot the brackets in your cast.
     
  3. techmage

    techmage

    Joined:
    Oct 31, 2009
    Posts:
    2,133
    Oops, forgot to put the brackets in the example code, I did originally have brackets in the code that was giving me the error

    gallery.galleryImages = (Texture2D[]) Resources.LoadAll("Images/" + galleryAttributes.nameOfGalleries, typeof(Texture2D));

    That still gives me the error

    InvalidCastException: Cannot cast from source type to destination type.

    When using Object[] it works fine and I can later cast all the calls to .galleryImages to Texture2D just fine. Is there something that specifically inhibits you from casting the Resources.LoadAll function?
     
    Last edited: May 13, 2011
  4. Jessy

    Jessy

    Joined:
    Jun 7, 2007
    Posts:
    7,325
    No problems here with generic variables; it must be some problem with your variable types not matching.

    Code (csharp):
    1. Texture2D[] textures = (Texture2D[]) Resources.LoadAll("Images/", typeof(Texture2D));
     
  5. andorov

    andorov

    Joined:
    Feb 10, 2011
    Posts:
    1,061
    An Object array (Object[]) is not the same as Texture2D array (Texture2D[]).

    Even if all the elements in the Object array are of type Texture2D, you cannot simply cast it by doing (Texture2D[])ArrayOfObjects.

    Resources.LoadAll explicitly returns an Object[]. You must declare a Texture2D[...] and then copy over the references. This is NOT a C# generic function.
     
  6. Jessy

    Jessy

    Joined:
    Jun 7, 2007
    Posts:
    7,325
    No, it really works fine. Try it.

    Code (csharp):
    1. [SerializeField] GameObject[] gameObjects;
    2.  
    3. void Reset () {
    4.     gameObjects = (GameObject[]) FindObjectsOfType(typeof(GameObject));
    5. }
    Read this for an explanation:
    http://forum.unity3d.com/threads/40114-C-List-vs.-JavaScript-Array?p=259592&viewfull=1#post259592

    Personally, I'd do
    Code (csharp):
    1. gameObjects = this.FindObjectsOfType<GameObject>();
    2.  
    using extension methods:

    Code (csharp):
    1. public static T FindObjectOfType <T> (this Object unityObject) where T : Object {
    2.     return Object.FindObjectOfType(typeof(T)) as T;
    3. }
    4. public static T[] FindObjectsOfType <T> (this Object unityObject) where T : Object {
    5.     return Object.FindObjectsOfType(typeof(T)) as T[];
    6. }
    because I hate having to deal with that extra clutter. I wish they'd put it in the API, so this. wasn't necessary, though.
     
    Last edited: May 14, 2011
  7. andorov

    andorov

    Joined:
    Feb 10, 2011
    Posts:
    1,061
    Hmm. I guess unity does initialize the right type of array.

    I actually just load all the resources at once, separate them out into a dictionaries by type, and then make a generalized function

    Fetch<Type>(name)

    and use that throughout my code.
     
  8. Stephan-B

    Stephan-B

    Unity Technologies

    Joined:
    Feb 23, 2011
    Posts:
    2,269
    Sorry to dig up this old post but this doesn't work correct?

    Since we are trying to cast Object[] to a Texture2D[] ... casting to a lower level than what the function (Resources.loadall) returns doesn't work... ?
     
  9. JohnnyA

    JohnnyA

    Joined:
    Apr 9, 2010
    Posts:
    5,039
    Are you asking why or how?

    How:

    Code (csharp):
    1.  
    2. Texture2D[] textures = System.Array.ConvertAll(Resources.LoadAll("folder", typeof(Texture2D)),o=>(Texture2D)o);
    3.  
    Slower but I <3 linq :)

    Code (csharp):
    1.  
    2. using System.Linq;
    3. Texture2D[] textures = (Resources.LoadAll("folder", typeof(Texture2D))).Cast<Texture2D>().ToArray();
    4.  
    Why:

    Try googling 'array contravariance'
     
  10. Stephan-B

    Stephan-B

    Unity Technologies

    Joined:
    Feb 23, 2011
    Posts:
    2,269
    Getting confirmation since this doesn't work for me but I keep seeing examples of what Jessy posted and keep wondering if I am not missing something :)
     
  11. JohnnyA

    JohnnyA

    Joined:
    Apr 9, 2010
    Posts:
    5,039
    FindObjectsOfType is annotated with TypeInferenceRule an internal Unity attribute which looks to enable the casting based on rules defined in the attribute (in this case the type of the first argument).
     
    Last edited: Aug 29, 2012
  12. Fattie

    Fattie

    Joined:
    Jul 5, 2012
    Posts:
    475
  13. KelsoMRK

    KelsoMRK

    Joined:
    Jul 18, 2010
    Posts:
    5,539
  14. EDartBlade

    EDartBlade

    Joined:
    Oct 27, 2019
    Posts:
    4
    It's been 6 years, but for anyone looking for a final answer, in the 2020.3.19f1 version of unity, the following does not work:

    You have to cast them one by one in a loop like this:

    Code (CSharp):
    1. Object[] texObjs = Resources.LoadAll("Path/To/Textures");
    2. Texture2D[] textures = new Texture2D[texObjs.Length];
    3. for (int i = 0; i < texObjs.Length; i++)
    4.     textures[i] = (Texture2D) texObjs[i];
    or if you don't want to initialize a new array during runtime or just want your code to be more readable you can use a List:


    Code (CSharp):
    1. // Don't forget to include "using System.Collections.Generic;" at the top
    2. List<Texture2D> textures = new List<Texture2D>();
    3. Object[] texObjs = Resources.LoadAll("Path/To/Textures");
    4. foreach (Object obj in texObjs)
    5.     textures.Add((Texture2D) obj);
    Btw I have not tried out casting using "(Texture2D) obj" so if that doesn't work try "obj as Texture2D" instead
     
  15. KelsoMRK

    KelsoMRK

    Joined:
    Jul 18, 2010
    Posts:
    5,539
    Does the example in the doc not work? Because it certainly seems simpler than all that.
    Code (csharp):
    1.  
    2. // Loads all assets in the "Resources/Textures" folder
    3. // using Linq.
    4. using UnityEngine;
    5. using System.Linq;
    6.  
    7. public class ExampleClass : MonoBehaviour
    8. {
    9.    void Start()
    10.    {
    11.        var textures = Resources.LoadAll("Textures", typeof(Texture2D)).Cast<Texture2D>().ToArray();
    12.        foreach (var t in textures)
    13.            Debug.Log(t.name);
    14.    }
    15. }
    16.  
    https://docs.unity3d.com/ScriptReference/Resources.LoadAll.html
     
    MelvMay likes this.
  16. Bunny83

    Bunny83

    Joined:
    Oct 18, 2010
    Posts:
    3,524
    Uhm, have you actually read the last two posts above yours? There is now a generic version of LoadAll. It actually returns an array of the type you pass in.
    Code (CSharp):
    1. Texture2D[] texs = Resources.LoadAll<Texture2D>("Path/To/Textures");
    Also in my UA answer that Fattie linked above I did suggest either manually casting each element manually, or use linq to do it. That was 8 years ago. I don't remember when the generic version got added, but judging by KelsoMRK's post it's there for at least 6 years.

    Since covariance and contravariance was mentioned earlier in this thread, I want to make something clear here. Usually arrays of different types can not be casted into each other, even when one of the element types is derived from the element type of the other. However C# does allow only covariance for arrays. This is essentially a special case which can lead to runtime errors when not handled correctly. Note that contravariance is not supported at all for arrays. So you can not create an
    Object[]
    and cast it to a
    Texture2D[]
    . That's just not allowed. The issue here is that when putting elements into an array a type check is performed by the CLR. So in the covariance case (treating a Texture2D array as an Object array), the CLR will check the object type against the actual element type and throw a runtime exception if you try to put anything other than a Texture2D into that object array.

    However for the contravariance case, the actual Object array could already contain non Texture2D objects. So casting it to a Texture2D array is not allowed as this can not be ensured. The CLR would have to iterate through all elements in order to allow such a conversion. Even if such a check was performed, you could use the object array reference to exchange an element of the array with a non Texture2D element and you're back at the start. The issue is that reading an array element does not perform a type check. That would be expensive in all usual cases. So if you have a Texture2D array, you can be sure it only contains Texture2D elements. That's why contravariance is not supported for arrays.

    Even covariance is an issue, but at least if misused the CLR can detect misuse and throw an exception.

    Code (CSharp):
    1. Texture2D[] texArr = new Texture2D[5];
    2. Object[] objArr = texArr; // array covariance allows this
    3. objArr[0] = new Mesh(); // this does compile but it would throw an exception at runtime
    4.  
    The array covariance support is a very special case and only native .NET arrays support this. This does not work with generic Lists. It's an explicit language feature that was added very early on due to some design requirements. So this is a very special case and not directly related to the actual feature of covariance / contravariance that the language supports.

    Co and contra variance are usually one-way features and you can only have one or the other for a certain type. It's actually about generic arguments.

    Covariance
    is about generic type variance that flows out the class / type. For it to work, the generic argument has to be decorated with the out keyword. Such generic type parameters can only be used in the class for outflowing data. So only return types, out parameters or readonly properties can use that type. This ensures there's never a case where data of that type has to flow into the class. Covariance allows you to treat a generic class with a certain type argument as the same generic class with a base type of that argument. Here's a more contrete example

    Code (CSharp):
    1. public interface IGetSomething<out T>
    2. {
    3.     T Get();
    4. }
    5.  
    6. public class MyStringReturningClass : IGetSomething<string>
    7. {
    8.     public string Get()
    9.     {
    10.         return "Hello world";
    11.     }
    12. }
    13.  
    14. IGetSomething<object> getAnObject = new MyStringReturningClass();
    15.  
    16. object o = getAnObject.Get();
    17.  
    As you can see, the interface we defined here has a generic type argument, restricted as out parameter. We use it in the interface as a return type. So data flows out of the class. We can successfully cast our "MyStringReturningClass" to an
    IGetSomething<object>
    since the type "object" is a base class of "string". So we get less specific so the cast is possible. However it would not be possible the other way round. So having a class implementing
    IGetSomething<object>
    and then try to cast an instance of such a class to a
    IGetSomething<string>
    is not allowed, since the actual class could return any kind of object, not just a string. However the casted reference would need to return a string. So that's not possible.

    Contravariance
    on the other side is about data flowing into the type. Generic arguments would need to be decorated with the in keyword to restrict the type usage to only be used for parameters of methods. Here the whole dataflow is reversed.

    Code (CSharp):
    1. public interface IDoSomething<in T>
    2. {
    3.     void Do(T data);
    4. }
    5.  
    6. public class MyObjectReceivingClass : IDoSomething<object>
    7. {
    8.     public void Do(object data)
    9.     {
    10.         // do something with data;
    11.     }
    12. }
    13.  
    14. IDoSomething<string> someActionWithString = new MyObjectReceivingClass();
    15.  
    16. someActionWithString.Do("Hello World");
    17.  
    As you can see, here the whole argument goes the other way round. Here the actual implementation has to be the more generic version, accepting an argument of type object, and we can cast our instance to a more restrictive interface type that only allows strings. That's because the data flows into the class. So restricting the incoming type to a more concrete type does not impose any problem since the actual class expects an object and string is an object.

    Here again, the other way round would not work. You can not have a class that expects a string argument and cast it to an interface that expects an object.

    Conclusion
    Covariance and contravariance only ever work in seperation from one another. You can not have a generic type argument that allows both in and out (which is the default if nothing is specified) and have co or contra variance working. The best example is the generic List class. It has methods that accepts elements (Add, Insert, ...) and it has methods that return elements (this[]). So if you would allow to cast a
    List<string>
    to an
    IList<object>
    for example, we could use
    Add(new object())
    on the IList and it would violate the contravariance restrictions. Likewise if you reverse the case, having a
    List<object>
    and cast it to a
    IList<string>
    would violate the covariance restrictions since an
    IList<string>
    should only return strings which can not be ensured.

    That's why co / contra variance is only supported in one-way scenarios. Of course you could implement two additional interfaces, one that only provides the reading aspects and one only providing the writing aspects of IList and have them be co / contra variance compatible. Though you can not make the
    System.Collections.Generic.List
    class implement those. Though you can do that with your own derived class(es).
     
  17. Bunny83

    Bunny83

    Joined:
    Oct 18, 2010
    Posts:
    3,524
    Well, it only seems like it's simpler :) The linq extension method
    Cast<Tector2D>
    would actually do the same and iterating through all elements and casting each element to the target type. The ToArray extension method would then accumulate all elements in an internal buffer / list and construct a new array our of the elements. So using linq certainly looks shorter, but in most cases is more expensive and slower compared to other solutions. As it was already mentioned, we do have a generic version of LoadAll which should be used anyways and does not have this issue.
     
  18. KelsoMRK

    KelsoMRK

    Joined:
    Jul 18, 2010
    Posts:
    5,539
    Fair - but when loading something off disk like this I think a couple of extension methods are the least of your worries when it comes to performance.

    Even though I was the one who said it, I couldn't remember if there actually was a generic overload for this and the docs don't actually mention it. :)

    With all that said, even the generic overload is basically doing the same thing so at the end of the day I'd argue it's more about readability than anything.
    Code (csharp):
    1.  
    2. internal static T[] ConvertObjects<T>(Object[] rawObjects) where T : Object
    3. {
    4.   if (rawObjects == null) return null;
    5.   T[] typedObjects = new T[rawObjects.Length];
    6.   for (int i = 0; i < typedObjects.Length; i++)
    7.     typedObjects[i] = (T)rawObjects[i];
    8.   return typedObjects;
    9. }
    10.  
    Code (csharp):
    1.  
    2. public static T[] LoadAll<T>(string path) where T : Object
    3. {
    4.   return ConvertObjects<T>(LoadAll(path, typeof(T)));
    5. }
    6.  
    https://github.com/Unity-Technologi...untime/Export/Resources/Resources.bindings.cs
     
  19. Bunny83

    Bunny83

    Joined:
    Oct 18, 2010
    Posts:
    3,524
    Uhm, it does, just scroll down a bit ^^.

    Well, yes, the outcome is the same, but the overhead is not, especially with a large number of elements. You're right that the generic version also does the manual implace conversion into a new array, however the linq solution allocates more memory. That's because the ToArray extension method can not determine how many elements are in the IEnumerable. So it uses a Buffer internally that works similar to a List. So it starts with a small array and as you add elements, the array is replaced by a larger one and the old data is copied over. Sure, for a small number of elements it wouldn't really matter. However the generic version is already build-in, is cleaner and shorter to write and you can't really get a better performance unless you would directly work with the IEnumerable so you can avoid the extra array allocation.