1

I needed to cast a generic type deriving from UnityEngine.Object into UnityEngine.AudioClip, but I was having this error all the time:

Error Message:

error CS0030: Cannot convert type 'T[]' to 'UnityEngine.AudioClip[]'

And I was baffled, cause AudioClip derives from Object, so it shouldn't be an issue to cast to it.

So the Example Code 1 below is how my code was when I got the error. Then I solved the error by changing the code to the one in Example Code 2.

So my question is:

Why did a direct cast (ie. using parenthesis) NOT work, but casting using the as-keyword did?

I want to understand why Example Code 2 works. I've looked at the "Direct casting vs 'as' operator?"-answer, but I don't understand how that would be related to this issue. So any help or explanation as to why this works the way it does would be really appreciated.


Example Code 1: Direct Cast (ie. using Parenthesis)

private void UpdateAudioClipsOnDragAndDrop<T>(T[] draggedObjects) where T : UnityEngine.Object
{
    audioClips = (AudioClip[])draggedObjects;
}

Example Code 2: Cast using the "as"-keyword

private void UpdateAudioClipsOnDragAndDrop<T>(T[] draggedObjects) where T : UnityEngine.Object
{
    audioClips = draggedObjects as AudioClip[];
}

Here's also a Screenshot of the Error in Visual Studio

Filburt
  • 17,626
  • 12
  • 64
  • 115
  • 1
    I know this is not related to your immediate question, but why can't you just accept a `AudioClip[]` as the parameter? – Sweeper Feb 22 '20 at 16:17
  • Since your method accepts `T` where `T` is `UnityEngine.Object`, you can pass in any type that inherits from `UnityEngine.Object`. So if you were to pass in an array of [UnityEngine.Avatar](https://docs.unity3d.com/ScriptReference/Avatar.html), what would you expect to happen when `audioClips = (AudioClip[])draggedObjects` executes? – devNull Feb 22 '20 at 16:42
  • This is actually a really good question. It made me learn something new - that `as` and the cast operator differs in an aspect that I didn't know before. @devNull That doesn't explain why `as` doesn't produce a compile time error. – Sweeper Feb 22 '20 at 17:03
  • @Sweeper I can't accept `AudioClip[]` as a parameter cause I want the user to be able to make their own custom implementation of this method - if they'd like to. This method is just going to be called in a highly controlled manner, ie. whenever someone drops something into the generic _DragAndDrop_-area for the _EditorGUI_ that I've made. And since the Area is supposed to be able to accept any `Type` that the actual GUI Code Implementation wants, then this callback method needs to be generic. – Pablo Sorribes Bernhard Feb 22 '20 at 19:25
  • @devNull This is also why this won't be an issue. The caller is already defining the Type that they want to accept / be returned by the DragAndDrop-callback - thus it's up to them to actually look for that same type when they write the Callback Implementation :) – Pablo Sorribes Bernhard Feb 22 '20 at 19:27
  • You _may_ have the wrong design. Does this method actually work if you use `as`? If it does, please consider accepting the answer that best answer your question. If it doesn't, I suggest you ask another question about how to do this, providing more details about how you desire to call `UpdateAudioClipsOnDragAndDrop`. – Sweeper Feb 22 '20 at 19:31
  • @Sweeper yeah, it works with `as`. I was just curious why it actually worked, compared to the explicit cast. - I've marked @Rixment 's answer as the correct one, since it easily explained why it worked. – Pablo Sorribes Bernhard Feb 22 '20 at 19:40

2 Answers2

0

In both cases you're casting down the inheritance tree, so it cannot be done implicitly as you and compiler doesn't know what type of object is being passed as type of T.

In the first case with direct casting you're saying to compiler that it HAS to be the type of AudioClip[] and in the second that it MIGHT be - if it's not, then null will be the result.

You can give a hint to compiler as you already do with the use of where keyword and specifying the class of T, so that even the first case will be non problematic to the compiler.

Eric
  • 1,685
  • 1
  • 17
  • 32
  • Thanks! Now I actually understand what the difference is between the explicit cast `(TypeToCastTo)` and using the `as`-keyword, and why the former gives a compile error, whilst the latter works in this case :) – Pablo Sorribes Bernhard Feb 22 '20 at 19:43
0

And I was baffled, cause AudioClip derives from Object, so it shouldn't be an issue to cast to it.

Yes, casting from Object[] to AudioClip[] is ok (at least at compile time), but you are doing here is casting from T[] to AudioClip[].

The generic constraint T: Object does not mean T == Object. It means that T is a subclass of Object. So it could be the case that T == AudioClip, or T == Component or T == GameObject. Hopefully you'll agree that there is no explicit conversion between GameObject[] and AudioClip[]. If you don't agree, here's the language spec:

The explicit reference conversions are:

  • ...
  • From an array_type S with an element type SE to an array_type T with an element type TE, provided all of the following are true:
    • S and T differ only in element type. In other words, S and T have the same number of dimensions.
    • Both SE and TE are reference_types.
    • An explicit reference conversion exists from SE to TE.

Is there a explicit reference conversion from T to AudioClip then? This section does not list such a conversion, so no.

The as operator however, works like this (excerpt from the language spec again):

The as operator is used to explicitly convert a value to a given reference type or nullable type. Unlike a cast expression, the as operator never throws an exception. Instead, if the indicated conversion is not possible, the resulting value is null.

In an operation of the form E as T, E must be an expression and T must be a reference type, a type parameter known to be a reference type, or a nullable type. Furthermore, at least one of the following must be true, or otherwise a compile-time error occurs:

  • An identity, implicit nullable, implicit reference, boxing , explicit nullable, explicit reference, or unboxing conversion exists from E to T.
  • The type of E or T is an open type.
  • E is the null literal.

The important point is that as will not produce an error if the type of E is an open type. According to this section of the spec, T[] is an open type because it is an array type of a type parameter.

Why did the language designers design it like this? You'd have to ask them to know the true answer! :) My guess is that they wanted to have a stricter kind of casting and a less strict kind that doesn't crash when it fails.


In the end, though, even if you used as, this specific conversion will still fail (audioClips will be null) unless T == AudioClip.

Sweeper
  • 213,210
  • 22
  • 193
  • 313
  • Thanks for the answer. Can't say I understood everything, but as you say, the `as`-conversion works as long as the `T[]` that is sent into the method is of the type AudioClip. I described it in another comment, but basically, the way the caller calls on the actual DragAndDropArea GUI Code, they will also specify what kind of Type they want/accept being dropped in that Area. So if they wanna do something with this callback, they should already know what type they've requested in the GUI Code :) – Pablo Sorribes Bernhard Feb 22 '20 at 19:50