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

Примере:

public class SomeClass<Derived> 
    where Derived : class
{
    public void call<Parent>()
        where Parent : class
    {
        ParenthoodChecker<Derived, Parent> checker =
            new ParenthoodChecker<Derived, Parent>();
    }
}

public class ParenthoodChecker<Derived, Parent> 
    where Parent  : class 
    where Derived : Parent
{
    public ParenthoodChecker()
    {
    }
}

В настоящее время я получаю следующее сообщение об ошибке:

Ошибка CS0311: тип «Производный» нельзя использовать в качестве параметра типа «Производный» в универсальном типе или методе «ParenthoodChecker». Не существует неявного преобразования ссылок из «Derived» в «Parent». ( CS0311 )

Есть ли какой-нибудь способ заставить такую вещь компилировать время? Я предпочел бы не проверять во время выполнения, и я чувствую, что компилятор должен быть в состоянии вывести это.

2
Godfather 28 Фев 2018 в 13:06

3 ответа

Проблема, с которой вы сталкиваетесь, заключается в том, что общее ограничение (ключевое слово where) связано только с общими аргументами класса / метода, в котором оно используется. Поэтому при написании универсального метода call<Parent> ограничения могут быть определены только для аргумента Parent.

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

public class SomeClass<Derived>
    where Derived : class
{
    public void call<Parent, NewDerived>()
        where Parent : class
        where NewDerived: Derived, Parent
    {
        ParenthoodChecker<NewDerived, Parent> checker =
            new ParenthoodChecker<NewDerived, Parent>();
    }
}

Я полагаю, что, помимо того, что это уродливо, и кроме добавления сложности, это решение не повлечет за собой неправильного поведения. Тип NewDerived по-прежнему Derived.

На более высоком теоретическом уровне это проблема сравнения значений: если A > B и A > C, можем ли мы сказать, что B > C? - Очевидно, что не потому, что точные отношения между B и C здесь не описаны.

И наоборот, если вы скажете, что Parent > NewDerived и Derived > NewDerived, это будет хорошо. Но все же вам не хватит доказательств того, что Parent > Derived. Вот и вся причина, по которой (я думаю) невозможно написать такую функцию, которая позволила бы компилятору понять, что Parent действительно является супертипом Derived.

В приведенной выше реализации вы даже можете свободно вызывать метод с Derived вместо NewDerived:

class A { }
class B : A { }

SomeClass<B> s = new SomeClass<B>();
s.call<A, B>();

В этом примере есть только два класса, A и B, поэтому нет другого класса, который бы играл роль вымышленного NewDerived. Вся операция остается между типами A (как основа) и B (как производная).

4
Zoran Horvat 28 Фев 2018 в 23:17

Компилятор запутывается здесь с фактическим именем типа:

public class ParenthoodChecker<Derived, **Parent**> 
        where Parent : class 
        where Derived : Parent

    {
        public ParenthoodChecker()
        { }
    }

Обычно мы передаем универсальный тип и указываем ограничение типа с помощью предложения where.

Попробуй это:

 public class ParenthoodChecker<Derived, TParent>        
        where Derived : Parent
        where TParent : class 
    {
        public ParenthoodChecker()
        { }
    }
-1
Bose_geek 28 Фев 2018 в 10:31

Учитывая ParenthoodChecker, на мой взгляд, такую проверку во время компиляции можно сделать более простым способом:

public class Parent { }
public class Derived : Parent { }
public class NotDerived { }

Parent p1 = (Derived)null; // This will compile
Parent p2 = (NotDerived)null; // This won't compile
0
Francesco Bonizzi 28 Фев 2018 в 10:55