=============
ATUALIZAÇÃO: usei esta resposta como base para esta entrada do blog:
Por que os parâmetros ref e out não permitem variação de tipo?
Veja a página do blog para mais comentários sobre esse assunto. Obrigado pela ótima pergunta.
=============
Vamos supor que você tem aulas Animal
, Mammal
, Reptile
, Giraffe
, Turtle
eTiger
, com os relacionamentos subclassificação óbvias.
Agora, suponha que você tenha um método void M(ref Mammal m)
. M
pode ler e escrever m
.
Você pode passar uma variável do tipo Animal
para M
?
Não. Essa variável pode conter a Turtle
, mas M
assumirá que ela contém apenas mamíferos. A Turtle
não é a Mammal
.
Conclusão 1 : ref
parâmetros não podem ser "maiores". (Existem mais animais que mamíferos, a variável está ficando "maior" porque pode conter mais coisas.)
Você pode passar uma variável do tipo Giraffe
para M
?
Não M
pode escrever para m
, e M
pode querer escrever uma Tiger
em m
. Agora você colocou um Tiger
em uma variável que é realmente do tipo Giraffe
.
Conclusão 2 : os ref
parâmetros não podem ser "menores".
Agora considere N(out Mammal n)
.
Você pode passar uma variável do tipo Giraffe
para N
?
Não. N
Pode escrever para n
e N
pode querer escrever a Tiger
.
Conclusão 3 : os out
parâmetros não podem ser "menores".
Você pode passar uma variável do tipo Animal
para N
?
Hmm.
Bem, porque não? N
não pode ler n
, só pode escrever, certo? Você escreve a Tiger
para uma variável do tipo Animal
e está tudo pronto, certo?
Errado. A regra não é " N
só pode escrever para n
".
As regras são, brevemente:
1) N
tem que escrever n
antes de N
retornar normalmente. (Se N
jogar, todas as apostas estão desativadas.)
2) N
tem que escrever algo n
antes de ler algo n
.
Isso permite esta sequência de eventos:
- Declare um campo
x
do tipo Animal
.
- Passe
x
como um out
parâmetro para N
.
N
escreve a Tiger
into n
, que é um alias para x
.
- Em outro segmento, alguém escreve um
Turtle
para x
.
N
tenta ler o conteúdo de n
e descobre um Turtle
no que ele acha que é uma variável do tipo Mammal
.
Claramente, queremos tornar isso ilegal.
Conclusão 4 : os out
parâmetros não podem ser "maiores".
Conclusão final : Nem ref
nem out
parâmetros podem variar seus tipos. Fazer o contrário é quebrar a segurança do tipo verificável.
Se essas questões na teoria básica de tipos lhe interessam, considere ler minha série sobre como a covariância e a contravariância funcionam no C # 4.0 .
out
parâmetros não podem ser "maiores"? A sequência que você descreveu pode ser aplicada a qualquer variável, não apenas àout
variável de parâmetro. E também as necessidades do leitor para lançar o valor do argumento paraMammal
antes de ele tenta acessá-lo comoMammal
e, claro, ele pode falhar se ele não tem consideraçãoPorque em ambos os casos, você deve poder atribuir valor ao parâmetro ref / out.
Se você tentar passar b para o método Foo2 como referência, e no Foo2 tentar atribuir a = new A (), isso seria inválido.
Mesmo motivo que você não pode escrever:
fonte
Você está lutando com o clássico problema de covariância (e contravariância) da OOP , consulte a Wikipedia : por mais que esse fato possa desafiar as expectativas intuitivas, é matematicamente impossível permitir a substituição de classes derivadas em vez das básicas por argumentos mutáveis (atribuíveis) (e também contêineres cujos itens são atribuíveis, pelo mesmo motivo), ainda respeitando o princípio de Liskov . Por que isso acontece, é esboçado nas respostas existentes e explorado mais profundamente nesses artigos e links da wiki.
As linguagens OOP que parecem fazê-lo enquanto permanecem tradicionalmente tipicamente seguras estão "trapaceando" (inserindo verificações ocultas de tipo dinâmico ou exigindo um exame em tempo de compilação de TODAS as fontes para verificar); a escolha fundamental é: desistir dessa covariância e aceitar a perplexidade dos profissionais (como o C # faz aqui) ou passar para uma abordagem de digitação dinâmica (como a primeira linguagem OOP, Smalltalk, fez) ou passar para imutável (single- dados de atribuição), como as linguagens funcionais (sob imutabilidade, você pode oferecer suporte à covariância e também evitar outros quebra-cabeças relacionados, como o fato de que você não pode ter a subclasse Square Rectangle em um mundo de dados mutáveis).
fonte
Considerar:
Isso violaria a segurança de tipo
fonte
var
foi totalmente errado. Fixo.Enquanto as outras respostas explicaram sucintamente o raciocínio por trás desse comportamento, acho que vale a pena mencionar que, se você realmente precisar fazer algo dessa natureza, poderá obter funcionalidade semelhante transformando o Foo2 em um método genérico, como tal:
fonte
Porque dar
Foo2
umref B
resultaria em um objeto malformado, porqueFoo2
só sabe preencherA
parte deleB
.fonte
Não é o compilador dizendo que você deseja converter o objeto explicitamente para que tenha certeza de que sabe quais são suas intenções?
fonte
Faz sentido do ponto de vista da segurança, mas eu preferiria que o compilador desse um aviso em vez de um erro, já que existem usos legítimos de objetos polimétricos passados por referência. por exemplo
Isso não será compilado, mas funcionaria?
fonte
Se você usar exemplos práticos para seus tipos, verá:
E agora você tem sua função que leva o ancestral ( ie
Object
):O que pode estar errado com isso?
Você acabou de atribuir um
Bitmap
ao seuSqlConnection
.Isso não é bom.
Tente novamente com outras pessoas:
Você colocou um
OracleConnection
over-top no seuSqlConnection
.fonte
No meu caso, minha função aceitou um objeto e não pude enviar nada, então simplesmente
E isso funciona
My Foo está no VB.NET e verifica o tipo interno e faz muita lógica
Peço desculpas se minha resposta é duplicada, mas outras foram muito longas
fonte