views:

108

answers:

2

What does the statement mean?

From here

ref and out parameters in C# and cannot be marked as variant.

1) Does it mean that the following can not be done.

public class SomeClass<R, A>: IVariant<R, A>
{
    public virtual R DoSomething( ref A args )
    {
        return null;
    }
}

2) Or does it mean I cannot have the following.

public delegate R Reader<out R, in A>(A arg, string s);

public static void AssignReadFromPeonMethodToDelegate(ref Reader<object, Peon> pReader)
{
    pReader = ReadFromPeon;
}

static object ReadFromPeon(Peon p, string propertyName)
    {
        return p.GetType().GetField(propertyName).GetValue(p);
    }

static Reader<object, Peon> pReader;

static void Main(string[] args)
    {
        AssignReadFromPeonMethodToDelegate(ref pReader);
        bCanReadWrite = (bool)pReader(peon, "CanReadWrite");

        Console.WriteLine("Press any key to quit...");
        Console.ReadKey();
    }

I tried (2) and it worked.

+1  A: 

It means you can't have the following declaration:

public delegate R MyDelegate<out R, in A>(ref A arg);

Edit: @Eric Lippert corrected me that this one is still legal:

public delegate void MyDelegate<R, in A>(A arg, out R s);

It actually makes sense, since the R generic parameter is not marked as a variant, so it doesn't violate the rule. However, this one is still illegal:

public delegate void MyDelegate<out R, in A>(A arg, out R s);
Franci Penov
I see. Thanks much. :-)
Water Cooler v2
This is incorrect. The second line is perfectly legal. You are correct that the first line is illegal.
Eric Lippert
@Eric - ah, the second line is legal because the R generic parameter is not marked as variant. is that right?
Franci Penov
Many thanks, Eric. I should've jumped at that myself if only I had the patience and proof-reading skills you do. But let me assure you, I completely follow the reasons behind the error that you've shed light on.
Water Cooler v2
+4  A: 

"out" means, roughly speaking, "only appears in output positions".

"in" means, roughly speaking, "only appears in input positions".

The real story is a bit more complicated than that, but the keywords were chosen because most of the time this is the case.

Consider a method of an interface or the method represented by a delegate:

delegate void Foo</*???*/ T>(ref T item);

Does T appear in an input position? Yes. The caller can pass a value of T in via item; the callee Foo can read that. Therefore T cannot be marked "out".

Does T appear in an output position? Yes. The callee can write a new value to item, which the caller can then read. Therefore T cannot be marked "in".

Therefore if T appears in a "ref" formal parameter, T cannot be marked as either in or out.

Let's look at some real examples of how things go wrong. Suppose this were legal:

delegate void X<out T>(ref T item);
...
X<Dog> x1 = (ref Dog d)=>{ d.Bark(); }
X<Animal> x2 = x1; // covariant;
Animal a = new Cat();
x2(ref a);

Well dog my cats, we just made a cat bark. "out" cannot be legal.

What about "in"?

delegate void X<in T>(ref T item);
...
X<Animal> x1 = (ref Animal a)=>{ a = new Cat(); }
X<Dog> x2 = x1; // contravariant;
Dog d = new Dog();
x2(ref d);

And we just put a cat in a variable that can only hold dogs. T cannot be marked "in" either.

What about an out parameter?

delegate void Foo</*???*/T>(out T item);

? Now T only appears in an output position. Should it be legal to make T marked as "out"?

Unfortunately no. "out" actually is not different than "ref" behind the scenes. The only difference between "out" and "ref" is that the compiler forbids reading from an out parameter before it is assigned by the callee, and that the compiler requires assignment before the callee returns normally. Someone who wrote an implementation of this interface in a .NET language other than C# would be able to read from the item before it was initialized, and therefore it could be used as an input. We therefore forbid marking T as "out" in this case. That's regrettable, but nothing we can do about it; we have to obey the type safety rules of the CLR.

Furthermore, the rule of "out" parameters is that they cannot be used for input before they are written to. There is no rule that they cannot be used for input after they are written to. Suppose we allowed

delegate void X<out T>(out T item);
class C
{
    Animal a;
    void M()
    {
        X<Dog> x1 = (out Dog d)=> { d = null; N(); if (d != null) d.Bark(); };
        x<Animal> x2 = x1; // covariant
        x2(out this.a);
    }
    void N() { if (this.a == null) this.a = new Cat(); }
}

Once more we have made a cat bark. We cannot allow T to be "out".

It is very foolish to use out parameters for input in this way, but legal.

Eric Lippert
Hi Eric, I am going to have to get some sleep before I can assure you of a devoted following of your discussion. For now, thanks a million. Your passion for your work is evidently insurmountable, unsurpassable (if there is a word as such).While I do appreciate the reasons behind the improbabilities of having a ref/out type as a variant parameter or return type, I'll re-read your answer when I'm well rested to do some justice to the effort you've put in.Thank you many times over again.
Water Cooler v2