Не получается закрывать дженерик наследником класса с другим закрытым дженериком в .net core

Допустим есть следующая иерархия:

public class ExampleHierarchy
{
    public interface IValue
    {}

    public abstract class ValueClassBase : IValue
    {}

    public class ValueClass : ValueClassBase
    {}

    public interface IInterface1<TValue>
        where TValue : IValue
    {
        TValue SomeValue { get; set; }
    }

    public abstract class RealizeInterface1Base<TValue> : IInterface1<TValue>
        where TValue : IValue
    {
        public TValue SomeValue { get; set; }
    }

    public class RealizeInterface1 : RealizeInterface1Base<ValueClass>
    {
    }

    public interface IInterface2<TTool>
        where TTool : IInterface1<IValue>
    {
        TTool SomeTool { get; set; }
    }

    public abstract class RealizeInterface2Base<TTool> : IInterface2<TTool>
        where TTool : IInterface1<IValue>
    {
        public TTool SomeTool { get; set; }
    }

    public class RealizeInterface2 : RealizeInterface2Base<RealizeInterface1>
    {
    }
}

Получаем следующую ошибку:

The type 'PokerDecisions.ExampleHierarchy.RealizeInterface1' must be convertible to 'PokerDecisions.ExampleHierarchy.IInterface1<PokerDecisions.ExampleHierarchy.IValue>' in order to use it as parameter 'TTool' in the generic class 'PokerDecisions.ExampleHierarchy.RealizeInterface2Base<TTool>'

Моя иерархия ещё более запутанная и сложная: у меня есть ещё одно вложенное наследование всех этих иерархий, и получается аж два неявно закрытых дженерика, а не один, как здесь. Что меня озадачивает: класс RealizeInterface1 реализует интерфейс IInterface1 и закрывает дженерик, но он "неконвертируемый", как пишет компилятор. Если я вместо него подставляю RealizeInterface1Base<ValueClass> и также с базовым ValueClassBase, то получу ровно ту же ошибку, однако если вместо ValueClass подставить IValue, то всё магическим образом становится конвертируемым.

    public class RealizeInterface2 : RealizeInterface2Base<RealizeInterface1Base<IValue>>
    {
    }

Делаю вывод, что ограничение в виде интерфейса, который сам является ограничением, работает как-то не совсем так, как я ожидал, но уже второй день ломаю мозги, как же будет правильно? Очевидным кажется выход для IValue также добавить параметр типа, тогда всё вылечится, например:

    public interface IInterface2<TTool, TValue>
        where TTool : IInterface1<TValue>
        where TValue : IValue
    {
        TTool SomeTool { get; set; }
    }

    public abstract class RealizeInterface2Base<TTool, TValue> : IInterface2<TTool, TValue>
        where TTool : IInterface1<TValue>
        where TValue : IValue
    {
        public TTool SomeTool { get; set; }
    }

    public class RealizeInterface2 : RealizeInterface2Base<RealizeInterface1, ValueClass>
    {
    }

Всё компилируется, все счастливы, кроме меня. Зачем мне наследник, закрывающий дженерик, если я всё равно вынужден явно смотреть, каким классом закрыт этот дженерик и явно закрывать его во всей иерархии?

Здесь это не выглядит так уж страшно, но мне нужно прошерстить пару десятков файлов и изменить ограничения, а при каждом новом файлике посмотреть из чего состоят свойства классов, которыми я закрыл верхний дженерик, и это сейчас у меня "всего" три параметра типа, а если где-то в середине объявится ещё один дженерик, нужно будет вверх по иерархии передать ещё один тип?

И ещё не нравится, что я как бы создал наследника, у которого закрыты параметры типов, и вынужден явно рядом с ним приписывать тот же тип, которым только что в нём закрыл этот параметр...

Нет ли какого-либо более изящного выхода? Или может я вообще что-то не так применяю?


Ответы (0 шт):