Substituição vs ocultação de método [duplicado]
-
27-09-2019 - |
Pergunta
Essa pergunta já tem resposta aqui:
Estou um pouco confuso sobre substituir vs.ocultando um método em C#.Os usos práticos de cada um também seriam apreciados, bem como uma explicação para quando um usaria cada um.
Estou confuso sobre a substituição - por que substituímos?O que aprendi até agora é que através da substituição podemos fornecer a implementação desejada para um método de uma classe derivada, sem alterar a assinatura.
Se eu não substituir o método da superclasse e fizer alterações no método da subclasse, isso fará alterações no método da superclasse?
Também estou confuso sobre o seguinte – o que isso demonstra?
class A
{
virtual m1()
{
console.writeline("Bye to all");
}
}
class B : A
{
override m1()
{
console.writeLine("Hi to all");
}
}
class C
{
A a = new A();
B b = new B();
a = b; (what is this)
a.m1(); // what this will print and why?
b = a; // what happens here?
}
Solução
Considerar:
public class BaseClass
{
public void WriteNum()
{
Console.WriteLine(12);
}
public virtual void WriteStr()
{
Console.WriteLine("abc");
}
}
public class DerivedClass : BaseClass
{
public new void WriteNum()
{
Console.WriteLine(42);
}
public override void WriteStr()
{
Console.WriteLine("xyz");
}
}
/* ... */
BaseClass isReallyBase = new BaseClass();
BaseClass isReallyDerived = new DerivedClass();
DerivedClass isClearlyDerived = new DerivedClass();
isReallyBase.WriteNum(); // writes 12
isReallyBase.WriteStr(); // writes abc
isReallyDerived.WriteNum(); // writes 12
isReallyDerived.WriteStr(); // writes xyz
isClearlyDerived.WriteNum(); // writes 42
isClearlyDerived.writeStr(); // writes xyz
A substituição é a maneira clássica de OO pela qual uma classe derivada pode ter um comportamento mais específico do que uma classe base (em alguns idiomas que você não tem escolha a não ser fazê -lo). Quando um método virtual é chamado em um objeto, a versão mais derivada do método é chamada. Portanto, apesar de estarmos lidando com isReallyDerived
como um BaseClass
então funcionalidade definida em DerivedClass
é usado.
Esconder significa que temos um método completamente diferente. Quando ligamos WriteNum()
sobre isReallyDerived
Então não há como saber que há um diferente WriteNum()
sobre DerivedClass
Portanto, não é chamado. Só pode ser chamado quando estamos lidando com o objeto Como uma DerivedClass
.
Na maioria das vezes, esconder é ruim. Geralmente, você deve ter um método como virtual, se é provável que seja alterado em uma classe derivada e substituí -lo na classe derivada. No entanto, há duas coisas para as quais é útil:
Compatibilidade encaminhada. Se
DerivedClass
tinha umDoStuff()
método, e depois mais tardeBaseClass
foi alterado para adicionar umDoStuff()
Método (lembre -se de que eles podem ser escritos por pessoas diferentes e existem em diferentes assembléias), então a proibição do esconderDerivedClass
Buggy sem mudar. Além disso, se o novoDoStuff()
sobreBaseClass
foi virtual, então automaticamente fazendo isso emDerivedClass
Uma substituição pode levar ao chamado o método pré-existente quando não deveria. Portanto, é bom que o esconder seja o padrão (nós usamosnew
Para deixar claro, definitivamente queremos nos esconder, mas deixá -lo esconde e emite um aviso na compilação).Covariância do pobre homem. Considere a
Clone()
método emBaseClass
que retorna um novoBaseClass
Essa é uma cópia disso criada. Na substituiçãoDerivedClass
Isso criará umDerivedClass
Mas devolva -o como umBaseClass
, o que não é tão útil. O que poderíamos fazer é ter uma proteção virtualCreateClone()
Isso é substituído. DentroBaseClass
nós temos umaClone()
isso retorna o resultado disso - e tudo está bem - emDerivedClass
escondemos isso com um novoClone()
que retorna aDerivedClass
. ChamandoClone()
sobreBaseClass
sempre retornará umBaseClass
referência, que será umBaseClass
valor ou aDerivedClass
valor conforme apropriado. ChamandoClone()
sobreDerivedClass
retornará umDerivedClass
valor, que é o que queremos nesse contexto. Existem outras variantes desse princípio, no entanto, deve -se notar que todas são muito raras.
Uma coisa importante a ser observada com o segundo caso é que usamos esconder -se precisamente a remover surpresas para o código de chamada, como a pessoa que usa DerivedClass
pode razoavelmente esperar seu Clone()
para devolver um DerivedClass
. Os resultados de qualquer uma das maneiras pelas quais ele poderia ser chamado são mantidos consistentes entre si. A maioria dos casos de ocultar riscos introduzindo surpresas, e é por isso que elas geralmente são desaprovadas. Este é justificado com precisão porque resolve o próprio problema que o esconderijo geralmente apresenta.
Ao todo, o esconderijo às vezes é necessário, raramente útil, mas geralmente ruim, por isso tenha muito cuidado com isso.
Outras dicas
A substituição ocorre quando você fornece um novo override
implementação de um método em uma classe descendente quando esse método é definido na classe base como virtual
.
Ocultar é quando você fornece uma nova implementação de um método em uma classe descendente quando esse método é não definido na classe base como virtual
, ou quando sua nova implementação não especifica override
.
Esconder-se muitas vezes é ruim;geralmente você deve tentar não fazer isso, se puder evitá-lo.Ocultar pode fazer com que coisas inesperadas aconteçam, porque os métodos ocultos são usados apenas quando chamados em uma variável do tipo real que você definiu, e não se estiver usando uma referência de classe base...por outro lado, os métodos virtuais que são substituídos terminarão com a versão adequada do método sendo chamada, mesmo quando chamados usando a referência da classe base em uma classe filha.
Por exemplo, considere estas classes:
public class BaseClass
{
public virtual void Method1() //Virtual method
{
Console.WriteLine("Running BaseClass Method1");
}
public void Method2() //Not a virtual method
{
Console.WriteLine("Running BaseClass Method2");
}
}
public class InheritedClass : BaseClass
{
public override void Method1() //Overriding the base virtual method.
{
Console.WriteLine("Running InheritedClass Method1");
}
public new void Method2() //Can't override the base method; must 'new' it.
{
Console.WriteLine("Running InheritedClass Method2");
}
}
Vamos chamá-lo assim, com uma instância de InheritedClass, em uma referência correspondente:
InheritedClass inherited = new InheritedClass();
inherited.Method1();
inherited.Method2();
Isso retorna o que você deveria esperar;ambos os métodos dizem que estão executando as versões InheritedClass.
Executando o método InheritedClass1
Executando Método HerdadoClass2
Este código cria uma instância do mesmo, InheritedClass, mas a armazena em uma referência BaseClass:
BaseClass baseRef = new InheritedClass();
baseRef.Method1();
baseRef.Method2();
Normalmente, sob os princípios OOP, você deve esperar o mesmo resultado do exemplo acima.Mas você não obtém a mesma saída:
Executando o método InheritedClass1
Executando o Método BaseClass2
Quando você escreveu o código InheritedClass, talvez você quisesse que todas as chamadas fossem Method2()
para executar o código que você escreveu nele.Normalmente, seria assim que funcionaria - supondo que você esteja trabalhando com um virtual
método que você substituiu.Mas porque você está usando um new
/hidden , ele chama a versão na referência que você está usando.
Se esse é o comportamento que você realmente quero, então;ai está.Mas eu sugiro fortemente que, se é isso que você deseja, pode haver um problema arquitetônico maior com o código.
A substituição do método é simples, substituindo uma implementação padrão de um método de classe base na classe derivada.
Esconder
Como
class Foo
{
public virtual void foo1()
{
}
}
class Bar:Foo
{
public new virtual void foo1()
{
}
}
Agora, se você fizer outro bar1 de classe que é derivado do bar, poderá substituir o Foo1 que é definido em bar.