Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Why do C# out generic type parameters violate covariance?

Tags:

c#

out

covariance

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.

like image 652
MerickOWA Avatar asked Jan 18 '12 16:01

MerickOWA


People also ask

Why do we write in C?

The C programming language is the recommended language for creating embedded system drivers and applications. The availability of machine-level hardware APIs, as well as the presence of C compilers, dynamic memory allocation, and deterministic resource consumption, make this language the most popular.

What is '#' in C language?

'#' is called pre-processor directive and the word after '#' is called pre-processor command. Pre-processor is a program which performs before compilation. Each pre-processing directive must be on its own line.

Why semicolon is used in C?

Semicolons are end statements in C. The Semicolon tells that the current statement has been terminated and other statements following are new statements. Usage of Semicolon in C will remove ambiguity and confusion while looking at the code.

Why do people still use C?

C exists everywhere in the modern world. A lot of applications, including Microsoft Windows, run on C. Even Python, one of the most popular languages, was built on C. Modern applications add new features implemented using high-level languages, but a lot of their existing functionalities use C.


2 Answers

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.

like image 96
BrokenGlass Avatar answered Oct 11 '22 15:10

BrokenGlass


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);
        }
    }
}
like image 45
konrad.kruczynski Avatar answered Oct 11 '22 15:10

konrad.kruczynski