C # Неудачное ковариантное приведение - PullRequest
1 голос
/ 23 октября 2019

Я пытаюсь создать что-то вроде универсальной фабрики таблиц с рядом реализаций. Пример ниже не требует пояснений, и приведение в конце не работает, даже если оба атрибута типа помечены как «out». Я полагаю, проблема в том, что Table не является интерфейсом, и это нарушает приведение. Но это не должно быть интерфейсом и особенно не ковариантным. Есть идеи, как правильно отлить это? Определенно не хочу использовать отражение, чтобы использовать методы.

interface ITableRow { }

class Table<T> where T : ITableRow { }

interface ITableFactory<out TRow, out TTable>
    where TRow : ITableRow
    where TTable : Table<TRow>
{
    TTable CreateTable();
}

// example implementation:

class SuperRow : ITableRow { }

class SuperTableFactory : ITableFactory<SuperRow, Table<SuperRow>>
{
    public Table<SuperRow> CreateTable() { throw new NotImplementedException(); }
}

// run:

class VarianceTest
{
    public static void Test()
    {
        var factory = Activator.CreateInstance(Type.GetType("Snippets.Var.SuperTableFactory"));

        var casted = (ITableFactory<ITableRow, Table<ITableRow>>) factory; // cast fails
    }
}

1 Ответ

4 голосов
/ 23 октября 2019

Ваш SuperTableFactory претендует на производство экземпляров Table<SuperRow>. Это означает, что к какой бы таблице вы не вернулись из нее, вы можете добавить SuperRows и прочитать SuperRows.

Однако ваш ITableFactory<ITableRow, Table<ITableRow>> утверждает, что он создает Table<ITableRows> - таблицы, которые могут добавлять людиITableRows to и чтение ITableRows from.

Но ваша фактическая фабрика таблиц выдает Table<SuperRow> s - базовое хранилище строк равно SuperRow. Вы не можете позволить кому-то положить туда старую ITableRow!

Вот почему ваш каст не удался.

Вы можете обойти это, если избавитесь от "людей, которые могут добавлять строкик таблицам "и обещают, что люди могут только когда-либо читать строки. Тогда не имеет значения, является ли хранилище строк Table на самом деле SuperRow, так как люди не могут попытаться поместить туда другие типы строк.

Вы делаете это, делая Table<T>ковариантны. Но, как вы заметили, классы не могут быть ковариантными, только интерфейсы. Так что вам понадобится ITable<out T>.

...