C# нет доступного метода расширения для переопределения с определенным типом

Простите, если об этом уже спрашивали. Я искал это, но ничего не нашел.

Похоже, компилятор сбивает с толку этот код

public abstract class C1
{
    public int c1Prop;
}

public class C2 : C1
{
    public int c2Prop;
}

public abstract class P1
{
    public abstract void Run<T>(T c) where T : C1;
}

public class P2 : P1
{
    public override void Run<C2>(C2 c) 
    {
        c.c1Prop = 1; //Is recognized
        c.c2Prop = 2; //Is NOT recognized and is an error
    }
}

Я не понимаю, почему это не работает на функциональном уровне. Поскольку C2 расширяет C1, это не нарушает проверку where, но тип C2 все еще не распознается в переопределенном методе.

Для некоторой предыстории я использую аналогичный шаблон в Unity ScriptableObject, который не работает с универсальными классами, поэтому я не могу переместить общий тип на уровень класса. Однако перемещение его туда, похоже, решает проблему.

Еще один возможный обходной путь, который я придумал, - это полностью отказаться от универсального метода в пользу приведения типов. Это не кажется таким выразительным, как универсальный. Кроме того, приведение c в нескольких местах в методе Run может раздражать.

Присвоение имени аргументу универсального типа C2 не связывает его ни с каким типом с таким же именем. Ваш базовый класс объявляет, что он поддерживает вызов Run(C1). Производный класс не может этого убрать. Правильный способ решения этой проблемы зависит от того, для чего у вас базовый класс. Вы можете сделать его универсальным базовым классом, если вы просто хотите избежать дублирования кода. Не могли бы вы предоставить дополнительную информацию о том, сработает ли это?

user743382 13.09.2018 19:03
0
1
953
4
Перейти к ответу Данный вопрос помечен как решенный

Ответы 4

Ответ принят как подходящий

Когда вы говорите void Run<C2>(C2 c), вы говорите, что C2 - это общий тип, это нет, конкретный тип C2. Чтобы было понятнее, замените C2 на T:

public override void Run<T>(T c)
{
    c.c1Prop = 1; //Is recognized
    c.c2Prop = 2; //Is NOT recognized and is an error
}

Причина, по которой вы можете получить доступ к c1Prop, - это ограничение типа where T : C1 ранее в иерархии.

Один из способов обойти это - сделать сам P1 универсальным:

public abstract class P1<T> where T : C1
{
    public abstract void Run(T c);
}

Что делает P2 таким:

public class P2 : P1<C2>
{
    public override void Run(C2 c)
    {
        c.c1Prop = 1;
        c.c2Prop = 2;
    }
}

Ах, да, я не думал, что троллю себя, называя общий тип метода таким же, как мой класс. Спасибо, что прояснили это для меня.

Jim S. 13.09.2018 19:20

Первоначальная причина путаницы заключается в том, что в вашем переопределении RunC2 является параметр типа - это не, класс с именем C2. Полезно прояснить это, оставив его как T в объявлении метода переопределения:

public class P2 : P1
{
    // Changed type parameter name from C2 to T for clarity
    public override void Run<T>(T c) 
    {
        c.c1Prop = 1;
        c.c2Prop = 2;
    }
}

Это абсолютно эквивалентный код, но он понятнее, что происходит.

Теперь T ограничен where T : C1, именно так работает c.c1Prop - но это вполне осуществимо, что c не будет C2. Например, я мог бы написать:

class OtherC1 : C1 {}

P2 p2 = new P2();
p2.Run(new OtherC1());

Это явно не может работать с вашим текущим кодом - в c2Prop нет OtherC1.

Похоже, вы можете захотеть, чтобы P1 был общим, а не методом Run. У вас могло быть:

public abstract class P1<T> where T : C1
{
    public abstract void Run(T c);
}

public class P2 : P1<C2>
{
    public override void Run(C2 c) 
    {
        c.c1Prop = 1; //Is recognized
        c.c2Prop = 2; //Is NOT recognized and is an error
    }
}

Затем это будет скомпилировано, и весь код будет знать, что вы можете Только предоставить C2 (или более производный класс) для P2.Run. Таким образом, наш предыдущий пример с OtherC1 больше не будет компилироваться (чего мы и хотели бы).

В сигнатуре вашего метода указано, что метод должен быть универсальным и не может переопределять его только для определенного типа, вы можете сделать следующее:

    public class P2 : P1
    {
        public override void Run<T>(T c)
        {
            c.c1Prop = 1; //Is recognized since you have where T : C1 clause
            var c2 = c as C2;
            if (c2 != null)
            {
                c2.c2Prop = 2;
            }
        }
    }

Хотя я поддерживаю подход @DavidG, если вы не хотите использовать общее объявление в своем классе, вы можете использовать этот подход.

public interface IC1
{
    int prop1 { get; set; }
}

public interface IC2
{
    int prop2 { get; set; }
}

public abstract class C1 : IC1
{
    #region Implementation of IC1

    public int prop1 { get; set; }

    #endregion
}

public class C2 : C1, IC2
{
    #region Implementation of IC2

    public int prop2 { get; set; }

    #endregion
}

public abstract class P1
{
    public abstract void Run<T>(T c) where T : IC1, IC2;
}

public class P2 : P1
{
    public override void Run<T>(T c)
    {
        c.prop1 = 1; 
        c.prop2 = 2; 
    }
}

Другие вопросы по теме