What does the statement mean?
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.
“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:
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:
Well dog my cats, we just made a cat bark. “out” cannot be legal.
What about “in”?
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?
? 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
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.
UPDATE: C# 7 has added
inas a formal parameter declaration, which means that we now have bothinandoutmeaning two things; this is going to create some confusion. Let me clear that up:in,outandrefon a formal parameter declaration in a parameter list means “this parameter is an alias to a variable supplied by the caller”.refmeans “the callee may read or write the aliased variable, and it must be known to be assigned before the call.outmeans “the callee must write the aliased variable via the alias before it returns normally”. It also means that the callee must not read the aliased variable via the alias before it writes it, because the variable might not be definitely assigned.inmeans “the callee may read the aliased variable but does not write to it via the alias”. The purpose ofinis to solve a rare performance problem, whereby a large struct must be passed “by value” but it is expensive to do so. As an implementation detail,inparameters are typically passed via a pointer-sized value, which is faster than copying by value, but slower on the dereference.in,outandrefare all the same thing; the rules about who reads and writes what variables at what times, the CLR does not know or care.refalso apply toinandoutparameters.In contrast,
inandouton type parameter declarations mean “this type parameter must not be used in a covariant manner” and “this type parameter must not be used in a contravariant manner”, respectively.As noted above, we chose
inandoutfor those modifiers because if we seeIFoo<in T, out U>thenTis used in “input” positions andUis used in “output” positions. Though that is not strictly true, it is true enough in the 99.9% use case that it is a helpful mnemonic.It is unfortunate that
interface IFoo<in T, out U> { void Foo(in T t, out U u); }is illegal because it looks like it ought to work. It cannot work because from the CLR verifier’s perspective, those are bothrefparameters and therefore read-write.This is just one of those weird, unintended situations where two features that logically ought to work together do not work well together for implementation detail reasons.