Почему параметры универсального типа в C # нарушают ковариацию? - PullRequest
12 голосов
/ 18 января 2012

Мне неясно, почему следующий фрагмент кода не ковариантен?

  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?
    }

Ошибка 1 Недопустимая дисперсия: параметр типа 'T' должен быть инвариантным допустимо для 'IResourceColl.TryGetValue (строка, вне T)'. «Т» ковариантны.

Мой интерфейс использует параметр шаблона только в выходных позициях. Я мог бы легко изменить этот код на что-то вроде

  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
    }

но я пытаюсь понять, действительно ли мой оригинальный код нарушает ковариацию или это ограничение ковариации компилятора или .NET.

Ответы [ 4 ]

13 голосов
/ 18 января 2012

Проблема действительно здесь:

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

Вы пометили obj как параметр out, который по-прежнему означает, что вы передаете obj, поэтому он не может быть ковариантным, поскольку вы оба передаете также экземпляр типа T как вернуть его.

Edit:

Эрик Липперт говорит, что это лучше, чем кто-либо другой, к кому я обращаюсь его ответ на «параметры ref и out в C # и не может быть помечен как вариант» и цитирую его в отношении out параметров:

Должно ли быть законным сделать T помеченным как "out"? К сожалению нет. "из" на самом деле не отличается от "реф" за кадром. Единственный Разница между "out" и "ref" в том, что компилятор запрещает чтение из параметра out, прежде чем он будет назначен вызываемым пользователем, и что компилятор требует присваивания перед возвращением вызываемого обычно. Кто-то, кто написал реализацию этого интерфейса в Язык .NET, отличный от C #, сможет читать из элемента раньше он был инициализирован, и, следовательно, его можно использовать в качестве входных данных. Мы поэтому в этом случае запрещается пометить T как «out» . Это прискорбно, но мы ничего не можем с этим поделать; мы должны соблюдать правила безопасности типа CLR.

4 голосов
/ 10 октября 2014

Вот возможный обходной путь с использованием метода расширения. Не обязательно удобно с точки зрения разработчика, но пользователь должен быть счастлив:

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);
        }
    }
}
1 голос
/ 18 января 2012

Изучите этот маленький пример, и вы поймете, почему он не разрешен:

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 означает, что s должно быть обязательно назначено до того, как выполнение покинет метод, и наоборот, вы не можете использовать s, пока оно не будет определенно назначено в теле метода. Похоже, это совместимо с правилами ковариация . Но ничто не мешает вам присвоить s на сайте вызова перед вызовом метода. Это значение передается методу, что означает, что даже если оно не пригодно для использования , вы фактически передаете параметр определенного типа методу, который идет вразрез с правилами ковариация , которые утверждают, что универсальный тип может использоваться только в качестве возвращаемого типа метода.

1 голос
/ 18 января 2012

Это нарушает ковариацию, потому что значение, предоставляемое для выходных параметров, должно иметь точно того же типа, что и объявление выходного параметра.Например, если предположить, что T является строкой, ковариация будет означать, что все будет в порядке

var someIResourceColl = new someIResourceCollClass<String>();
Object k;
someIResourceColl.TryGetValue("Foo", out k); // This will break because k is an Object, not a String
...