=============
ОБНОВЛЕНИЕ: я использовал этот ответ как основу для этой записи в блоге:
Почему параметры ref и out не допускают изменение типа?
См. Страницу блога для получения дополнительных комментариев по этому вопросу. Спасибо за отличный вопрос.
=============
Давайте предположим , что у вас есть классы Animal
, Mammal
, Reptile
, Giraffe
, Turtle
и Tiger
, с очевидными отношениями подклассов.
Теперь предположим, что у вас есть метод void M(ref Mammal m)
. M
может читать и писать m
.
Вы можете передать переменную типа Animal
в M
?
Нет. Эта переменная может содержать a Turtle
, но M
предполагается, что она содержит только Mammals. А Turtle
не а Mammal
.
Вывод 1 : ref
параметры нельзя делать «больше». (Животных больше, чем млекопитающих, поэтому переменная становится «больше», потому что может содержать больше вещей.)
Вы можете передать переменную типа Giraffe
в M
?
Нет. Вы M
можете писать на m
, а M
возможно, захотите написать Tiger
в m
. Теперь вы поместили Tiger
в переменную, которая на самом деле имеет тип Giraffe
.
Вывод 2 : ref
параметры нельзя делать «меньше».
Теперь посмотрим N(out Mammal n)
.
Вы можете передать переменную типа Giraffe
в N
?
Нет. N
Можете написать n
и, N
возможно, захотите написать a Tiger
.
Вывод 3 : out
параметры «меньше» делать нельзя.
Вы можете передать переменную типа Animal
в N
?
Хм.
А почему бы не? N
не может читать n
, он может только писать, не так ли? Вы пишете Tiger
переменную типаAnimal
и все готово, верно?
Неправильно. Правило не « N
можно только писать n
».
Вкратце правила таковы:
1) N
должен выполнить запись n
до N
нормального возврата. (Если N
бросает, все ставки отменяются.)
2) N
должен что-то написать до n
того, как что-то прочитает n
.
Это разрешает такую последовательность событий:
- Объявите поле
x
типа Animal
.
- Передайте
x
в качестве out
параметра N
.
N
записывает Tiger
в n
, который является псевдонимом для x
.
- В другом потоке кто-то записывает
Turtle
в x
.
N
пытается прочитать содержимое n
и обнаруживает, Turtle
что он считает переменной типа Mammal
.
Ясно, что мы хотим сделать это незаконным.
Вывод 4 : out
параметры нельзя делать «больше».
Окончательный вывод : Ни параметры, ref
ни out
параметры не могут изменять свои типы. В противном случае нарушается безопасность проверяемого типа.
Если вас интересуют эти вопросы базовой теории типов, подумайте о прочтении моей серии статей о том, как ковариация и контравариантность работают в C # 4.0 .
out
параметры нельзя сделать «больше»? Описанная вами последовательность может применяться к любой переменной, а не только кout
переменной параметра. А также читателю необходимо преобразовать значение аргумента в значение,Mammal
прежде чем он попытается получить к нему доступ,Mammal
и, конечно же, он может потерпеть неудачу, если он не внимателенПотому что в обоих случаях вы должны иметь возможность присвоить значение параметру ref / out.
Если вы попытаетесь передать b в метод Foo2 в качестве ссылки, а в Foo2 вы попытаетесь назначить a = new A (), это будет недопустимо.
По той же причине, по которой вы не можете писать:
источник
Вы боретесь с классической проблемой ООП ковариантности (и контравариантности), см. Википедию : хотя этот факт может противоречить интуитивным ожиданиям, математически невозможно разрешить замену производных классов вместо базовых для изменяемых (назначаемых) аргументов (и также контейнеры, элементы которых можно присваивать по той же причине), при этом соблюдая принцип Лискова . Почему это так, описано в существующих ответах и более подробно исследовано в этих статьях вики и ссылках на них.
ООП-языки, которые, кажется, делают это, оставаясь при этом традиционно статически безопасными, являются «читерскими» (вставка скрытых проверок динамического типа или требование проверки во время компиляции ВСЕХ источников для проверки); фундаментальный выбор: либо отказаться от этой ковариантности и принять недоумение практикующих (как здесь делает C #), либо перейти к подходу динамической типизации (как это делал самый первый язык ООП, Smalltalk), либо перейти к неизменяемому (одно- присваивание), как это делают функциональные языки (при неизменности вы можете поддерживать ковариацию, а также избегать других связанных загадок, таких как тот факт, что у вас не может быть подкласса Square Rectangle в мире изменяемых данных).
источник
Рассматривать:
Это нарушит безопасность типов
источник
var
было совершенно неправильно. Исправлена.В то время как в других ответах лаконично объясняется причина такого поведения, я думаю, стоит упомянуть, что если вам действительно нужно сделать что-то в этом роде, вы можете выполнить аналогичную функцию, превратив Foo2 в общий метод как таковой:
источник
Поскольку предоставление
Foo2
aref B
приведет к искажению объекта, потому чтоFoo2
знает, как заполнить толькоA
частьB
.источник
Разве это не компилятор, говорящий вам, что он хотел бы, чтобы вы явно привели объект, чтобы он мог быть уверен, что вы знаете, каковы ваши намерения?
источник
Имеет смысл с точки зрения безопасности, но я бы предпочел, чтобы компилятор выдавал предупреждение вместо ошибки, поскольку есть законное использование полиморфных объектов, переданных по ссылке. например
Это не скомпилируется, но будет ли это работать?
источник
Если вы воспользуетесь практическими примерами для своих типов, вы увидите:
И теперь у вас есть функция, которая принимает предка ( т.е.
Object
):Что тут может быть плохого?
Вам только что удалось назначить
Bitmap
свойSqlConnection
.Это не хорошо.
Попробуйте еще раз с другими:
Вы набили
OracleConnection
свойSqlConnection
.источник
В моем случае моя функция приняла объект, и я не мог ничего отправить, поэтому просто сделал
И это работает
My Foo находится в VB.NET, он проверяет тип внутри и выполняет много логики
Прошу прощения, если мой ответ повторяется, но другие были слишком длинными
источник