0

I'm trying to understand contravariance in practice. It seemed to be straightforward when I read the book, but now I seem to have stuck. I understand there's a lot of topics on contravariance and I've googled many of them, none helped me understand this particular problem Here's what Microsoft docs say https://learn.microsoft.com/en-us/dotnet/standard/generics/covariance-and-contravariance

And here's my code:

using static System.Console;

namespace CSharpTests 
{
    class Program 
    {
        delegate void Action<T> (T obj);

        static void Main(string[] args) 
        {

            Action<Device> b = DeviceAction;
            Action<Mouse> d = b; // Error cannot implicitly convert type CSharpTests.Program.Action<CSharpTests.Device> to CSharpTests.Program.Action<CSharpTests.Mouse>
            d(new Mouse());

            ReadLine();
        }

        private static void DeviceAction(Device target) {
            WriteLine(target.GetType().Name);
        }
    }
    class Device { }
    class Mouse : Device { } 
}

What is the crucial difference? My code doesn't even compile. As you can see I've got a delegate accepting generic type which, as far as I understand, allows contravariance. But on practice, I got a compile time error. I also try to do it with the "out" parameter and got the same error

using static System.Console;

namespace CSharpTests {

    class Program {

        delegate void Action<T> (out T obj);

        static void Main(string[] args) {

            Action<Device> b = DeviceAction;
            Action<Mouse> d = b; // Error cannot implicitly convert type CSharpTests.Program.Action<CSharpTests.Device> to CSharpTests.Program.Action<CSharpTests.Mouse>
            Mouse m;
            d(out m);

            ReadLine();
        }

        private static void DeviceAction(out Device target) {
            target = new Device();
            WriteLine(target.GetType().Name);
        }
    }
    class Device { }
    class Mouse : Device { } 
}
Rand Random
  • 7,300
  • 10
  • 40
  • 88
Konstantin
  • 1,150
  • 13
  • 31
  • If you follow the links to e.g. `Action` from that document, you'll see that *you have to decorate the type parameters with `in` or `out` to actually declare their variance* – Damien_The_Unbeliever May 02 '18 at 14:14
  • I tried to do it, and I wrote about that. It gave me just the same error – Konstantin May 02 '18 at 14:16
  • 2
    I think you have declared the type Action with the line `delegate void Action (T obj);` but it is not marked as being contravarient - to do that you would write `delegate void Action (T obj);` – Mike Jerred May 02 '18 at 14:17
  • 2
    `out` in `(...)` has a different meaning to `(out T value)`. – ProgrammingLlama May 02 '18 at 14:19
  • Change the signature to `delegate void Action`. `in` indicates contravariance; `out` indicates covariance. You can usually tell which one to use because `in` is for _inputs_ (e.g., parameters) and `out` is for _outputs_ (e.g., return values). – Mike Strobel May 02 '18 at 14:20
  • @Mike ahh, exactly! You've just saved my nerves :D Thanks! Unfortunately I cannot accept comment as an answer. – Konstantin May 02 '18 at 14:20

3 Answers3

3

Here you are declaring an invariant T with an out parameter:

delegate void Action<T> (out T obj);

Moving out to Action<out T> would give you a covariant T instead. What you wanted to do is this (a contravariant T with a parameter):

delegate void Action<in T> (T obj);
Camilo Terevinto
  • 31,141
  • 6
  • 88
  • 120
3

Change the signature to delegate void Action<in T>(T arg).

Declaring a type parameter as in indicates contravariance; out indicates covariance.

You can usually tell which one to use because in is for inputs (e.g., parameters) and out is for outputs (e.g., return values).

Mike Strobel
  • 25,075
  • 57
  • 69
-1

Well your creating a target = new Device(); this is not the same as what you put in...

Try like this:

[TestClass]
public class Method_Tetsts
{
    class Device { }
    class Mouse : Device { }

    [TestMethod]
    public void ActionTest()
    {

        void DeviceAction<T>( T target)
        {  
            Assert.AreEqual(target.GetType().Name, "Mouse");
        }

        Action<Device> b = DeviceAction;
        Action<Mouse> d = b; // Error cannot implicitly convert type CSharpTests.Program.Action<CSharpTests.Device> to CSharpTests.Program.Action<CSharpTests.Mouse>
        d(new Mouse());
    }
}
Walter Verhoeven
  • 3,867
  • 27
  • 36