Bisakah saya menambahkan metode ekstensi ke kelas statis yang ada?

535

Saya penggemar metode ekstensi di C #, tetapi belum berhasil menambahkan metode ekstensi ke kelas statis, seperti Konsol.

Misalnya, jika saya ingin menambahkan ekstensi ke Konsol, yang disebut 'WriteBlueLine', sehingga saya dapat membuka:

Console.WriteBlueLine("This text is blue");

Saya mencoba ini dengan menambahkan metode statis publik lokal, dengan Konsol sebagai parameter 'ini' ... tetapi tidak ada dadu!

public static class Helpers {
    public static void WriteBlueLine(this Console c, string text)
    {
        Console.ForegroundColor = ConsoleColor.Blue;
        Console.WriteLine(text);
        Console.ResetColor();
    }
}

Ini tidak menambahkan metode 'WriteBlueLine' ke Konsol ... apakah saya salah melakukannya? Atau meminta yang tidak mungkin?

Leon Bambrick
sumber
3
Baiklah. disayangkan tapi saya pikir saya akan bertahan. Saya MASIH masih perawan metode ekstensi (dalam kode produksi). Mungkin suatu hari, jika saya beruntung.
Andy McCloucher
Saya telah menulis sejumlah ekstensi HtmlHelper untuk ASP.NET MVC. Menulis satu untuk DateTime untuk memberi saya akhir dari tanggal yang diberikan (23: 59.59). Bermanfaat ketika Anda meminta pengguna untuk menentukan tanggal akhir, tetapi benar-benar ingin itu menjadi akhir hari itu.
tvanfosson
12
Tidak ada cara untuk menambahkannya saat ini karena fitur tidak ada di C #. Bukan karena tidak mungkin per se , tetapi karena C # peeps sangat sibuk, kebanyakan tertarik metode penyuluhan untuk membuat pekerjaan LINQ dan tidak melihat cukup manfaat dalam metode penyuluhan statis untuk membenarkan waktu mereka akan mengambil untuk melaksanakan. Eric Lippert menjelaskan di sini .
Jordan Grey
1
Panggil saja Helpers.WriteBlueLine(null, "Hi");:)
Hüseyin Yağlı

Jawaban:

286

Tidak. Metode ekstensi memerlukan variabel instan (nilai) untuk objek. Namun Anda dapat, menulis pembungkus statis di sekitar ConfigurationManagerantarmuka. Jika Anda menerapkan pembungkus, Anda tidak perlu metode ekstensi karena Anda bisa langsung menambahkan metode.

 public static class ConfigurationManagerWrapper
 {
      public static ConfigurationSection GetSection( string name )
      {
         return ConfigurationManager.GetSection( name );
      }

      .....

      public static ConfigurationSection GetWidgetSection()
      {
          return GetSection( "widgets" );
      }
 }
tvanfosson
sumber
8
@Luis - dalam konteksnya, idenya adalah "bisakah saya menambahkan metode ekstensi ke kelas ConfigurationManager untuk mendapatkan bagian tertentu?" Anda tidak bisa menambahkan metode ekstensi ke kelas statis karena membutuhkan instance objek, tetapi Anda bisa menulis kelas pembungkus (atau fasad) yang mengimplementasikan tanda tangan yang sama dan menolak panggilan aktual ke ConfigurationManager yang sebenarnya. Anda bisa menambahkan metode apa pun yang Anda inginkan ke kelas pembungkus sehingga tidak perlu menjadi ekstensi.
tvanfosson
Saya merasa lebih bermanfaat untuk hanya menambahkan metode statis ke kelas yang mengimplementasikan ConfigurationSection. Jadi diberi implementasi yang disebut MyConfigurationSection, saya akan memanggil MyConfigurationSection.GetSection (), yang mengembalikan bagian yang sudah diketik, atau nol jika tidak ada. Hasil akhirnya sama, tetapi tidak menambah kelas.
ketuk
1
@tap - ini hanya sebuah contoh, dan yang pertama muncul di benak saya. Prinsip tanggung jawab tunggal mulai berlaku. Haruskah "wadah" benar-benar bertanggung jawab untuk menafsirkan dirinya dari file konfigurasi? Biasanya saya hanya memiliki ConfigurationSectionHandler dan melemparkan output dari ConfigurationManager ke kelas yang sesuai dan tidak repot-repot dengan pembungkus.
tvanfosson
5
Untuk penggunaan dalam-rumah, saya mulai membuat varian 'X' kelas statis dan struktur untuk menambahkan Ekstensi kustom: 'ConsoleX' berisi metode statis baru untuk 'Console', 'MathX' berisi metode statis baru untuk 'Math', 'ColorX' memperluas metode 'Warna', dll. Tidak persis sama, tetapi mudah diingat dan ditemukan di IntelliSense.
user1689175
1
@ Xtro Saya setuju ini mengerikan, tetapi tidak lebih buruk daripada tidak dapat menggunakan tes ganda di tempatnya atau, lebih buruk lagi, menyerah pada pengujian kode Anda karena kelas statis membuatnya sangat sulit. Microsoft tampaknya setuju dengan saya karena itulah alasan mereka memperkenalkan kelas HttpContextWrapper / HttpContextBase untuk menyiasati HttpContext statis. Saat ini untuk MVC.
tvanfosson
92

Bisakah Anda menambahkan ekstensi statis ke kelas di C #? Tidak, tetapi Anda bisa melakukan ini:

public static class Extensions
{
    public static T Create<T>(this T @this)
        where T : class, new()
    {
        return Utility<T>.Create();
    }
}

public static class Utility<T>
    where T : class, new()
{
    static Utility()
    {
        Create = Expression.Lambda<Func<T>>(Expression.New(typeof(T).GetConstructor(Type.EmptyTypes))).Compile();
    }
    public static Func<T> Create { get; private set; }
}

Begini cara kerjanya. Meskipun Anda tidak dapat secara teknis menulis metode ekstensi statis, alih-alih kode ini mengeksploitasi celah dalam metode ekstensi. Celah itu adalah Anda dapat memanggil metode ekstensi pada objek nol tanpa mendapatkan pengecualian nol (kecuali Anda mengakses apa pun melalui @ ini).

Jadi, inilah cara Anda menggunakan ini:

    var ds1 = (null as DataSet).Create(); // as oppose to DataSet.Create()
    // or
    DataSet ds2 = null;
    ds2 = ds2.Create();

    // using some of the techniques above you could have this:
    (null as Console).WriteBlueLine(...); // as oppose to Console.WriteBlueLine(...)

Sekarang MENGAPA saya memilih memanggil konstruktor default sebagai contoh, dan DAN mengapa saya tidak mengembalikan T () baru dalam cuplikan kode pertama tanpa melakukan semua sampah Ekspresi? Baiklah hari ini hari keberuntunganmu karena kamu mendapatkan 2fer. Seperti diketahui oleh setiap pengembang .NET canggih, T () baru lambat karena menghasilkan panggilan ke System.Activator yang menggunakan refleksi untuk mendapatkan konstruktor default sebelum memanggilnya. Sialan kau, Microsoft! Namun kode saya memanggil konstruktor default objek secara langsung.

Ekstensi statis akan lebih baik dari ini, tetapi saat-saat putus asa membutuhkan tindakan putus asa.

Mr. Obnoxious
sumber
2
Saya pikir untuk Dataset, trik ini akan berhasil, tetapi saya ragu bahwa ini berfungsi untuk kelas Konsol karena Konsol adalah kelas statis, tipe statis tidak dapat digunakan sebagai argumen :)
ThomasBecker
Ya, saya akan mengatakan hal yang sama. Ini adalah metode ekstensi pseudo-statis pada kelas non-statis. OP adalah metode ekstensi pada kelas statis.
Mark A. Donohoe
2
Jauh lebih baik dan lebih mudah untuk hanya memiliki konvensi penamaan untuk metode seperti XConsole, ConsoleHelperdan sebagainya.
Alex Zhukovskiy
9
Ini adalah trik yang menarik, tetapi hasilnya berbau. Anda membuat objek nol, kemudian muncul untuk memanggil metode di atasnya - meskipun bertahun-tahun diberitahu bahwa "memanggil metode pada objek nol menyebabkan pengecualian". Berhasil, tapi ... wah ... Membingungkan untuk siapa pun yang memelihara nanti. Saya tidak akan mengundurkan diri, karena Anda telah menambah kumpulan informasi tentang apa yang mungkin. Tapi saya sangat berharap tidak ada yang pernah menggunakan teknik ini !! Keluhan tambahan: Jangan meneruskan salah satu dari ini ke metode, dan berharap untuk mendapatkan subklasifikasi OO: metode yang dipanggil akan menjadi tipe deklarasi parameter, bukan tipe parameter yang diteruskan .
ToolmakerSteve
5
Ini sulit, tetapi saya menyukainya. Salah satu alternatif untuk (null as DataSet).Create();bisa default(DataSet).Create();.
Bagerfahrer
54

Itu tidak mungkin.

Dan ya saya pikir MS melakukan kesalahan di sini.

Keputusan mereka tidak masuk akal dan memaksa programmer untuk menulis (seperti dijelaskan di atas) kelas pembungkus yang tidak berguna.

Berikut ini adalah contoh yang baik: Mencoba untuk memperpanjang kelas pengujian Unit MS statis. Tegaskan: Saya ingin 1 lagi metode tegas AreEqual(x1,x2).

Satu-satunya cara untuk melakukan ini adalah dengan menunjuk ke kelas yang berbeda atau menulis pembungkus sekitar 100-an metode Assert yang berbeda. Mengapa!?

Jika keputusan telah dibuat untuk mengizinkan ekstensi instance, saya tidak melihat alasan logis untuk tidak mengizinkan ekstensi statis. Argumen tentang pustaka bagian tidak berdiri setelah instance dapat diperpanjang.

Tom Deloford
sumber
20
Saya juga mencoba untuk memperluas kelas MS Unit Test Assert untuk menambahkan Assert.Throws dan Assert. Apakah Tidak dan menghadapi masalah yang sama.
Stefano Ricciardi
3
Ya saya juga :( Saya pikir saya bisa lakukan Assert.Throwsuntuk menjawab stackoverflow.com/questions/113395/...
CallMeLaNN
27

Saya tersandung pada utas ini ketika mencoba menemukan jawaban untuk pertanyaan yang sama dengan yang OP miliki. Saya tidak menemukan jawaban yang saya inginkan tetapi akhirnya saya melakukan ini.

public static class MyConsole
{
    public static void WriteLine(this ConsoleColor Color, string Text)
    {
        Console.ForegroundColor = Color;
        Console.WriteLine(Text);   
    }
}

Dan saya menggunakannya seperti ini:

ConsoleColor.Cyan.WriteLine("voilà");
Adel G.Eibesh
sumber
19

Mungkin Anda bisa menambahkan kelas statis dengan namespace kustom Anda dan nama kelas yang sama:

using CLRConsole = System.Console;

namespace ExtensionMethodsDemo
{
    public static class Console
    {
        public static void WriteLine(string value)
        {
            CLRConsole.WriteLine(value);
        }

        public static void WriteBlueLine(string value)
        {
            System.ConsoleColor currentColor = CLRConsole.ForegroundColor;

            CLRConsole.ForegroundColor = System.ConsoleColor.Blue;
            CLRConsole.WriteLine(value);

            CLRConsole.ForegroundColor = currentColor;
        }

        public static System.ConsoleKeyInfo ReadKey(bool intercept)
        {
            return CLRConsole.ReadKey(intercept);
        }
    }
    class Program
    {
        static void Main(string[] args)
        {
            try
            {
                Console.WriteBlueLine("This text is blue");   
            }
            catch (System.Exception ex)
            {
                Console.WriteLine(ex.Message);
                Console.WriteLine(ex.StackTrace);
            }

            Console.WriteLine("Press any key to continue...");
            Console.ReadKey(true);
        }
    }
}
Pag Sun
sumber
1
Tetapi ini tidak menyelesaikan masalah perlunya mengimplementasikan kembali setiap metode dari kelas statis asli yang ingin Anda simpan dalam pembungkus Anda. Ini masih sebuah pembungkus, meskipun memang perlu sedikit perubahan dalam kode yang menggunakannya ...
binki
11

Nggak. Definisi metode ekstensi memerlukan turunan jenis yang Anda perluas. Sangat disayangkan; Saya tidak yakin mengapa ini diperlukan ...


sumber
4
Itu karena metode ekstensi digunakan untuk memperluas instance objek. Jika mereka tidak melakukan itu, mereka hanya akan menjadi metode statis biasa.
Derek Ekins
31
Akan menyenangkan melakukan keduanya, bukan?
7

Adapun metode ekstensi, metode ekstensi itu sendiri statis; tetapi mereka dipanggil seolah-olah mereka adalah metode contoh. Karena kelas statis tidak instantiable, Anda tidak akan pernah memiliki instance dari kelas untuk memanggil metode ekstensi. Untuk alasan ini kompiler tidak mengijinkan metode ekstensi untuk didefinisikan untuk kelas statis.

Tn. Obnoxious menulis: "Seperti yang diketahui oleh pengembang .NET, T () baru lambat karena menghasilkan panggilan ke System.Activator yang menggunakan refleksi untuk mendapatkan konstruktor default sebelum memanggilnya".

New () dikompilasi dengan instruksi "newobj" IL jika jenisnya diketahui pada waktu kompilasi. Newobj mengambil konstruktor untuk doa langsung. Panggilan ke System.Activator.CreateInstance () kompilasi dengan instruksi "panggilan" IL untuk memanggil System.Activator.CreateInstance (). Baru () saat digunakan terhadap tipe generik akan menghasilkan panggilan ke System.Activator.CreateInstance (). Pos oleh Tn. Obnoxious tidak jelas tentang hal ini ... dan yah, menjengkelkan.

Kode ini:

System.Collections.ArrayList _al = new System.Collections.ArrayList();
System.Collections.ArrayList _al2 = (System.Collections.ArrayList)System.Activator.CreateInstance(typeof(System.Collections.ArrayList));

menghasilkan IL ini:

  .locals init ([0] class [mscorlib]System.Collections.ArrayList _al,
           [1] class [mscorlib]System.Collections.ArrayList _al2)
  IL_0001:  newobj     instance void [mscorlib]System.Collections.ArrayList::.ctor()
  IL_0006:  stloc.0
  IL_0007:  ldtoken    [mscorlib]System.Collections.ArrayList
  IL_000c:  call       class [mscorlib]System.Type [mscorlib]System.Type::GetTypeFromHandle(valuetype [mscorlib]System.RuntimeTypeHandle)
  IL_0011:  call       object [mscorlib]System.Activator::CreateInstance(class [mscorlib]System.Type)
  IL_0016:  castclass  [mscorlib]System.Collections.ArrayList
  IL_001b:  stloc.1
Brian Griffin
sumber
5

Anda tidak dapat menambahkan metode statis ke suatu tipe. Anda hanya bisa menambahkan metode instance (pseudo-) ke instance dari suatu tipe.

Tujuan dari thispengubah adalah untuk memberitahu compiler C # untuk melewatkan instance di sisi kiri .sebagai parameter pertama dari metode statis / ekstensi.

Dalam hal menambahkan metode statis ke suatu tipe, tidak ada instance untuk melewatkan parameter pertama.

Brannon
sumber
4

Saya mencoba melakukan ini dengan System.Environment ketika saya belajar metode ekstensi dan tidak berhasil. Alasannya, seperti yang disebutkan orang lain, karena metode ekstensi memerlukan turunan kelas.

Robert S.
sumber
3

Tidak mungkin untuk menulis metode ekstensi, namun dimungkinkan untuk meniru perilaku yang Anda minta.

using FooConsole = System.Console;

public static class Console
{
    public static void WriteBlueLine(string text)
    {
        FooConsole.ForegroundColor = ConsoleColor.Blue;
        FooConsole.WriteLine(text);
        FooConsole.ResetColor();
    }
}

Ini akan memungkinkan Anda untuk memanggil Console.WriteBlueLine (fooText) di kelas lain. Jika kelas lain menginginkan akses ke fungsi statis lain dari Konsol, mereka harus secara eksplisit dirujuk melalui namespace mereka.

Anda selalu dapat menambahkan semua metode ke kelas pengganti jika Anda ingin memiliki semuanya di satu tempat.

Jadi, Anda akan memiliki sesuatu seperti

using FooConsole = System.Console;

public static class Console
{
    public static void WriteBlueLine(string text)
    {
        FooConsole.ForegroundColor = ConsoleColor.Blue;
        FooConsole.WriteLine(text);
        FooConsole.ResetColor();
    }
    public static void WriteLine(string text)
    {
        FooConsole.WriteLine(text);
    }
...etc.
}

Ini akan memberikan jenis perilaku yang Anda cari.

* Catatan Konsol harus ditambahkan melalui namespace yang Anda masukkan.

Douglas Potesta
sumber
1

ya, dalam arti terbatas.

public class DataSet : System.Data.DataSet
{
    public static void SpecialMethod() { }
}

Ini berfungsi tetapi Konsol tidak karena itu statis.

public static class Console
{       
    public static void WriteLine(String x)
    { System.Console.WriteLine(x); }

    public static void WriteBlueLine(String x)
    {
        System.Console.ForegroundColor = ConsoleColor.Blue;
        System.Console.Write(.x);           
    }
}

Ini berfungsi karena selama itu tidak ada di namespace yang sama. Masalahnya adalah Anda harus menulis metode proxy statis untuk setiap metode yang dimiliki System.Console. Ini tidak selalu merupakan hal yang buruk karena Anda dapat menambahkan sesuatu seperti ini:

    public static void WriteLine(String x)
    { System.Console.WriteLine(x.Replace("Fck","****")); }

atau

 public static void WriteLine(String x)
    {
        System.Console.ForegroundColor = ConsoleColor.Blue;
        System.Console.WriteLine(x); 
    }

Cara kerjanya adalah Anda menghubungkan sesuatu ke WriteLine standar. Itu bisa berupa jumlah baris atau filter kata yang buruk atau apa pun. Setiap kali Anda hanya menentukan Konsol di namespace Anda mengatakan WebProject1 dan mengimpor Sistem namespace, WebProject1.Console akan dipilih daripada System.Console sebagai default untuk kelas-kelas di namespace WebProject1. Jadi kode ini akan mengubah semua panggilan Console.WriteLine menjadi biru sejauh Anda tidak pernah menentukan System.Console.WriteLine.

Anjing hitam
sumber
sayangnya pendekatan menggunakan turunan tidak bekerja ketika kelas dasar disegel (seperti banyak di perpustakaan kelas .NET)
George Birbilis
1

Berikut ini ditolak sebagai hasil edit dari jawaban tvanfosson. Saya diminta untuk berkontribusi sebagai jawaban saya sendiri. Saya menggunakan sarannya dan menyelesaikan implementasi ConfigurationManagerpembungkus. Pada prinsipnya saya hanya mengisi jawaban ...in tvanfosson.

Tidak. Metode ekstensi membutuhkan turunan objek. Namun Anda dapat, menulis pembungkus statis di sekitar antarmuka ConfigurationManager. Jika Anda menerapkan pembungkus, Anda tidak perlu metode ekstensi karena Anda bisa langsung menambahkan metode.

public static class ConfigurationManagerWrapper
{
    public static NameValueCollection AppSettings
    {
        get { return ConfigurationManager.AppSettings; }
    }

    public static ConnectionStringSettingsCollection ConnectionStrings
    {
        get { return ConfigurationManager.ConnectionStrings; }
    }

    public static object GetSection(string sectionName)
    {
        return ConfigurationManager.GetSection(sectionName);
    }

    public static Configuration OpenExeConfiguration(string exePath)
    {
        return ConfigurationManager.OpenExeConfiguration(exePath);
    }

    public static Configuration OpenMachineConfiguration()
    {
        return ConfigurationManager.OpenMachineConfiguration();
    }

    public static Configuration OpenMappedExeConfiguration(ExeConfigurationFileMap fileMap, ConfigurationUserLevel userLevel)
    {
        return ConfigurationManager.OpenMappedExeConfiguration(fileMap, userLevel);
    }

    public static Configuration OpenMappedMachineConfiguration(ConfigurationFileMap fileMap)
    {
        return ConfigurationManager.OpenMappedMachineConfiguration(fileMap);
    }

    public static void RefreshSection(string sectionName)
    {
        ConfigurationManager.RefreshSection(sectionName);
    }
}
André C. Andersen
sumber
0

Anda dapat menggunakan gips pada null untuk membuatnya berfungsi.

public static class YoutTypeExtensionExample
{
    public static void Example()
    {
        ((YourType)null).ExtensionMethod();
    }
}

Ekstensi:

public static class YourTypeExtension
{
    public static void ExtensionMethod(this YourType x) { }
}

Tipe kamu:

public class YourType { }
Wouter
sumber
-4

Anda BISA melakukan ini jika Anda bersedia "menikmatinya" sedikit dengan membuat variabel dari kelas statis dan menetapkannya ke nol. Namun, metode ini tidak akan tersedia untuk panggilan statis di kelas, jadi tidak yakin berapa banyak menggunakannya:

Console myConsole = null;
myConsole.WriteBlueLine("my blue line");

public static class Helpers {
    public static void WriteBlueLine(this Console c, string text)
    {
        Console.ForegroundColor = ConsoleColor.Blue;
        Console.WriteLine(text);
        Console.ResetColor();
    }
}
Tenaka
sumber
ini persis apa yang saya lakukan. Kelas saya disebut MyTrace :)
Gishu
Tip yang bermanfaat. sedikit bau kode, tapi saya kira kita bisa menyembunyikan objek nol di kelas dasar atau sesuatu. Terima kasih.
Tom Deloford
1
Saya tidak dapat mengkompilasi kode ini. Kesalahan 'System.Console': tipe statis tidak dapat digunakan sebagai parameter
kuncevic.dev
Ya ini tidak bisa dilakukan. Sial, saya pikir Anda ke sesuatu di sana! Tipe statis tidak dapat diteruskan sebagai parameter ke dalam metode yang masuk akal. Semoga saja MS melihat kayu dari pohon yang satu ini dan mengubahnya.
Tom Deloford
3
Saya seharusnya mencoba mengkompilasi kode saya sendiri! Seperti kata Tom, ini tidak akan bekerja dengan kelas statis.
Tenaka