Apakah mungkin untuk mengganti metode non-virtual?

92

Apakah ada cara untuk mengganti metode non-virtual? atau sesuatu yang memberikan hasil yang serupa (selain membuat metode baru untuk memanggil metode yang diinginkan)?

Saya ingin mengganti metode dari Microsoft.Xna.Framework.Graphics.GraphicsDevicedengan pengujian unit dalam pikiran.

zfedoran.dll
sumber
8
Apakah maksud Anda overload atau override ? Overload = menambahkan metode dengan nama yang sama tetapi parameter berbeda (mis. Kelebihan Console.WriteLine yang berbeda). Override = (secara kasar) mengubah perilaku default metode (misalnya metode Shape.Draw yang memiliki perilaku berbeda untuk Circle, Rectangle, dll.). Anda selalu dapat membebani metode dalam kelas turunan, tetapi penggantian hanya berlaku untuk metode virtual.
itowlson

Jawaban:

112

Tidak, Anda tidak dapat mengganti metode non-virtual. Hal terdekat yang dapat Anda lakukan adalah menyembunyikan metode dengan membuat newmetode dengan nama yang sama tetapi ini tidak disarankan karena melanggar prinsip desain yang baik.

Tetapi bahkan menyembunyikan metode tidak akan memberi Anda waktu eksekusi pengiriman polimorfik panggilan metode seperti yang dilakukan oleh panggilan metode virtual yang sebenarnya. Pertimbangkan contoh ini:

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");
    }
}

Dalam contoh ini, kedua panggilan ke Mmetode cetak Foo.M. Seperti yang Anda lihat, pendekatan ini memungkinkan Anda untuk memiliki implementasi baru untuk suatu metode selama referensi ke objek tersebut berasal dari jenis turunan yang benar, tetapi menyembunyikan metode dasar tidak merusak polimorfisme.

Saya akan merekomendasikan agar Anda tidak menyembunyikan metode dasar dengan cara ini.

Saya cenderung berpihak pada mereka yang menyukai perilaku default C # yang metodenya non-virtual secara default (sebagai lawan dari Java). Saya akan melangkah lebih jauh dan mengatakan bahwa kelas juga harus disegel secara default. Warisan sulit untuk dirancang dengan benar dan fakta bahwa ada metode yang tidak ditandai sebagai virtual menunjukkan bahwa pembuat metode tersebut tidak pernah bermaksud agar metode tersebut diganti.

Edit: "waktu pelaksanaan pengiriman polimorfik" :

Yang saya maksud dengan ini adalah perilaku default yang terjadi pada waktu eksekusi saat Anda memanggil metode virtual. Katakanlah misalnya bahwa dalam contoh kode saya sebelumnya, daripada mendefinisikan metode non-virtual, saya sebenarnya mendefinisikan metode virtual dan metode overridden yang sebenarnya juga.

Jika saya harus memanggil b.Foodalam kasus itu, CLR akan dengan benar menentukan jenis objek yang bdirujuk referensi sebagai Bardan akan mengirimkan panggilan dengan Mtepat.

Andrew Hare
sumber
6
Meskipun "waktu pelaksanaan pengiriman polimorfik" secara teknis adalah cara yang tepat untuk mengatakannya, saya membayangkan ini mungkin melampaui kepala hampir semua orang!
Orion Edwards
3
Meskipun benar bahwa penulis mungkin bermaksud untuk melarang penggantian metode, tidak benar bahwa itu adalah hal yang benar untuk dilakukan. Saya merasa bahwa tim XNA seharusnya menerapkan antarmuka IGraphicsDevice untuk memungkinkan pengguna lebih fleksibel dalam debugging dan pengujian unit. Saya dipaksa untuk melakukan beberapa hal yang sangat buruk dan ini seharusnya sudah diramalkan oleh tim. Diskusi lebih lanjut dapat ditemukan di sini: forums.xna.com/forums/p/30645/226222.aspx
zfedoran
2
@Orion saya juga tidak memahaminya tetapi setelah google cepat saya menghargai melihat penggunaan istilah yang "benar".
zfedoran
10
Saya sama sekali tidak percaya argumen "penulis tidak berniat untuk itu". Itu seperti mengatakan bahwa penemu roda tidak mengantisipasi roda dipasang pada mobil sehingga kita tidak dapat menggunakannya pada mobil. Saya tahu cara kerja roda / metode dan saya ingin melakukan sesuatu yang sedikit berbeda dengannya. Saya tidak peduli apakah pencipta menginginkan saya atau tidak. Maaf karena mencoba menghidupkan kembali utas yang mati. Saya hanya perlu mengeluarkan sedikit tenaga sebelum saya menemukan beberapa cara yang sangat tidak nyaman untuk mengatasi masalah ini Saya berada :)
Jeff
2
Jadi bagaimana dengan saat Anda mewarisi basis kode yang besar, dan perlu menambahkan pengujian untuk fungsionalitas baru, tetapi untuk menguji bahwa Anda perlu membuat instance banyak mesin. Di Java, saya hanya akan memperluas bagian-bagian itu dan mengganti metode yang diperlukan dengan stub. Di C #, jika metode tidak ditandai sebagai virtual, saya harus menemukan beberapa mekanisme lain (perpustakaan mengejek atau semacamnya, dan bahkan kemudian).
Adam Parkin
22

Tidak, kamu tidak bisa.

Anda hanya dapat mengganti metode virtual - lihat MSDN di sini :

Dalam C #, kelas turunan dapat berisi metode dengan nama yang sama sebagai metode kelas dasar.

  • Metode kelas dasar harus didefinisikan secara virtual.
ChrisF
sumber
6

Jika kelas dasar tidak disegel maka Anda dapat mewarisinya dan menulis metode baru yang menyembunyikan kelas dasar (gunakan kata kunci "baru" dalam deklarasi metode). Jika tidak, tidak, Anda tidak dapat menimpanya karena penulis asli tidak pernah bermaksud untuk menimpanya, oleh karena itu mengapa ini bukan virtual.

siput
sumber
3

Saya pikir Anda mendapatkan overloading dan override bingung, overloading berarti Anda memiliki dua atau lebih metode dengan nama yang sama tetapi set parameter yang berbeda sementara overriding berarti Anda memiliki implementasi yang berbeda untuk metode dalam kelas turunan (dengan demikian mengganti atau memodifikasi perilaku di kelas dasarnya).

Jika sebuah metode virtual, Anda dapat menimpanya menggunakan kata kunci override di kelas derrived. Namun, metode non-virtual hanya dapat menyembunyikan implementasi dasar dengan menggunakan kata kunci baru sebagai pengganti kata kunci pengganti. Rute non-virtual tidak berguna jika pemanggil mengakses metode melalui variabel yang diketik sebagai tipe dasar karena kompilator akan menggunakan pengiriman statis ke metode dasar (artinya kode di kelas derrived Anda tidak akan pernah dipanggil).

Tidak pernah ada yang mencegah Anda untuk menambahkan beban berlebih ke kelas yang sudah ada, tetapi hanya kode yang mengetahui tentang kelas Anda yang dapat mengaksesnya.

Rory
sumber
2

Anda tidak dapat mengganti metode non-virtual kelas apa pun di C # (tanpa meretas CLR), tetapi Anda dapat mengganti metode antarmuka apa pun yang diterapkan kelas. Pertimbangkan kami memiliki non-disegel

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();
    }
}

Dan inilah demo

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
sumber
@Dan inilah demo langsungnya: dotnetfiddle.net/VgRwKK
Vyacheslav Napadovsky
0

Jika Anda mewarisi dari kelas yang tidak diturunkan, Anda dapat dengan mudah membuat kelas super abstrak dan mewarisi darinya sebagai gantinya.

pengguna3853059
sumber
0

Apakah ada cara untuk mengganti metode non-virtual? atau sesuatu yang memberikan hasil yang serupa (selain membuat metode baru untuk memanggil metode yang diinginkan)?

Anda tidak dapat mengganti metode non-virtual. Namun Anda dapat menggunakan newkata kunci pengubah untuk mendapatkan hasil yang serupa:

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

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

Anda juga akan ingin memastikan bahwa pengubah akses juga sama, jika tidak, Anda tidak akan mendapatkan warisan. Jika lain mewarisi kelas dari Class1yang newkata kunci dalam Class1tidak akan mempengaruhi benda mewarisi dari itu, kecuali akses pengubah adalah sama.

Jika pengubah akses tidak sama:

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());

... dibandingkan jika akses pengubah adalah sama:

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());

Seperti yang ditunjukkan dalam jawaban sebelumnya, ini bukanlah prinsip desain yang baik.

Aaron Thomas
sumber
-5

Ada cara untuk mencapai ini dengan menggunakan kelas abstrak dan metode abstrak.

Mempertimbangkan

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

     void Method1()
     {
     }

     void Method2()
     {
     }

     ...
}

Sekarang, jika Anda ingin memiliki versi berbeda dari metode MethodToBeTested (), ubah Basis Kelas menjadi kelas abstrak dan metode MethodToBeTested () sebagai metode abstrak

abstract Class Base
{

     abstract void MethodToBeTested();

     void Method1()
     {
     }

     void Method2()
     {
     }

     ...
}

Dengan void abstrak MethodToBeTested () muncul masalah; implementasinya hilang.

Oleh karena itu buatlah class DefaultBaseImplementation : Baseagar memiliki implementasi default.

Dan buat yang lain class UnitTestImplementation : Baseuntuk menerapkan pengujian unit.

Dengan 2 kelas baru ini, fungsionalitas kelas dasar dapat diganti.

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

}

Class UnitTestImplementation : Base
{

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

}

Sekarang Anda memiliki 2 kelas yang mengimplementasikan (mengganti) MethodToBeTested().

Anda dapat membuat instance kelas (turunan) sesuai kebutuhan (yaitu dengan implementasi dasar atau dengan implementasi pengujian unit).

ShivanandSK
sumber
@slavoo: Hai slavoo. Terima kasih atas pembaruan kodenya. Tapi bisakah Anda memberi tahu saya alasan tidak memilih ini?
ShivanandSK
6
Karena itu tidak menjawab pertanyaan itu. Dia menanyakan apakah Anda dapat mengganti anggota yang tidak ditandai virtual. Anda telah menunjukkan bahwa Anda harus menerapkan anggota yang ditandai abstrak.
Lee Louviere