13

I'm unclear as to why the following code snippet isn't covarient?

  public interface IResourceColl<out T> : IEnumerable<T> where T : IResource {

    int Count { get; }

    T this[int index] { get; }

    bool TryGetValue( string SUID, out T obj ); // Error here?
    }

Error 1 Invalid variance: The type parameter 'T' must be invariantly valid on 'IResourceColl.TryGetValue(string, out T)'. 'T' is covariant.

My interface only uses the template parameter in output positions. I could easily refactor this code to something like

  public interface IResourceColl<out T> : IEnumerable<T> where T : class, IResource {

    int Count { get; }

    T this[int index] { get; }

    T TryGetValue( string SUID ); // return null if not found
    }

but I'm trying to understand if my original code actually violates covariance or if this is a compiler or .NET limitation of covariance.

casperOne
  • 73,706
  • 19
  • 184
  • 253
MerickOWA
  • 7,453
  • 1
  • 35
  • 56
  • possible duplicate of [C# : Why doesn't 'ref' and 'out' support polymorphism?](http://stackoverflow.com/questions/1207144/c-sharp-why-doesnt-ref-and-out-support-polymorphism) – Jon Jan 18 '12 at 16:43
  • The important thing to note here is that [`out`](http://msdn.microsoft.com/en-us/library/ee332485.aspx) (the parameter modifier) is totally unrelated to [`out`](http://msdn.microsoft.com/en-us/library/dd469487.aspx) (used on a generic type parameter). – Jon Jan 18 '12 at 16:44
  • @Jon - that question applies to C# 3.0 and before. The syntax described here is C# 4.0 – Oded Jan 18 '12 at 16:44
  • It's worth pointing out that C# generics are not templates. If you are coming from a C++ background, C++ templates and C# generics do not behave the same way. – Anthony Pegram Jan 18 '12 at 16:45
  • I highly recommend reading Eric Lippert's excellent series describing variance in C#. [Here is the first entry](http://blogs.msdn.com/b/ericlippert/archive/2007/10/16/covariance-and-contravariance-in-c-part-one.aspx) – Rob H Jan 18 '12 at 16:44
  • @Oded: The generic variance syntax is 4.0, but it has nothing to do with the issue here; the `out` parameter (which *is* the issue) is not. – Jon Jan 18 '12 at 16:52
  • @AnthonyPegram you're right, sorry. I didn't mean to confuse the question with incorrect terminology – MerickOWA Jan 18 '12 at 16:56
  • @Jon why is it unrelated? Removing 'out' on the T generic parameter is another way to "fix" the error so the two are obviously related to this error. – MerickOWA Jan 18 '12 at 16:58
  • @MerickOWA: My point is that the problem here is trying to have a variant `out` parameter, which cannot work and is not triggered *only* by generic parameter variance (see linked question). In retrospect, "unrelated" was probably not the correct word. – Jon Jan 18 '12 at 17:08

4 Answers4

14

The problem is indeed here:

bool TryGetValue( string SUID, out T obj ); // Error here?

You marked obj as out parameter, that still means though that you are passing in obj so it cannot be covariant, since you both pass in an instance of type T as well as return it.

Edit:

Eric Lippert says it better than anyone I refer to his answer to "ref and out parameters in C# and cannot be marked as variant" and quote him in regards to out parameters:

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.

Community
  • 1
  • 1
BrokenGlass
  • 158,293
  • 28
  • 286
  • 335
  • I might pass in an instance of something other than a T, but C# rules require that I assign something before I can read it, so it'll never cause a problem. – MerickOWA Jan 18 '12 at 17:00
  • Ah I see now, it is possible to read the value in other languages, and eric's blog points out cases even in C# where it 'could' break. That explains why covariance is violated. – MerickOWA Jan 18 '12 at 17:25
  • The problem is that so-called `out` parameters really aren't; a real `out` parameter would cause the compiler to generate a struct type for the function's return, which would include the designated return type and all `out` parameters; the caller would then automatically copy the appropriate fields from the struct to `out` parameters, and then regard the remaining field (if any) as the return value. If `out` parameters were implemented in that way, they could indeed be covariant. – supercat Jun 28 '12 at 18:08
  • 1
    @MerickOWA You can also understand this without interfaces and generic covariance. Consider C# version 1.2. Make a method `void MyMethod(out Dog dog) { /* method body */}`. Why can't you call this method like so: `Animal a; MyMethod(out a);` The reason is the same. MyMethod might then get in an `Animal` field. Then call a method that sets the same `Animal` field to `new Cat()`. Then say `dog.Bark();`. (This is just an imitation of Jon Skeet's last example, linked in this answer.) – Jeppe Stig Nielsen Sep 06 '12 at 16:01
2

Here's the possible workaround using extension method. Not necessarily convenient from the implementor point of view, but user should be happy:

public interface IExample<out T>
{
    T TryGetByName(string name, out bool success);
}

public static class HelperClass
{
    public static bool TryGetByName<T>(this IExample<T> @this, string name, out T child)
    {
        bool success;
        child = @this.TryGetByName(name, out success);
        return success;
    }
}

public interface IAnimal { };

public interface IFish : IAnimal { };

public class XavierTheFish : IFish { };

public class Aquarium : IExample<IFish>
{
    public IFish TryGetByName(string name, out bool success)
    {
        if (name == "Xavier")
        {
            success = true;
            return new XavierTheFish();
        }
        else
        {
            success = false;
            return null;
        }
    }
}

public static class Test
{
    public static void Main()
    {
        var aquarium = new Aquarium();
        IAnimal child;
        if (aquarium.TryGetByName("Xavier", out child))
        {
            Console.WriteLine(child);
        }
    }
}
konrad.kruczynski
  • 46,413
  • 6
  • 36
  • 47
1

It violates covariance because the value provided to output parameters must be of exactly the same type as the output parameter declaration. For instance, assuming T was a string, covariance would imply that it would be ok to do

var someIResourceColl = new someIResourceCollClass<String>();
Object k;
someIResourceColl.TryGetValue("Foo", out k); // This will break because k is an Object, not a String
Chris Shain
  • 50,833
  • 6
  • 93
  • 125
1

Examine this little example and you will understand why it is not allowed:

public void Test()
{
    string s = "Hello";
    Foo(out s);
}

public void Foo(out string s) //s is passed with "Hello" even if not usable
{
    s = "Bye";
}

out means that s must be definitely assigned before execution leaves the method and conversely you can not use s until it is definitely assigned in the method body. This seems to be compatible with covariance rules. But nothing stops you from assigning s at the call site before calling the method. This value is passed to the method which means that even if it is not usable you are effectively passing in a parameter of a defined type to the method which goes against the rules of covariance which state that the generic type can only be used as the return type of a method.

InBetween
  • 32,319
  • 3
  • 50
  • 90