0

Why do I get an InvalidCastException with the following code?

public interface ICommandContext<out TAssociatedObject> : ICommandContext<object, TAssociatedObject> { }

public interface ICommandContext<TParameter, out TAssociatedObject> : ICommandContext
{
    new TParameter Parameter { get; }
    new TAssociatedObject AssociatedObject { get; }
}

public interface ICommandContext
{
    object Parameter { get; }
    object AssociatedObject { get; }
    IMenuItem Menu { get; }
}

public class CommandHandlerContext<TParameter, TAssociatedObject> :
    ICommandContext<TParameter, TAssociatedObject>
{
    public CommandHandlerContext() { }

    public CommandHandlerContext(TParameter parameter, TAssociatedObject associatedObject, IMenuItem menu)
    {
        Parameter = parameter;
        AssociatedObject = associatedObject;
        Menu = menu;
    }

    public TParameter Parameter { get; set; }
    public TAssociatedObject AssociatedObject { get; set; }
    public IMenuItem Menu { get; set; }

    object ICommandContext.Parameter => Parameter;
    object ICommandContext.AssociatedObject => AssociatedObject;
}

static class Testing
{
    public static void Test()
    {
        var context = new CommandHandlerContext<object, IShell>();
        var casted = (ICommandContext<object, IShell>)context;
        var casted2 = (ICommandContext<IShell>)casted;
    }
}

The line var casted2 = (ICommandContext<IShell>)casted; throws an InvalidCastException and it's bugging me why is that.

I want to be able to define two generics arguments, but also only define the second one and have the first default to object.

Jyosua
  • 648
  • 1
  • 6
  • 18
JobaDiniz
  • 862
  • 1
  • 14
  • 32
  • 2
    The cast fails because `context` is not a `ICommandContext`, it is a base class of that. Related: https://stackoverflow.com/questions/1099440/how-do-you-provide-a-default-type-for-generics – Rotem Jul 25 '19 at 22:00
  • 1
    To use a simple example: A dog is a animal, but not every animal is a dog. `CommandHandlerContext` is the dog. `ICommandContext` is the animal. Also please make examples that are less cluttered. You could literally have made this with dog and animal. Rather then using interfaces and classes that have 2 generic parameters. – Christopher Jul 25 '19 at 22:04
  • 2
    You instantiate a new object that implements `CommandHandlerContext`, and then try to cast it to a `ICommandContext`. I'm genuinely curious why you think it *should* work? – Rufus L Jul 25 '19 at 22:53

3 Answers3

2

If we simplify the names a little:

A = ICommandContext<out TAssociatedObject>
B = ICommandContext<TParameter, out TAssociatedObject>
C = ICommandContext

Then the interfaces look like:

interface A : B { }
interface B : C { }
interface C { }

And the code looks like:

var x = new B();
var y = (C) x;
var z = (A) x;  // <-- throws an InvalidCastException

Note that in our interface definitions, B does not implement A. Therefore trying to cast x (which is a B) to an A, will throw an InvalidCastException.

Rufus L
  • 36,127
  • 5
  • 30
  • 43
  • `B does not implement A`, yeah I saw that and tried to `public class CommandHandlerContext : ICommandContext, ICommandContext` and that cannot be done either https://stackoverflow.com/questions/15316898/why-does-this-result-in-cs0695 – JobaDiniz Jul 26 '19 at 00:10
0

Well, the reason is pretty simple. CommandHandlerContext implements ICommandContext<TParameter, TAssociatedObject>, not ICommandContext<out TAssociatedObject>.

I'm not sure the reason you need this, but wouldn't it make more sense just to cast to ICommandContext instead of ICommandContext<IShell>?

Jyosua
  • 648
  • 1
  • 6
  • 18
0

Just to keep the classes from being piontlessly complex, I make the simple Animal/Dog examples.

class Animal{

}

class Dog : Animal {

}

var ItLives = new Animal();
Dog someDog = (Dog)ItLives;

That this does not work is clear. No amount of mad science will allow this cast to work. Note that it is also fundamentally different from:

var someDog = new Dog();
//The cast to animal is implicit
Animal someAnimal = someDog;
Dog = (Dog)someAnimal;

Just because for a while you Treat a dog as "just a Animal", does not mean it stops being a dog.

Also note that with Generics, casting is usually not as relevant. What usualy matters more is variance and covariance. It happens that you cast a List[T] to IList[T], but usually those are implicit and they only change very little about the use.

Meanwhile it is more likely that you will variance (is that term?) a List[Dog] to a List[Animal] or vice versa. One of the directions is variance, one covariance. I just can never keep straight wich is wich.

Christopher
  • 9,634
  • 2
  • 17
  • 31