Por que não 'ref' e 'out' polimorfismo apoio?
-
05-07-2019 - |
Pergunta
Leve o seguinte:
class A {}
class B : A {}
class C
{
C()
{
var b = new B();
Foo(b);
Foo2(ref b); // <= compile-time error:
// "The 'ref' argument doesn't match the parameter type"
}
void Foo(A a) {}
void Foo2(ref A a) {}
}
Por que ocorre o erro de tempo de compilação acima? Isso acontece com ambos os argumentos ref
e out
.
Solução
=============
UPDATE: eu usei esta resposta como a base para esta entrada do blog:
Por que parâmetros ref e fora não permite alteração de tipo?
Veja a página de blog para mais comentários sobre esta questão. Agradecimentos para a grande questão.
=============
Vamos supor que você tem aulas Animal
, Mammal
, Reptile
, Giraffe
, Turtle
e Tiger
, com as relações de subclassificação óbvio.
Agora, suponha que você tem um void M(ref Mammal m)
método. M
pode tanto ler e escrever m
.
Você pode passar uma variável do tipo
Animal
paraM
?
No. Essa variável pode conter um Turtle
, mas M
vai assumir que ele contém apenas mamíferos. A Turtle
não é um Mammal
.
Conclusão 1 : parâmetros ref
não pode ser feita "maior". (Há mais animais do que os mamíferos, então a variável está ficando "maior" porque pode conter mais coisas.)
Você pode passar uma variável do tipo
Giraffe
paraM
?
No. M
pode escrever para m
e M
pode querer escrever uma Tiger
em m
. Agora você colocar um Tiger
em uma variável que é realmente do tipo Giraffe
.
Conclusão 2 :. Parâmetros ref
não pode ser feita "menor"
Agora, considere N(out Mammal n)
.
Você pode passar uma variável do tipo
Giraffe
paraN
?
No. N
pode escrever para n
e N
pode querer escrever uma Tiger
.
Conclusão 3 :. Parâmetros out
não pode ser feita "menor"
Você pode passar uma variável do tipo
Animal
paraN
?
Hmm.
Bem, por que não? N
não pode ler n
, só pode escrever para ele, certo? Você escreve um Tiger
a uma variável do tipo Animal
e está tudo pronto, certo?
errada. A regra não é "N
só pode escrever para n
".
As regras são, resumidamente:
1) N
tem de escrever para n
antes N
retorna normalmente. (Se N
mantas, todas as apostas estão fora.)
2) N
tem que escrever algo para n
antes ele lê algo de n
.
Isso permite que essa seqüência de eventos:
- Declare uma
x
campo do tipoAnimal
. - Passe
x
como um parâmetroout
paraN
. -
N
escreve umTiger
emn
, que é um alias parax
. - Em outro segmento, alguém escreve um
Turtle
emx
. -
N
tentativas de ler o conteúdo don
, e descobre umTurtle
no que ele pensa que é uma variável do tipoMammal
.
É claro que nós queremos fazer é ilegal.
Conclusão 4 :. Parâmetros out
não pode ser feita "maior"
Conclusão final : Nem os parâmetros ref
nem out
podem variar seus tipos. Para fazer o contrário é quebrar a segurança de tipos verificável.
Se estas questões no tipo theo básicary lhe interessam, considere ler minha série sobre como covariância e contravariance trabalho em C # 4.0 .
Outras dicas
Porque em ambos os casos, você deve ser capaz de valor atribuir a ref / out parâmetro.
Se você tentar passar b em método foo2 como referência, e em foo2 tentar assing a = new A (), isso seria inválido.
Mesma razão que você não pode escrever:
B b = new A();
Você está lutando com o problema clássico OOP de covariância (e contravariance), consulte wikipedia : tanto quanto este fato pode desafiar as expectativas intuitivas, é matematicamente impossível para permitir a substituição de classes derivadas em vez das bases para mutável (atribuíveis) argumentos (e também recipientes cujos itens são atribuíveis, por apenas pelo mesmo motivo) enquanto ainda respeitando o princípio do Liskov. Por que é tão é esboçado nas respostas existentes, e explorou mais profundamente nestes artigos wiki e links dos mesmos.
linguagens OOP que aparecem a fazê-lo mantendo-se tradicionalmente estaticamente typesafe são "batota" (inserir verificações de tipo dinâmica escondidos, ou que necessitam de exame em tempo de compilação de todas as fontes para verificação); a escolha fundamental é: ou desistir dessa covariância e aceito perplexidade dos praticantes (como C # faz aqui), ou mudança para uma abordagem tipagem dinâmica (como a primeira linguagem OOP, Smalltalk, fez), ou movimento para imutável (single- dados de atribuição), como linguagens funcionais fazer (sob imutabilidade, você pode apoiar covariância, e também evitar outros enigmas relacionados, tais como o fato de que você não pode ter Praça subclasse retângulo em um mundo de dados mutáveis).
Considere o seguinte:
class C : A {}
class B : A {}
void Foo2(ref A a) { a = new C(); }
B b = null;
Foo2(ref b);
É violaria tipo de segurança
Enquanto as outras respostas têm sucintamente explicou o raciocínio por trás esse comportamento, eu acho que vale a pena mencionar que, se você realmente precisa fazer algo desta natureza você pode realizar funcionalidade semelhante, fazendo foo2 em um método genérico, como tal:
class A {}
class B : A {}
class C
{
C()
{
var b = new B();
Foo(b);
Foo2(ref b); // <= no compile error!
}
void Foo(A a) {}
void Foo2<AType> (ref AType a) where AType: A {}
}
Porque dar Foo2
um ref B
resultaria em um objeto malformado porque Foo2
só sabe como preencher parte A
de B
.
Não é que o compilador lhe dizendo que gostaria que você converter explicitamente o objeto para que ele pode ter certeza que você sabe quais são suas intenções?
Foo2(ref (A)b)
Faz sentido a partir de uma perspectiva de segurança, mas eu teria preferido que se o compilador deu um aviso, em vez de um erro, uma vez que existem usos legítimos de objetos polymoprhic passados ??por referência. por exemplo.
class Derp : interfaceX
{
int somevalue=0; //specified that this class contains somevalue by interfaceX
public Derp(int val)
{
somevalue = val;
}
}
void Foo(ref object obj){
int result = (interfaceX)obj.somevalue;
//do stuff to result variable... in my case data access
obj = Activator.CreateInstance(obj.GetType(), result);
}
main()
{
Derp x = new Derp();
Foo(ref Derp);
}
Isso não irá compilar, mas será que funciona?
Se você usa exemplos práticos para os seus tipos, você vai vê-lo:
SqlConnection connection = new SqlConnection();
Foo(ref connection);
E agora você tem a sua função que leva o ancestral ( i Object
.):
void Foo2(ref Object connection) { }
O que pode haver de errado com isso?
void Foo2(ref Object connection)
{
connection = new Bitmap();
}
Você só conseguiu atribuir uma Bitmap
ao seu SqlConnection
.
Isso não é bom.
Tente novamente com os outros:
SqlConnection conn = new SqlConnection();
Foo2(ref conn);
void Foo2(ref DbConnection connection)
{
conn = new OracleConnection();
}
Você recheado um OracleConnection
sobre-topo de sua SqlConnection
.
No meu caso a minha função aceita um objeto e eu não poderia enviar em nada, então eu simplesmente fiz
object bla = myVar;
Foo(ref bla);
E que as obras
Meu Foo está em VB.NET e ele verifica tipo dentro e faz um monte de lógica
Peço desculpas se minha resposta é duplicado, mas outros eram demasiado longo