Apakah ada cara di C # untuk menimpa metode kelas dengan metode ekstensi?

98

Ada saat-saat di mana saya ingin menimpa metode di kelas dengan metode ekstensi. Apakah ada cara untuk melakukan itu di C #?

Sebagai contoh:

public static class StringExtension
{
    public static int GetHashCode(this string inStr)
    {
        return MyHash(inStr);
    }
}

Kasus di mana saya ingin melakukan ini adalah untuk dapat menyimpan hash string ke dalam database dan memiliki nilai yang sama digunakan oleh semua kelas yang menggunakan hash kelas string (yaitu Kamus, dll.) Sejak Algoritme hashing. Net bawaan tidak dijamin kompatibel dari satu versi Framework ke versi berikutnya, saya ingin menggantinya dengan versi saya sendiri.

Ada kasus lain yang saya temui di mana saya ingin mengganti metode kelas dengan metode ekstensi juga sehingga tidak hanya spesifik untuk kelas string atau metode GetHashCode.

Saya tahu saya bisa melakukan ini dengan subclassing dari kelas yang ada tetapi akan berguna untuk dapat melakukannya dengan ekstensi dalam banyak kasus.

Phred Menyhert
sumber
Karena Dictionary adalah struktur data dalam memori, apa bedanya jika algoritme hashing berubah dari versi framework ke versi berikutnya? Jika versi framework berubah, maka jelas aplikasi telah direstart dan kamus telah dibuat ulang.
David Nelson

Jawaban:

92

Tidak; metode ekstensi tidak pernah mengambil prioritas di atas metode instance dengan tanda tangan yang sesuai, dan tidak pernah berpartisipasi dalam polimorfisme ( GetHashCodeadalah virtualmetode).

Marc Gravell
sumber
"... tidak pernah berpartisipasi dalam polimorfisme (GetHashCode adalah metode virtual)" Bisakah Anda menjelaskan dengan cara lain mengapa metode ekstensi tidak akan pernah berpartisipasi dalam polimorfisme karena bersifat virtual? Saya mengalami masalah dalam melihat hubungan dengan kedua pernyataan tersebut.
Alex
3
@Alex dengan menyebutkan virtual, saya hanya menjelaskan apa artinya menjadi polimorfik. Di hampir semua penggunaan GetHashCode, jenis konkretnya tidak diketahui - jadi polimorfisme berperan. Dengan demikian, metode ekstensi tidak akan membantu meskipun metode tersebut diprioritaskan dalam compiler biasa. Apa yang benar-benar diinginkan oleh OP adalah menambal monyet. Yang c # dan .net tidak memfasilitasi.
Marc Gravell
4
Itu menyedihkan, di C # begitu banyak metode non-sense yang menyesatkan, seperti misalnya .ToString()dalam array. Ini jelas merupakan fitur yang dibutuhkan.
Hi-Angel
Mengapa Anda tidak mendapatkan kesalahan kompiler? Misalnya saya mengetik. namespace System { public static class guilty { public static string ToLower(this string s) { return s.ToUpper() + " I'm Malicious"; } } }
LowLevel
@LowLevel mengapa Anda?
Marc Gravell
7

Jika metode tersebut memiliki tanda tangan yang berbeda, maka itu bisa dilakukan - jadi dalam kasus Anda: tidak.

Tetapi jika tidak, Anda perlu menggunakan warisan untuk melakukan apa yang Anda cari.

Chris Brandsma
sumber
2

Sejauh yang saya tahu jawabannya adalah tidak, karena metode ekstensi bukanlah sebuah instance. Ini lebih seperti fasilitas intellisense bagi saya yang memungkinkan Anda memanggil metode statis menggunakan instance kelas. Saya rasa solusi untuk masalah Anda dapat berupa interseptor yang mencegat pelaksanaan metode tertentu (mis. GetHashCode ()) dan melakukan hal lain.Untuk menggunakan pencegat seperti itu (seperti yang disediakan Proyek Kastil) semua objek harus instansiated menggunakan pabrik objek (atau wadah IoC di Castle) sehingga antarmuka mereka dapat dicegat melalui proxy dinamis yang dihasilkan dalam runtime. (Caslte juga memungkinkan Anda mencegat anggota virtual kelas)

Beatles 1692
sumber
0

Saya telah menemukan cara untuk memanggil metode ekstensi dengan tanda tangan yang sama sebagai metode kelas, namun tampaknya tidak terlalu elegan. Saat bermain-main dengan metode ekstensi, saya melihat beberapa perilaku yang tidak terdokumentasi. Kode sampel:

public static class TestableExtensions
{
    public static string GetDesc(this ITestable ele)
    {
        return "Extension GetDesc";
    }

    public static void ValDesc(this ITestable ele, string choice)
    {
        if (choice == "ext def")
        {
            Console.WriteLine($"Base.Ext.Ext.GetDesc: {ele.GetDesc()}");
        }
        else if (choice == "ext base" && ele is BaseTest b)
        {
            Console.WriteLine($"Base.Ext.Base.GetDesc: {b.BaseFunc()}");
        }
    }

    public static string ExtFunc(this ITestable ele)
    {
        return ele.GetDesc();
    }

    public static void ExtAction(this ITestable ele, string choice)
    {
        ele.ValDesc(choice);
    }
}

public interface ITestable
{

}

public class BaseTest : ITestable
{
    public string GetDesc()
    {
        return "Base GetDesc";
    }

    public void ValDesc(string choice)
    {
        if (choice == "")
        {
            Console.WriteLine($"Base.GetDesc: {GetDesc()}");
        }
        else if (choice == "ext")
        {
            Console.WriteLine($"Base.Ext.GetDesc: {this.ExtFunc()}");
        }
        else
        {
            this.ExtAction(choice);
        }
    }

    public string BaseFunc()
    {
        return GetDesc();
    }
}

Apa yang saya perhatikan adalah jika saya memanggil metode kedua dari dalam metode ekstensi, itu akan memanggil metode ekstensi yang cocok dengan tanda tangan bahkan jika ada metode kelas yang juga cocok dengan tanda tangan. Misalnya dalam kode di atas, ketika saya memanggil ExtFunc (), yang pada gilirannya memanggil ele.GetDesc (), saya mendapatkan string kembali "Extension GetDesc" alih-alih string "Base GetDesc" yang kita harapkan.

Menguji kode:

var bt = new BaseTest();
bt.ValDesc("");
//Output is Base.GetDesc: Base GetDesc
bt.ValDesc("ext");
//Output is Base.Ext.GetDesc: Extension GetDesc
bt.ValDesc("ext def");
//Output is Base.Ext.Ext.GetDesc: Extension GetDesc
bt.ValDesc("ext base");
//Output is Base.Ext.Base.GetDesc: Base GetDesc

Hal ini memungkinkan Anda untuk bolak-balik antara metode kelas dan metode ekstensi sesuka hati, tetapi memerlukan penambahan metode "pass-through" duplikat untuk membawa Anda ke "cakupan" yang Anda inginkan. Saya menyebutnya ruang lingkup di sini karena kurangnya kata yang lebih baik. Mudah-mudahan seseorang bisa memberi tahu saya apa sebenarnya namanya.

Anda mungkin telah menebak dengan nama metode "pass-through" saya bahwa saya juga bermain-main dengan gagasan meneruskan delegasi kepada mereka dengan harapan bahwa satu atau dua metode dapat bertindak sebagai pass-through untuk beberapa metode dengan tanda tangan yang sama. Sayangnya itu tidak terjadi setelah delegasi diekstrak, ia selalu memilih metode kelas daripada metode ekstensi bahkan dari dalam metode ekstensi lain. "Cakupan" tidak lagi penting. Saya belum banyak menggunakan delegasi Action dan Func, jadi mungkin seseorang yang lebih berpengalaman dapat memahami bagian itu.

Lembab
sumber