Casting generic delegates to another type with int

2019-07-07 04:03发布

问题:

(Using .NET 4.0) Ok, so I have

private Dictionary<int, Action<IMyInterface, IMyInterface>> handler {get; set;}

public void Foo<T, U>(Action<T, U> myAction)
    where T : IMyInterface
    where U : IMyInterface
    {
        // | This line Fails
        // V
        Action<IMyInterface, IMyInterface> anotherAction = myAction;
        handler.Add(someInt, anotherAction);
    }

I'm trying to store the delegate in a generic collection, so I can pull it back out later to invoke it. How do I properly cast it?

回答1:

The generic parameters to the Action delegate are type-contravariant; they are not type covariant. As a result, you can pass in a less specific type, but not a more specific type.

So this compiles:

protected void X()
{
    Action<string> A = Foo;
}

void Foo(object s) { }

But this doesn't:

protected void X()
{
    Action<object> A = Foo;
}

void Foo(string s) { }

Since T and U : IMyInterface, your code is analogous to the first example.

The intellisense explains it rather clearly: (here's a bigger version)



回答2:

Welp... looks like me and my friend found a bit of a work around.

public void Foo<T, U>(Action<T, U> myAction)
    where T : IMyInterface
    where U : IMyInterface
    {
        Action<IMyInterface, IMyInterface> anotherAction = (x, y) => eventHandler.Invoke((TSender)x, (TObject),y);
        handler.Add(someInt, anotherAction);
    }

With a simple lambda wrap, we accomplished what we needed.



回答3:

I don't think there is a type safe way of doing what you're trying to accomplish. Using the example in your updated question:

private Dictionary<int, Action<IMyInterface, IMyInterface>> handler {get; set;}

public void Foo<T, U>(Action<T, U> myAction)
    where T : IMyInterface
    where U : IMyInterface
    {
        Action<IMyInterface, IMyInterface> anotherAction = (x, y) => myAction.Invoke((T)x, (U)y);
        handler.Add(someInt, anotherAction);
    }

Assuming IMyInterface and MyImplementation are defined as follows:

interface IMyInterface
{
    void bar();
}

class MyImplementation : IMyInterface
{
    void IMyInterface.bar()
    {
        //Snip: Do the things
    }

    void nope()
    {
        //Snip: Do other things
    }
}

class MySimplerImplementation : IMyInterface
{
    void IMyInterface.bar()
    {
        //Snip: Do things
    }
}

We could find ourselves in the following situation:

void test()
{
    //Create an action with a method that only MyImplementation implements
    Action<MyImplementation, MyImplementation> forMyImplementationOnly =
        (x, y) => x.nope();

    //Use Foo (defined in the example code above) to 'cast' this
    //action and add it to the handler dictionary
    Foo<MyImplementation, Myimplementation>(forMyImplementationOnly);

    //Retrieve the action from the handler dictionary
    Action<IMyInterface, IMyInterface> castedAction = handler[someInt];

    //Try running the action using MySimplerImplementation
    castedAction(new MySimplerImplementation(), new MySimplerImplementation());

    //This code will fail because MySimplerImplementation
    //can not be cast to MyImplementation. It does not even 
    //define the nope() method that your initial Action required
}

It's for this reason that the Action generic is contravariant (you can use less specific types, but not more specific types).