É possível substituir um método não virtual?

90

Existe alguma maneira de substituir um método não virtual? ou algo que dá resultados semelhantes (diferente de criar um novo método para chamar o método desejado)?

Eu gostaria de substituir um método Microsoft.Xna.Framework.Graphics.GraphicsDevicecom o teste de unidade em mente.

zfedoran
fonte
8
Você quer dizer sobrecarregar ou substituir ? Overload = adiciona um método com o mesmo nome, mas parâmetros diferentes (por exemplo, diferentes sobrecargas de Console.WriteLine). Override = (aproximadamente) altera o comportamento padrão do método (por exemplo, um método Shape.Draw que possui um comportamento diferente para Círculo, Retângulo, etc.). Você sempre pode sobrecarregar um método em uma classe derivada, mas a substituição só se aplica a métodos virtuais.
itowlson

Respostas:

111

Não, você não pode substituir um método não virtual. A coisa mais próxima que você pode fazer é ocultar o método criando um newmétodo com o mesmo nome, mas isso não é aconselhável, pois quebra os bons princípios de design.

Mas mesmo ocultar um método não dará a você o tempo de execução do despacho polimórfico de chamadas de método como uma verdadeira chamada de método virtual faria. Considere este exemplo:

using System;

class Example
{
    static void Main()
    {
        Foo f = new Foo();
        f.M();

        Foo b = new Bar();
        b.M();
    }
}

class Foo
{
    public void M()
    {
        Console.WriteLine("Foo.M");
    }
}

class Bar : Foo
{
    public new void M()
    {
        Console.WriteLine("Bar.M");
    }
}

Neste exemplo, ambas as chamadas para o Mmétodo print Foo.M. Como você pode ver esta abordagem permitirá que você tenha uma nova implementação de um método, desde que a referência a esse objeto é do tipo derivado correto, mas escondendo um método base faz polimorfismo pausa.

Eu recomendo que você não oculte métodos básicos dessa maneira.

Eu tendo a ficar do lado daqueles que favorecem o comportamento padrão do C #, que os métodos são não virtuais por padrão (ao contrário do Java). Eu iria ainda mais longe e diria que as classes também deveriam ser fechadas por padrão. A herança é difícil de projetar adequadamente e o fato de haver um método que não está marcado como virtual indica que o autor desse método nunca pretendeu que o método fosse sobrescrito.

Editar: "envio polimórfico do tempo de execução" :

O que quero dizer com isso é o comportamento padrão que ocorre em tempo de execução quando você chama métodos virtuais. Digamos, por exemplo, que em meu exemplo de código anterior, em vez de definir um método não virtual, eu de fato defini um método virtual e um verdadeiro método substituído.

Se eu fosse chamar b.Foonesse caso, o CLR determinaria corretamente o tipo de objeto para o qual a breferência aponta Bare despacharia a chamada Mapropriadamente.

Andrew Hare
fonte
6
Embora "despacho polimórfico em tempo de execução" seja tecnicamente a maneira correta de dizer isso, imagino que isso provavelmente passe pela cabeça de quase todo mundo!
Orion Edwards
3
Embora seja verdade que o autor pode ter pretendido proibir a substituição de método, não é verdade que era necessariamente a coisa correta a fazer. Eu acho que a equipe XNA deveria ter implementado uma interface IGraphicsDevice para permitir ao usuário mais flexibilidade na depuração e teste de unidade. Sou obrigado a fazer coisas muito feias e isso deveria ter sido previsto pela equipe. Mais discussões podem ser encontradas aqui: forums.xna.com/forums/p/30645/226222.aspx
zfedoran
2
@Orion Eu também não entendi, mas depois de uma rápida pesquisa no Google, gostei de ver o uso dos termos "corretos".
zfedoran
9
Eu não acredito em nada do argumento do "autor não pretendia". É como dizer que o inventor da roda não previu que rodas fossem colocadas em carros, então não podemos usá-las em carros. Eu sei como funciona a roda / método e quero fazer algo um pouco diferente com ele. Não me importa se o criador quis ou não. Desculpe por tentar reviver um tópico morto. Só preciso desabafar antes de descobrir uma maneira realmente inconveniente de contornar este problema em que estou :)
Jeff,
2
E quando você herda uma grande base de código e precisa adicionar um teste para uma nova funcionalidade, mas para testar isso você precisa instanciar um monte de máquinas. Em Java, eu apenas estenderia essas partes e substituiria os métodos necessários com stubs. Em C #, se os métodos não estiverem marcados como virtuais, tenho que encontrar algum outro mecanismo (biblioteca de simulação ou algo parecido, e mesmo assim).
Adam Parkin
22

Não, você não pode.

Você só pode substituir um método virtual - consulte o MSDN aqui :

Em C #, as classes derivadas podem conter métodos com o mesmo nome dos métodos da classe base.

  • O método da classe base deve ser definido virtual.
ChrisF
fonte
6

Se a classe base não for selada, você pode herdar dela e escrever um novo método que oculte o básico (use a palavra-chave "new" na declaração do método). Caso contrário, não, você não pode substituí-lo porque nunca foi a intenção do autor original que fosse substituído, daí porque não é virtual.

slugster
fonte
3

Acho que você está ficando sobrecarregado e confuso de sobreposição, sobrecarregar significa que você tem dois ou mais métodos com o mesmo nome, mas diferentes conjuntos de parâmetros, enquanto sobrepor significa que você tem uma implementação diferente para um método em uma classe derivada (substituindo ou modificando assim o comportamento em sua classe base).

Se um método for virtual, você pode substituí-lo usando a palavra-chave override na classe derrived. No entanto, os métodos não virtuais só podem ocultar a implementação básica usando a nova palavra-chave no lugar da palavra-chave override. A rota não virtual é inútil se o chamador acessa o método por meio de uma variável digitada como o tipo base, pois o compilador usaria um despacho estático para o método base (significando que o código em sua classe derivada nunca seria chamado).

Nunca há nada que o impeça de adicionar uma sobrecarga a uma classe existente, mas apenas o código que sabe sobre sua classe seria capaz de acessá-la.

Rory
fonte
2

Você não pode substituir o método não virtual de qualquer classe em C # (sem hackear o CLR), mas pode substituir qualquer método de interface que a classe implemente. Considere que temos não selados

class GraphicsDevice: IGraphicsDevice {
    public void DoWork() {
        Console.WriteLine("GraphicsDevice.DoWork()");
    }
}

// with its interface
interface IGraphicsDevice {
    void DoWork();
}

// You can't just override DoWork in a child class,
// but if you replace usage of GraphicsDevice to IGraphicsDevice,
// then you can override this method (and, actually, the whole interface).

class MyDevice: GraphicsDevice, IGraphicsDevice {
    public new void DoWork() {
        Console.WriteLine("MyDevice.DoWork()");
        base.DoWork();
    }
}

E aqui está a demonstração

class Program {
    static void Main(string[] args) {

        IGraphicsDevice real = new GraphicsDevice();
        var myObj = new MyDevice();

        // demo that interface override works
        GraphicsDevice myCastedToBase = myObj;
        IGraphicsDevice my = myCastedToBase;

        // obvious
        Console.WriteLine("Using real GraphicsDevice:");
        real.DoWork();

        // override
        Console.WriteLine("Using overriden GraphicsDevice:");
        my.DoWork();

    }
}
Vyacheslav Napadovsky
fonte
@Dan aqui está a demonstração ao vivo: dotnetfiddle.net/VgRwKK
Vyacheslav Napadovsky
0

No caso de você estar herdando de uma classe não derivada, pode simplesmente criar uma superclasse abstrata e herdar dela posteriormente.

user3853059
fonte
0

Existe alguma maneira de substituir um método não virtual? ou algo que dá resultados semelhantes (diferente de criar um novo método para chamar o método desejado)?

Você não pode substituir um método não virtual. No entanto, você pode usar a newpalavra-chave modificadora para obter resultados semelhantes:

class Class0
{
    public int Test()
    {
        return 0;
    }
}

class Class1 : Class0
{
    public new int Test()
    {
        return 1;
    }
}
. . .
// result of 1
Console.WriteLine(new Class1().Test());

Você também desejará certificar-se de que o modificador de acesso também é o mesmo, caso contrário, você não obterá herança no futuro. Se outra classe herdar da palavra Class1- newchave in Class1, não afetará os objetos que herdam dela, a menos que o modificador de acesso seja o mesmo.

Se o modificador de acesso não for o mesmo:

class Class0
{
    protected int Test()
    {
        return 0;
    }
}

class Class1 : Class0
{
    // different access modifier
    new int Test()
    {
        return 1;
    }
}

class Class2 : Class1
{
    public int Result()
    {
        return Test();
    }
}
. . .
// result of 0
Console.WriteLine(new Class2().Result());

... versus se o modificador de acesso for o mesmo:

class Class0
{
    protected int Test()
    {
        return 0;
    }
}

class Class1 : Class0
{
    // same access modifier
    protected new int Test()
    {
        return 1;
    }
}

class Class2 : Class1
{
    public int Result()
    {
        return Test();
    }
}
. . .
// result of 1
Console.WriteLine(new Class2().Result());

Conforme apontado em uma resposta anterior, este não é um bom princípio de design.

Aaron Thomas
fonte
-5

Existe uma maneira de fazer isso usando classe abstrata e método abstrato.

Considerar

Class Base
{
     void MethodToBeTested()
     {
        ...
     }

     void Method1()
     {
     }

     void Method2()
     {
     }

     ...
}

Agora, se você deseja ter diferentes versões do método MethodToBeTested (), altere a base de classe para uma classe abstrata e o método MethodToBeTested () como um método abstrato

abstract Class Base
{

     abstract void MethodToBeTested();

     void Method1()
     {
     }

     void Method2()
     {
     }

     ...
}

Com o void abstrato MethodToBeTested () surge um problema; a implementação acabou.

Portanto, crie um class DefaultBaseImplementation : Basepara ter a implementação padrão.

E crie outro class UnitTestImplementation : Basepara ter implementação de teste de unidade.

Com essas 2 novas classes, a funcionalidade da classe base pode ser substituída.

Class DefaultBaseImplementation : Base    
{
    override void MethodToBeTested()    
    {    
        //Base (default) implementation goes here    
    }

}

Class UnitTestImplementation : Base
{

    override void MethodToBeTested()    
    {    
        //Unit test implementation goes here    
    }

}

Agora você tem 2 classes implementando (substituindo) MethodToBeTested().

Você pode instanciar a classe (derivada) conforme necessário (ou seja, com implementação básica ou com implementação de teste de unidade).

ShivanandSK
fonte
@slavoo: Oi, slavoo. Obrigado pela atualização do código. Mas você poderia me dizer o motivo de não votar?
ShivanandSK
6
Porque não responde à pergunta. Ele está perguntando se você pode substituir membros não marcados como virtuais. Você demonstrou que precisa implementar membros marcados como abstratos.
Lee Louviere