Em C #, a out
palavra - chave pode ser usada de duas maneiras diferentes.
Como um modificador de parâmetro no qual um argumento é passado por referência
class OutExample { static void Method(out int i) { i = 44; } static void Main() { int value; Method(out value); // value is now 44 } }
Como um modificador de parâmetro de tipo para especificar covariância .
// Covariant interface. interface ICovariant<out R> { } // Extending covariant interface. interface IExtCovariant<out R> : ICovariant<R> { } // Implementing covariant interface. class Sample<R> : ICovariant<R> { } class Program { static void Test() { ICovariant<Object> iobj = new Sample<Object>(); ICovariant<String> istr = new Sample<String>(); // You can assign istr to iobj because // the ICovariant interface is covariant. iobj = istr; } }
Minha pergunta é: por que?
Para um iniciante, a conexão entre os dois não parece intuitiva . O uso com genéricos não parece ter nada a ver com a passagem por referência.
Aprendi pela primeira vez o que out
havia em relação à passagem de argumentos por referência, e isso dificultou minha compreensão do uso da definição de covariância com genéricos.
Existe uma conexão entre esses usos que estou perdendo?
c#
terminology
language-design
keywords
Rowan Freeman
fonte
fonte
System.Func<in T, out TResult>
delegado .Respostas:
Existe uma conexão, no entanto, é um pouco frouxa. Em C #, as palavras-chave 'in' e 'out', como o nome sugere, representam entrada e saída. Isso é muito claro no caso dos parâmetros de saída, mas menos limpo o que isso tem a ver com os parâmetros do modelo.
Vamos dar uma olhada no princípio de substituição de Liskov :
Veja como a contravariância está associada à entrada e a covariância está associada à saída? Em C #, se você sinalizar uma variável de modelo
out
para torná-la covariável, mas observe que você só poderá fazer isso se o parâmetro de tipo mencionado aparecer apenas como saída (tipo de retorno da função). Portanto, o seguinte é inválido:Semelhante, se você sinalizar um parâmetro de tipo
in
, significa que você pode usá-lo apenas como entrada (parâmetro de função). Portanto, o seguinte é inválido:Então, para resumir, a conexão com a
out
palavra-chave é que, com parâmetros de função, significa que é um parâmetro de saída e, para parâmetros de tipo, significa que o tipo é usado apenas no contexto de saída .System.Func
também é um bom exemplo do que rwong mencionou em seu comentário. EmSystem.Func
todos os parâmetros de entrada, são alterados comin
e o parâmetro de saída é alterado comout
. A razão é exatamente o que eu descrevi.fonte
@ Gábor já explicou a conexão (contravariância para tudo que entra ", covariância para tudo que sai"), mas por que reutilizar palavras-chave?
Bem, as palavras-chave são muito caras. Você não pode usá-los como identificadores em seus programas. Mas há apenas tantas palavras no idioma inglês. Portanto, às vezes você se depara com conflitos e precisa renomear suas variáveis, métodos, campos, propriedades, classes, interfaces ou estruturas de maneira desajeitada para evitar conflitos com uma palavra-chave. Por exemplo, se você está modelando uma escola, como chama uma turma? Você não pode chamar de classe, porque
class
é uma palavra-chave!Adicionar uma palavra-chave a um idioma é ainda mais caro. Basicamente, torna ilegal todo o código que usa essa palavra-chave como identificador, quebrando a compatibilidade com versões anteriores em todo o lugar.
As palavras
in
-out
chave e já existiam, para que pudessem ser reutilizadas.Eles poderiam ter adicionado palavras-chave contextuais, que são apenas palavras-chave no contexto de uma lista de parâmetros de tipo, mas que palavras-chave eles escolheriam?
covariant
econtravariant
?+
e-
(como Scala fez, por exemplo)?super
eextends
como Java fez? Você consegue se lembrar de que parâmetros são covariantes e contravariantes?Com a solução atual, há um bom mnemônico: parâmetros do tipo de saída obtêm a
out
palavra - chave, parâmetros do tipo de entrada obtêm ain
palavra - chave. Observe a agradável simetria com os parâmetros do método: os parâmetros de saída obtêm aout
palavra - chave, os parâmetros de entrada obtêm ain
palavra - chave (bem, na verdade, nenhuma palavra-chave, pois a entrada é o padrão, mas você entendeu).[Nota: se você olhar o histórico de edições, verá que, na verdade, eu mudei os dois na minha frase introdutória. E até recebi uma votação durante esse tempo! Isso apenas mostra como é realmente importante esse mnemônico.]
fonte
interface Accepter<in T> { void Accept(T it);};
, umAccepter<Foo<T>>
aceitaráT
como parâmetro de entrada se oFoo<T>
aceitar como parâmetro de saída e vice-versa. Assim, contra- variância. Por outro lado,interface ISupplier<out T> { T get();};
aSupplier<Foo<T>>
terá qualquer tipo de variaçãoFoo
- portanto, co- variação.