8

So I have a delegate defined as:

public delegate void MyDelegate<T>(T myParameter);

Resharper suggests that I should make T contravariant as follows:

public delegate void MyDelegate<in T>(T myParameter);

Now, I have a hard time understanding what this is good for? I know it prevents me from making T a return type, but other than that, what useful constraints do I get by making T contravariant? That is, when it's time to use the delegate with an instance, what instances can I create with

public delegate void MyDelegate<T>(T myParameter);

that I cannot create with

public delegate void MyDelegate<in T>(T myParameter);
AxiomaticNexus
  • 6,190
  • 3
  • 41
  • 61

3 Answers3

6

Here's an instance where if you remove the contravariance in marker it won't compile:

delegate void Callback<in T>(T t);

public Form1()
{
    InitializeComponent();
    Callback<Control> showText = control => MessageBox.Show(control.Text);
    var button = new Button();
    AddButtonClickCallback(button, showText);
    var label = new Label();
    AddLabelClickCallback(label, showText);
}

static void AddButtonClickCallback(Button button, Callback<Button> callback)
{
    button.Click += delegate { callback(button); };
}

static void AddLabelClickCallback(Label label, Callback<Label> callback)
{
    label.Click += delegate { callback(label); };
}

Somewhat contrived, sure, but should at least give you an idea of the kind of things you can't do without it.

Especially think in terms of if AddLabelClickCallback and AddButtonClickCallback were library functions and Callback was a library delegate. If it was defined without contravariance, you'd have to define different delegates showButtonText and showLabelText even though you just want them to do the same thing.

Dax Fohl
  • 10,654
  • 6
  • 46
  • 90
  • I understand better what it does. But I still do not understand why do I have to use the `in` marker. A `Button` and a `Label` both extend `Control`. So your code will never cause incompatibility errors (with or without the `in` marker). Why isn't the `in` marker simply always implied? – AxiomaticNexus Sep 12 '13 at 20:28
  • @YasmaniLlanes Because if the delegate returns a type of that parameter, or a type based in part on it, then the delegate is no longer logically contravariant. It's also possible that someone specifically wants to prohibit this ability, which they could do by intentionally making it not contravariant. – Servy Sep 12 '13 at 20:32
  • 2
    @YasmaniLlanes Excellent question. While your suggestion makes sense in theory, Eric Lippert provides several pragmatic reasons for the necessity of `in` and `out` markers in his blog here: http://blogs.msdn.com/b/ericlippert/archive/2007/10/29/covariance-and-contravariance-in-c-part-seven-why-do-we-need-a-syntax-at-all.aspx – Dax Fohl Sep 12 '13 at 20:35
  • @Servy To your first reason: The moment I add that return type to the delegate, is the moment the type becomes not logically contravariant. I should not have to remove the `in` keyword; it's already implied when I added that type to the return statement. The same when I DO NOT have the type in the return statement; it should be implied that the type is contravariant, I should not have to be adding the `in` keyword. To your second reason: I can not think of a case in which someone might want to prohibit that. – AxiomaticNexus Sep 12 '13 at 20:40
  • 1
    @YasmaniLlanes If a type isn't properly built for inheritance that may be an option. If a type involved is a value type it may be important to prevent boxing. – Servy Sep 12 '13 at 20:43
  • @Servy I'm not sure what it means to have a type "not properly built for inheritance". Also, I guess I don't know what "boxing" means either. I guess I need to look into those things. – AxiomaticNexus Sep 12 '13 at 20:55
  • @DaxFohl I read through the article and I understand the compile complexity of implementing implied variance. That clears things a bit, but I guess I still need to get my head around the process the compiler would need to go through to make it work. – AxiomaticNexus Sep 12 '13 at 20:58
  • @YasmaniLlanes I'm pretty sure languages like Scala and Haskell do have implied variance, but I'm also *positive* C# is a *way* faster compiler. How much implied variance influences that I'm not sure. I really really wanted to start using Scala as my go-to language, but compile times just killed it for me. The C# team, to me, does a good job at balancing elegance without losing sight of pragmatism. – Dax Fohl Sep 12 '13 at 21:13
4

The in keyword on generic allows for implicit conversion to occur. Basically you can assign less derived delegate types to your delegate... Not always useful, read here for more information.

http://msdn.microsoft.com/en-us/library/dd469484.aspx

Example, from MSDN article:

// Contravariant delegate. 
public delegate void DContravariant<in A>(A argument);

// Methods that match the delegate signature. 
public static void SampleControl(Control control)
{ }
public static void SampleButton(Button button)
{ }

public void Test()
{
    // Instantiating the delegates with the methods.
    DContravariant<Control> dControl = SampleControl;
    DContravariant<Button> dButton = SampleButton;

    // You can assign dControl to dButton 
    // because the DContravariant delegate is contravariant.
    dButton = dControl;

    // Invoke the delegate.
    dButton(new Button()); 
}

In that example, the Control will be implicitly converted to Button type, though an exception could occur if the implicit conversion to Button type was not defined in the Button class to allow Control to become Button.

More on implicit conversion definitions: http://msdn.microsoft.com/en-us/library/z5z9kes2.aspx

Haney
  • 32,775
  • 8
  • 59
  • 68
  • 1
    That's perfect. I can finally see where the constraint is. However, another question poses now. I should always be able to do `dButton = dControl` regardless of whether I make the delegate contravariant or not, because a `Button` will always be a `Control`. Why would I ever constrain myself from doing `dButton = dControl` if that will never cause an error? Shouldn't `` always be implied? – AxiomaticNexus Sep 12 '13 at 20:12
  • You're discussing covariance... A is B and so A can be used in place of B... This is contravariance... A is B and we're using B in place of A. – Haney Sep 13 '13 at 21:47
1

This has been asked many times on stackoverflow: Covariance and contravariance in programming languages

I suggest you go through: http://blogs.msdn.com/b/ericlippert/archive/2007/10/22/covariance-and-contravariance-in-c-part-four-real-delegate-variance.aspx

Covariance and Contravariance in C#, Part One
Covariance and Contravariance in C#, Part Two: Array Covariance
Covariance and Contravariance in C#, Part Three: Method Group Conversion Variance
Covariance and Contravariance in C#, Part Four: Real Delegate Variance
Covariance and Contravariance In C#, Part Five: Higher Order Functions Hurt My Brain
Covariance and Contravariance in C#, Part Six: Interface Variance
Covariance and Contravariance in C# Part Seven: Why Do We Need A Syntax At All?
Covariance and Contravariance in C#, Part Eight: Syntax Options
Covariance and Contravariance in C#, Part Nine: Breaking Changes
Covariance and Contravariance in C#, Part Ten: Dealing With Ambiguity
Community
  • 1
  • 1
user007
  • 1,122
  • 1
  • 10
  • 30