HelterSkelter HelterSkelter - 3 months ago 13
C# Question

Unable to cast object of type System.Delegate[] to type System.Action[T][]

I'm trying to return an object of type

Action<T>[]
from some method by casting the returned value of
Delegate.GetInvocationList
, which is of type
Delegate[]
. An exception is thrown when trying to do that.

The following example demonstrate what I'm trying to achieve in some larger piece of code I have:

using System;

class Fish
{ }

class FishDishes
{
public static void DishA(Fish f) { Console.WriteLine("Dish A"); }
public static void DishB(Fish f) { Console.WriteLine("Dish B"); }
}

class FishSchef
{
protected Action<Fish> cookFishDish = null;

public void ShowOff()
{
System.Console.WriteLine("cooking every fish dish I know of!");
cookFishDish?.Invoke(new Fish());
}

public void LearnToCookNewDish(Action<Fish> new_dish)
{
cookFishDish += new_dish;
}
}

class SeniorFishSchef : FishSchef
{
// Mentor a Junior Schef by sending her an array of every dish Senior Schef knows of
public Action<Fish>[] MentorJuniorSchef()
{
return (Action<Fish>[]) cookFishDish.GetInvocationList();
}
}

class JuniorFishSchef : FishSchef
{
// Get mentored by a Senior Schef by adding sending her an array of every dish Senior Schef knows of
public void GetMentored(SeniorFishSchef senior)
{
Action<Fish>[] dishes_arr = senior.MentorJuniorSchef();
foreach (Action<Fish> fishdish in dishes_arr)
cookFishDish += fishdish;
}
}

class Test
{
public static void Main()
{
SeniorFishSchef senior = new SeniorFishSchef();
senior.LearnToCookNewDish(FishDishes.DishA);
senior.LearnToCookNewDish(FishDishes.DishB);
senior.ShowOff();

JuniorFishSchef junior = new JuniorFishSchef();
junior.GetMentored(senior);
junior.ShowOff();
}
}


When running, the following exception is thrown:


Unhandled Exception: System.InvalidCastException: Unable to cast object of type
'System.Delegate[]' to type 'System.Action`1[Fish][]'.
at SeniorFishSchef.MentorJuniorSchef() [etc etc]


Please help me understand:


  1. Why such a cast is not possible?

  2. Why doesn't the compiler issue an error?

  3. What the notation of the casting type is
    System.Action`1[Fish][]
    - why does it have two subscripts(
    [][]
    )?



Note: Aside from having answers to the above questions, alternatives are welcomed, with the constraint that the delegate must be protected. One that I know of is simply returning
Delegate[]
from MentorJuniorSchef, and make
dishes_arr
of this same type.

Answer

1.Why such a cast is not possible?

Because the array type System.Delegate[] is not the same as System.Action<Fish>[], nor can one be implicitly converted to the other. Note that in C#, there is a form of type variance that would allow an implicit conversion the other direction (i.e. from Action<Fish>[] to Delegate[]), because it's guaranteed that every Action<Fish> element in an instance of Action<Fish>[] is in fact a Delegate as well. But there's no such guarantee going the other way, from Delegate to Action<Fish>, so attempting to cast a Delegate[] to Action<Fish>[] is illegal.

2.Why doesn't the compiler issue an error?

When you write an explicit cast, the compiler generally assumes you know what you're doing. It usually will only emit an error if it knows for sure that the cast will fail (one notable exception involves generics, but it would be distracting to drag that into the discussion here).

Note that, because it is legal to convert from Action<Fish>[] to Delegate[] (see above), you could in fact have a Delegate[] reference that really refers to an instance of Action<Fish>[]. So in some cases, the cast you're trying to do could succeed, and so the compiler can't emit an error for it.

Of course, in this particular case, the array really is just Delegate[] and not Action<Fish>[], thus the run-time error. But the compiler has no way to know for sure that this would be the case.

3.What the notation of the casting type is System.Action`1[Fish][] - why does it have two subscripts([][])?

The square brackets around Fish aren't an array notation, but rather a way to indicate what the type parameter of the generic type Action<T> is. The unconstructed type name is shown as Action`1, where the `1 indicates a generic type with one type parameter. The second set of square brackets do indicate an array type.

Yes, it's a bit confusing.

As for alternatives, you can of course just return a new array, where you've copied the elements from the original. An easy way to do this would be:

return cookFishDish.GetInvocationList().Cast<Action<Fish>>().ToArray();

Since you know that the individual Delegate objects must actually be instances of Action<Fish>, casting each one individually with the Enumerable.Cast<T>() method and then converting the resulting IEnumerable<Action<Fish>> object back to an array should accomplish what you want.

Comments