简体   繁体   中英

Why does the static method in the generic interface affect covariance?

I have an interface

interface IContainer<out T>
{
    T Unpack();
}

It is covariant. If I add a method that uses T as input - the covariance will be broken.

interface IContainer<out T> // compilation ERROR!!!
{
    T Unpack();
    void RepackWith(T value);
}

It is understandable because the following method

void UseContainer(IContainer<Base> container)
{
    container.RepackWith(new Base());
}

is wrong to call

IContainer<Derived> d = ...
UseConatiner(d);

RepackWith(new Base()) doesn't cover expectation of Derived instance.

But why is it the same when I add the static method into the interface?

interface IContainer<out T> // compilation ERROR!!!
{
    T Unpack();
    static IContainer<T> Pack(T value) { ... }
}

As for me, it shouldn't affect covariance because of different contexts of using static and non-static methods.

void UseContainer<T>(IContainer<T> container)
{
    // Everything is OK, the container can be 
    // IContainer<Derived> for UseContainer<Base> call. 
    // container.Unpack() returns a Derived instance 
    // that is fully compatible with Base instance expectations.
    var content  = container.Unpack();

    // Everything is OK with the following too.
    // The static call has its own context IContainer<T>, 
    // and it can't break any expectation of type.
    var newContainer = IContainer<T>.Pack(content);
    var newContent = newContainer.Unpack();
}

What is the reason for this restriction? Which code example do I miss to understand how it is incompatible with covariance?

I cant find any technical reason for this, its not in the documentation, draft specs, or anything I could find on github. however, this likely (and unsatisfyingly) comes down to, it is what it is, and it was simpler to just follow the same rules of variance, or they just haven't implemented it yet.

I personally don't use static default interface methods. However, if you really wanted to stuff things away in hard to find places. You could do something like this. Though, I am really not sure why you would

public interface IContainer
{
   public static IContainer<T> Pack<T>(T value)
   {
      return null;
   }
}

public interface IContainer<out T> : IContainer
{
   T Unpack();
}

Also, you're probably better off not inheriting from it, as it would lead you to absurdity like

IContainer<Bob>.Pack<int>(4);

The other thing you could do is just make an extension method and move on

This will be fixed in a future version of C#. In fact, for .net 5.0 this is already fixed if you specify the language version as "preview".

For example, considering your sample code:

interface IContainer<out T>
{
    T Unpack();
    static IContainer<T> Pack(T value) { return default; }
}

With <LangVersion>latest</LangVersion> in the project file (for a .net 5.0 target) this yields the following error:

error CS8904: Invalid variance: The type parameter 'T' must be contravariantly valid on 'IContainer.Pack(T)' unless language version 'preview' or greater is used. 'T' is covariant.

Note that the error message explicitly tells you that you can use "preview".

So changing the language version to <LangVersion>preview</LangVersion> allows this to compile.

It would appear that the lack of support for this was due to the fact that the C# feature wasn't completely implemented yet.

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM