#c#
#c#
Вопрос:
Это беспокоило меня некоторое время. У меня возникли небольшие проблемы с пониманием того, почему явные приведения необходимы в следующем коде:
public static class CastStrangeness
{
public class A
{
}
public class B
: A
{
}
public static void Foo<T>(T item)
where T : A
{
// Works.
A fromTypeParameter = item;
// Does not compile without an explicit cast.
T fromConcreteType = (T)fromTypeParameter;
// Does not compile without an explicit cast.
Foo<T>((T)fromTypeParameter);
}
public static void Bar(A item)
{
// Compiles.
Foo<A>(item);
}
}
Мне кажется, что T гарантированно будет A , поэтому, конечно, компилятор может сделать вывод, что любой экземпляр A гарантированно может быть присвоен T ? В противном случае я не смог бы передать A или B в Foo() . Итак, что я упускаю?
PS. Я пытался искать бесконечные перестановки ключевых слов в этом, но каждый результат, похоже, заканчивается ссылкой на ковариацию и контравариантность общих интерфейсов 🙂
Комментарии:
1. «конечно, компилятор мог бы сделать вывод, что любой экземпляр A гарантированно присваивается T» Почему вы так думаете?
2. Как кто-то упоминал ниже, T более специфичен, чем A (я раньше об этом не думал). Сейчас я чувствую себя немного глупо, но это небольшое изменение перспективы было тем, чего мне не хватало.
3. Кстати, фактический код, с которым я работал, был немного сложнее (и я думаю, что, возможно, этот пример кода неправильно демонстрирует проблему). Я создавал функцию обхода для b-дерева, где каждый узел имел тип BTreeNode, и у него были свойства слева и справа от типа BTreeNode. Функция обхода заключала эти BTreeNodes в кортеж<BTreeNode, …> и для этого требовалось приведение). Я не мог видеть ситуации, когда назначение завершилось бы неудачей, поэтому я решил, что неявное приведение должно работать.
Ответ №1:
Возьмем случай:
Foo(new B());
Ваше первое назначение в порядке:
A fromtypeParameter = item;
Начиная с B: A.
Но это назначение не подходит:
T fromConcreteType = fromTypeParameter;
Потому что вы вполне могли бы назначить fromTypeParameter как:
fromTypeParameter = new A();
Который вы, очевидно, не можете привести к T (который в данном случае равен B). T более специфичен, чем A, он может быть получен из A. Таким образом, вы можете пойти одним путем, но не другим, без явного приведения (которое может завершиться неудачей).
Комментарии:
1. «T более специфичен, чем A». Спасибо — это то, чего мне не хватало 🙂
Ответ №2:
Мне кажется, что T гарантированно будет A , поэтому, конечно, компилятор может сделать вывод, что любой экземпляр A гарантированно может быть присвоен T ?
Ну, нет.
string
является объектом. Но
string s = new object();
является незаконным.
Ответ №3:
Все T могут быть A … но не все A являются T.
Когда вы создаете тип, производный от A, и передаете его в общий метод, компилятор знает его как T , производный тип. Если вы не приведете его обратно, он не будет знать, что вам нужны A или T или любой тип в дереве наследования между T и A.
Эта логика применима независимо от того, используете вы дженерики или нет.
public class A
{}
public class B : A
{}
public class C: B
{}
A animal = new C();
C cat = animal; // wont compile as it does not know that A is a cat,
// you have to cast even though it looks like
// a cat from the new C();
Даже если у вас есть общее ограничение, это, как правило, относится к способу использования общего метода и предотвращает нарушение ограничения. Это не относится к какой-либо ссылке на производный тип через базовую ссылку. Хотя компилятор «мог» понять это, он может не знать, что это было вашим намерением, поэтому лучше, чтобы это было безопасно и не выводило его.
Комментарии:
1. Спасибо, да — теперь я вижу, что упускал очевидное (честно говоря, я просто потратил слишком много времени на размышления об этом и был слишком близок к проблеме).
Ответ №4:
Это менее сложно, если вы используете более конкретные имена типов и переменных:
public class Animal
{}
public class Giraffe : Animal
{}
public static void Foo<TAnimal>(TAnimal animal) where TAnimal : Animal
{
Animal generalAnimal = animal;
TAnimal possiblyMoreSpecificAnimal = (TAnimal) generalAnimal;
// The above line only works with a cast because the compiler doesn't know
// based solely on the variable's type that generalAnimal is a more
// specific animal.
}