Недавно обсуждалось, зачем нужен downcast — приведение типа от более общего к более конкретному. А нужен ли upcast (повышающее приведение) — явное приведение типов в обратную сторону, от более конкретного к более общему? Ведь мы ничего не теряем, работая с более конкретным объектом?
1 Answers
Для начала, общая причина, которая касается не только C#, но и большинства объектно-ориентированных языков: семантика. Если у программиста есть объект конкретного типа, он тем не менее может хотеть работать с ним как с более общим объектом: programming against an interface, not implementation!
Это позволяет убедиться, что в коде не используются лишние, конкретные свойства, что будет мешать в будущем обобщить код.
Разумеется, обычно это слишком строгая цель, и без этого можно обойтись.
Следующая причина — выбор перегрузки, неполиморфного метода. В зависимости от статического типа объекта (при совпадающем динамическом типе) могут быть вызваны различные перегрузки при одинаково выглядящем коде. Примеры:
Вызов нужной перегрузки:
void f(object o) { Console.WriteLine("обрабатываем объект"); }void f(string s) { f((object)s); // избегаем рекурсии Console.WriteLine("дополнительная обработка для строки"); }
string s = "Пушкин"; f(s); // вызывает перегрузку со строкой f((object)s); // вызывает перегрузку с объектом
Ещё один пример, который часто встречается в коде:
class X { public static bool operator == (X x1, X x2) { // оптимизация: проверим совпадение объектов if ((object)x1 == (object)x2) return true; // далее более дорогая проверка равенства по свойствам } }Вызов перекрытого метода:
class Base { public void X() { Console.WriteLine("нужный метод"); } }class Derived : Base { public new void X() { Console.WriteLine("бесполезный метод"); } }
Derived d = new Derived(); ((Base)d).X();
Явная реализация интерфейса не позволяет вызвать метод по имени.
class X : IDisposable { void IDisposable.Dispose() {} }var x = new X(); // ... ((IDisposable)x).Dispose(); // по-другому не вызвать
В случаях, когда тип переменной выводится неявно из типа другой переменной, бывают случаи, когда нас не устраивает автоматически выведенный тип. Пример:
var list = new[] { 1, 2 }.ToList(); list.Add("ой");Мы хотим получить список
object'ов, но выведение типов даёт нам списокint'ов. Мы можем написатьvar list = new[] { (object)1, 2 }.ToList(); list.Add("ой");так всё будет компилироваться.
Ещё один тесно связанный случай — тернарный оператор. Если типы альтернатив различны, компилятор не может найти общий тип выражения, и приходится помогать:
Animal animal = nya ? new Cat() : new Dog(); // не компилируется в C# <9 Animal animal = nya ? (Animal)new Cat() : new Dog(); // компилируется(Очень похожая проблема возникает с
Nullable-типами:int? result = good ? 1 : nullтребует явного преобразования одного из операндов-альтернатив.)Этот случай подсказал @Pavel Mayorov в комментариях, спасибо! Впрочем, как указывает @EvgeniyZ, в C# 9 с target typing в некоторых случаях преобразование типов не нужно:
Animal animal = nya ? new Cat() : new Dog(); // компилируется в C#9 var animal = nya ? new Cat() : new Dog(); // не компилируется в C#9 var animal = nya ? (Animal)new Cat() : new Dog(); // компилируетсяЕщё одно применение — неявная упаковка (boxing). Например, функции типа
GetEnumerator()могут вернуть объект типа-значения, который реализует интерфейсIEnumerator<T>. Работать с ним не всегда удобно:static public IEnumerable<R> MultiZip<T, R>( this IEnumerable<List<T>> sequences, Func<IEnumerable<T>, R> resultSelector) { var enumerators = sequences.Select(s => (IEnumerator<T>)s.GetEnumerator()).ToList(); try { while (enumerators.All(en => en.MoveNext())) yield return resultSelector(enumerators.Select(en => en.Current)); } finally { foreach (var en in enumerators) en.Dispose(); } }Если бы мы забыли upcast к
IEnumerator<T>, то вenumeratorsмог бы оказаться набор value type (и это так и есть в нашем случае!). При этом, поскольку мы мутируем наши энумераторы (MoveNext), то для случая value type мы бы вызывали этот метод на копии значения, и таким образом код бы не сработал.Ещё один случай, наверное самый частый и самый незаметный — upcast происходит неявно и автоматически в том месте, когда мы присваиваем переменной базового типа выражение дочернего типа, или передаём в функцию, ожидающую базовый тип, аргумент производного типа. Например, такое бывает, когда мы пользуемся полиформными коллекциями (коллекция животных, содержащая и кошек, и собак).
- 206,799
Object.ReferenceEquals(x1, x2)– Jul 02 '15 at 16:51object.ReferenceEqualsтоже делает (неявный) апкаст. – VladD Jul 02 '15 at 16:51