0

I'm trying to figure out why this doesn't work:

public void DefaultAction( object obj = null ){}

public void Start()
{
    SomeReferenceType obj;
    DefaultAction( obj ); //works

    int i;
    string s;
    DefaultAction( i ); //works
    DefaultAction( s ); //works
}

//however...

public event Action OnNullAction = DefaultAction; //works
public event Action<SomeReferenceType> OnObjectAction = DefaultAction; //works
public event Action<int> OnIntAction = DefaultAction; //doesn't work!!

Trying to bind a void(object) to an Action<ValueType> throws a parameter mismatch error, even though you can call the function directly with an int/string/bool what have you. Is there some mysterious boxing/unboxing happening? And regardless, is it possible to create a delegate that can respond to any Action<T>?

Sam
  • 7,252
  • 16
  • 46
  • 65
  • It's not mysterious that an `int` would have to be boxed to pass to an `Action`. – Gabe Jun 21 '14 at 13:47
  • 2
    This is a limitation of generic contravariance, see http://stackoverflow.com/questions/12454794/why-covariance-and-contravariance-do-not-support-value-type – Lee Jun 21 '14 at 13:53
  • `public event Action OnNullAction = DefaultAction` this can't work. – Hamlet Hakobyan Jun 21 '14 at 14:02
  • I've closed this as a duplicate, but see my answer below for more details, in case the concepts of covariance and contravariance are unclear to you. – Eric Lippert Jun 21 '14 at 16:31

2 Answers2

7

You have discovered that delegate contravariance requires reference types.

That's pretty highfalutin, I know.

First off, let me clearly state what covariance and contravariance are. Suppose you have a relationship between types: "A value of type Giraffe can be assigned to a variable of type Animal". Let's notate that as

Animal <-- Giraffe

A generic type C<T> is said to be covariant in T if replacing every type with C<that type> preserves the direction of the arrow.

IEnumerable<Animal> <-- IEnumerable<Giraffe>

Since C# 4.0, when I added this feature to the language, ou can use a sequence of giraffes anywhere you need a sequence of animals.

A generic type C<T> is said to be contravariant in T if the replacement reverses the direction of the arrow:

Action<Animal> --> Action<Giraffe>

If you need an action that requires that you give it a Giraffe and you have an action that can take any Animal, then you're all set; you need something that can take a Giraffe and an Action<Animal> can take a Giraffe. But this is not covariant. If you have an Action<Giraffe> in hand and you need an Action<Animal>, you can't use the Action<Giraffe> because *you can pass a Tiger to an Action<Animal> but not an Action<Giraffe>.

What about Func<T>? It is covariant in T. If you need a function that returns an Animal and you have a function that returns a Giraffe, you're good, because the Giraffe will be an Animal.

What about Func<A, R>? It is contravariant in A and covariant in R. It should be clear why.

So now that we know what covariance and contravariance of generic types are, what are the rules in C#? The rules are:

  • The type declarations must be annotated with in (contravariant) and out (covariant). For example, delegate R Func<in A, out R>(A a). Notice that the in is the thing that goes into the function and the out is the thing that comes out of the function; we named them in and out on purpose.

  • The compiler has to be able to prove that the annotations are safe. See the spec or my blog for details.

  • Variance is only supported on generic delegates and interfaces, not on generic structs, enums or classes.

  • The varying types have to both be reference types.

So now we come to your question. Why do they have to both be reference types? You deduced the answer: where is the boxing instruction?

Action<object> oa = (object x)=>whatever;
Action<int> ia = oa; // Suppose this works.
ia(123);

Where is the boxing instruction? Not in the body of the lambda assigned to oa -- that thing takes an object already. Not in the call to ia(123) -- that thing takes an integer. The only possible solution is that oa and ia be unequal; that this be a shorthand for

Action<object> oa = (object x)=>{whatever};
Action<int> ia = (int x)=>{ oa(x); };

But if that's what you meant to say, then just say that. People expect that a reference conversion will maintain referential identity, so C# outlaws covariant or contravariant conversions that would have to box or unbox a value.

If you have more questions on this, search my old blog (blogs.msdn.com/ericlippert) for covariance or search for the C# covariance FAQ.

Eric Lippert
  • 647,829
  • 179
  • 1,238
  • 2,067
  • Question is "Is it possible to create a delegate for *any* Action?". It's not "Why can't I cast Action to Action?". – Atomosk Jun 21 '14 at 16:37
  • @Eric thanks for the great explanation of *why* this doesn't work. Atomosk thanks for the easy workaround :) – user3762946 Jun 22 '14 at 01:25
  • 4
    @Atomosk: The question as stated is *incoherent*. I can't figure out what "is it possible to create a delegate for any `Action` even *means*. `Action` *is* a delegate, so the question is nonsensical. Rather than trying to address the nonsensical question I instead chose to address the other question stated: **is there some kind of boxing happening?** and the implied question **why is there a type mismatch?**, as those are actually questions that have answers. – Eric Lippert Jun 22 '14 at 01:40
  • Isn't `Func` covariant in `T`? – T.C. Jun 22 '14 at 05:55
  • Your answer is great. I meant duplciate question isn't actualy duplicate. I don't know how to comment "marked as duplicate by Eric Lippert", so decided to write here. – Atomosk Jun 22 '14 at 13:01
1

Just make it generic

public void DefaultAction<T>(T param) { }
Atomosk
  • 1,871
  • 2
  • 22
  • 26