Bagikan melalui


Anggota ekstensi (Panduan Pemrograman C#)

Anggota ekstensi memungkinkan Anda untuk "menambahkan" metode ke jenis yang ada tanpa membuat jenis turunan baru, kompilasi ulang, atau memodifikasi jenis asli.

Dimulai dengan C# 14, ada dua sintaks yang Anda gunakan untuk menentukan metode ekstensi. C# 14 menambahkan extension kontainer, tempat Anda menentukan beberapa anggota ekstensi untuk jenis atau instans jenis. Sebelum C# 14, Anda menambahkan pengubah this ke parameter pertama metode statis untuk menunjukkan bahwa metode muncul sebagai anggota instans jenis parameter.

Metode ekstensi adalah metode statis, tetapi dipanggil seolah-olah metode instans pada jenis yang diperluas. Untuk kode klien yang ditulis dalam C#, F# dan Visual Basic, tidak ada perbedaan yang jelas antara memanggil metode ekstensi dan metode yang ditentukan dalam jenis. Kedua bentuk metode ekstensi dikompilasi ke IL (Bahasa Perantara) yang sama. Konsumen anggota ekstensi tidak perlu mengetahui sintaks mana yang digunakan untuk menentukan metode ekstensi.

Anggota ekstensi yang paling umum adalah operator kueri standar LINQ yang menambahkan fungsionalitas kueri ke jenis System.Collections.IEnumerable dan System.Collections.Generic.IEnumerable<T> yang sudah ada. Untuk menggunakan operator kueri standar, pertama-tama bawa ke dalam cakupan dengan perintah using System.Linq. Kemudian jenis apa pun yang menerapkan IEnumerable<T> tampaknya memiliki metode instans seperti GroupBy, , OrderByAverage, dan sebagainya. Anda dapat melihat metode tambahan ini dalam penyelesaian pernyataan IntelliSense saat Anda mengetik "titik" setelah instans jenis IEnumerable<T> seperti List<T> atau Array.

Contoh Pembagian Urutan

Contoh berikut menunjukkan cara memanggil metode operator OrderBy kueri standar pada array bilangan bulat. Ekspresi dalam tanda kurung adalah ekspresi lambda. Banyak operator kueri standar mengambil ekspresi lambda sebagai parameter. Untuk informasi selengkapnya, lihat Ekspresi Lambda.

int[] numbers = [10, 45, 15, 39, 21, 26];
IOrderedEnumerable<int> result = numbers.OrderBy(g => g);
foreach (int i in result)
{
    Console.Write(i + " ");
}
//Output: 10 15 21 26 39 45

Metode ekstensi didefinisikan sebagai metode statis tetapi dipanggil dengan menggunakan sintaks metode instans. Parameter pertamanya menentukan jenis metode yang dioperasikan. Parameter mengikuti pengubah ini . Metode ekstensi hanya berada dalam cakupan saat Anda secara eksplisit mengimpor namespace ke dalam kode sumber Anda dengan perintah using.

Mendeklarasikan anggota modul ekstensi

Dimulai dengan C# 14, Anda dapat mendeklarasikan blok ekstensi. Blok ekstensi adalah blok dalam kelas statis tidak bertingkat, non-generik, yang berisi anggota ekstensi untuk tipe atau instans dari tipe tersebut. Contoh kode berikut mendefinisikan blok ekstensi untuk jenis tersebut string . Blok ekstensi berisi satu anggota: metode yang menghitung kata-kata dalam string:

namespace CustomExtensionMembers;

public static class MyExtensions
{
    extension(string str)
    {
        public int WordCount() =>
            str.Split([' ', '.', '?'], StringSplitOptions.RemoveEmptyEntries).Length;
    }
}

Sebelum C# 14, Anda mendeklarasikan metode ekstensi dengan menambahkan this pengubah ke parameter pertama:

namespace CustomExtensionMethods;

public static class MyExtensions
{
    public static int WordCount(this string str) =>
        str.Split([' ', '.', '?'], StringSplitOptions.RemoveEmptyEntries).Length;
}

Kedua bentuk ekstensi harus didefinisikan di dalam kelas statis yang tidak bertingkat dan tidak generik.

Dan dapat dipanggil dari aplikasi dengan menggunakan sintaks untuk mengakses anggota instance:

string s = "Hello Extension Methods";
int i = s.WordCount();

Meskipun anggota ekstensi menambahkan kemampuan baru ke jenis yang ada, anggota ekstensi tidak melanggar prinsip enkapsulasi. Deklarasi akses untuk semua anggota tipe yang diperluas diterapkan pada anggota ekstensi.

MyExtensions Kelas dan WordCount metode adalah static, dan dapat diakses seperti anggota lainnya static. Metode WordCount ini dapat dipanggil seperti metode lain static sebagai berikut:

string s = "Hello Extension Methods";
int i = MyExtensions.WordCount(s);

Kode C# sebelumnya berlaku untuk blok ekstensi dan sintaks anggota ekstensi this. Kode sebelumnya:

  • Mendeklarasikan dan menginisialisasi string baru bernama s dengan nilai "Hello Extension Methods".
  • Memanggil MyExtensions.WordCount dengan argumen s yang diberikan.

Untuk informasi selengkapnya, lihat Cara menerapkan dan memanggil metode ekstensi kustom.

Secara umum, Anda mungkin lebih sering memanggil anggota ekstensi daripada mengimplementasikannya. Karena anggota ekstensi dipanggil seolah-olah mereka dinyatakan sebagai anggota kelas yang diperluas, tidak ada pengetahuan khusus yang diperlukan untuk menggunakannya dari kode klien. Untuk mengaktifkan anggota ekstensi untuk jenis tertentu, cukup tambahkan using direktif untuk namespace tempat metode ditentukan. Misalnya, untuk menggunakan operator kueri standar, tambahkan direktif ini using ke kode Anda:

using System.Linq;

Mengikat anggota ekstensi pada waktu kompilasi

Anda dapat menggunakan anggota ekstensi untuk memperluas kelas atau antarmuka, tetapi tidak untuk mengambil alih perilaku yang ditentukan dalam kelas. Anggota ekstensi dengan nama dan tanda tangan yang sama dengan antarmuka atau anggota kelas tidak pernah dipanggil. Pada waktu kompilasi, anggota ekstensi selalu memiliki prioritas yang lebih rendah daripada anggota instans (atau statis) yang ditentukan dalam jenis itu sendiri. Dengan kata lain, jika suatu jenis memiliki metode bernama Process(int i), dan Anda memiliki metode ekstensi dengan tanda tangan yang sama, pengkompilasi akan selalu merujuk ke metode bawaan dari jenis tersebut. Ketika kompilator menemui pemanggilan anggota, ia pertama-tama mencari kecocokan di anggota tipe tersebut. Jika tidak ada kecocokan yang ditemukan, ia mencari anggota ekstensi apa pun yang ditentukan untuk jenis tersebut. Ia mengikat pada anggota ekstensi pertama yang ditemukannya. Contoh berikut menunjukkan aturan yang diikuti pengkompilasi C# dalam menentukan apakah akan mengikat anggota instans pada jenis tersebut, atau ke anggota ekstensi. Kelas statis Extensions berisi anggota ekstensi yang ditentukan untuk semua jenis yang mengimplementasikan IMyInterface:

public interface IMyInterface
{
    void MethodB();
}

// Define extension methods for IMyInterface.

// The following extension methods can be accessed by instances of any
// class that implements IMyInterface.
public static class Extension
{
    public static void MethodA(this IMyInterface myInterface, int i) =>
        Console.WriteLine("Extension.MethodA(this IMyInterface myInterface, int i)");

    public static void MethodA(this IMyInterface myInterface, string s) =>
        Console.WriteLine("Extension.MethodA(this IMyInterface myInterface, string s)");

    // This method is never called in ExtensionMethodsDemo1, because each
    // of the three classes A, B, and C implements a method named MethodB
    // that has a matching signature.
    public static void MethodB(this IMyInterface myInterface) =>
        Console.WriteLine("Extension.MethodB(this IMyInterface myInterface)");
}

Ekstensi yang setara dapat dinyatakan menggunakan sintaks anggota ekstensi C# 14:

public static class Extension
{
    extension(IMyInterface myInterface)
    {
        public void MethodA(int i) =>
            Console.WriteLine("Extension.MethodA(this IMyInterface myInterface, int i)");

        public void MethodA(string s) =>
            Console.WriteLine("Extension.MethodA(this IMyInterface myInterface, string s)");

        // This method is never called in ExtensionMethodsDemo1, because each
        // of the three classes A, B, and C implements a method named MethodB
        // that has a matching signature.
        public void MethodB() =>
            Console.WriteLine("Extension.MethodB(this IMyInterface myInterface)");
    }
}

A, B, dan C semua mengimplementasikan antarmuka tersebut.

// Define three classes that implement IMyInterface, and then use them to test
// the extension methods.
class A : IMyInterface
{
    public void MethodB() { Console.WriteLine("A.MethodB()"); }
}

class B : IMyInterface
{
    public void MethodB() { Console.WriteLine("B.MethodB()"); }
    public void MethodA(int i) { Console.WriteLine("B.MethodA(int i)"); }
}

class C : IMyInterface
{
    public void MethodB() { Console.WriteLine("C.MethodB()"); }
    public void MethodA(object obj)
    {
        Console.WriteLine("C.MethodA(object obj)");
    }
}

Metode MethodB ekstensi tidak pernah dipanggil karena nama dan tanda tangannya sama persis dengan metode yang sudah diterapkan oleh kelas. Ketika pengkompilasi tidak dapat menemukan metode instans dengan tanda tangan yang cocok, ia mengikat ke metode ekstensi yang cocok jika ada.

// Declare an instance of class A, class B, and class C.
A a = new A();
B b = new B();
C c = new C();

// For a, b, and c, call the following methods:
//      -- MethodA with an int argument
//      -- MethodA with a string argument
//      -- MethodB with no argument.

// A contains no MethodA, so each call to MethodA resolves to
// the extension method that has a matching signature.
a.MethodA(1);           // Extension.MethodA(IMyInterface, int)
a.MethodA("hello");     // Extension.MethodA(IMyInterface, string)

// A has a method that matches the signature of the following call
// to MethodB.
a.MethodB();            // A.MethodB()

// B has methods that match the signatures of the following
// method calls.
b.MethodA(1);           // B.MethodA(int)
b.MethodB();            // B.MethodB()

// B has no matching method for the following call, but
// class Extension does.
b.MethodA("hello");     // Extension.MethodA(IMyInterface, string)

// C contains an instance method that matches each of the following
// method calls.
c.MethodA(1);           // C.MethodA(object)
c.MethodA("hello");     // C.MethodA(object)
c.MethodB();            // C.MethodB()
/* Output:
    Extension.MethodA(this IMyInterface myInterface, int i)
    Extension.MethodA(this IMyInterface myInterface, string s)
    A.MethodB()
    B.MethodA(int i)
    B.MethodB()
    Extension.MethodA(this IMyInterface myInterface, string s)
    C.MethodA(object obj)
    C.MethodA(object obj)
    C.MethodB()
 */

Pola penggunaan umum

Fungsionalitas Pengumpulan

Di masa lalu, umum untuk membuat "Kelas Koleksi" yang mengimplementasikan antarmuka System.Collections.Generic.IEnumerable<T> untuk tipe tertentu dan memiliki fungsionalitas yang bertindak pada koleksi tipe tersebut. Meskipun tidak ada yang salah dengan membuat jenis objek koleksi ini, fungsionalitas yang sama dapat dicapai dengan menggunakan ekstensi pada System.Collections.Generic.IEnumerable<T>. Ekstensi memiliki keuntungan memungkinkan fungsionalitas dipanggil dari koleksi apa pun seperti System.Array atau System.Collections.Generic.List<T> yang menerapkan System.Collections.Generic.IEnumerable<T> pada jenis tersebut. Contoh ini menggunakan Array Int32 dapat ditemukan sebelumnya di artikel ini.

Layer-Specific Fungsionalitas

Saat menggunakan Arsitektur Bawang atau desain aplikasi berlapis lainnya, umumnya memiliki sekumpulan Entitas Domain atau Objek Transfer Data yang dapat digunakan untuk berkomunikasi di seluruh batas aplikasi. Objek ini umumnya tidak berisi fungsionalitas, atau hanya fungsionalitas minimal yang berlaku untuk semua lapisan aplikasi. Metode ekstensi dapat digunakan untuk menambahkan fungsionalitas yang khusus untuk setiap lapisan aplikasi.

public class DomainEntity
{
    public int Id { get; set; }
    public required string FirstName { get; set; }
    public required string LastName { get; set; }
}

static class DomainEntityExtensions
{
    static string FullName(this DomainEntity value)
        => $"{value.FirstName} {value.LastName}";
}

Anda dapat mendeklarasikan properti yang setara FullName di C# 14 dan yang lebih baru menggunakan sintaks blok ekstensi baru:

static class DomainEntityExtensions
{
    extension(DomainEntity value)
    {
        string FullName => $"{value.FirstName} {value.LastName}";
    }
}

Memperluas Jenis yang Telah Ditentukan Sebelumnya

Daripada membuat objek baru saat fungsionalitas yang dapat digunakan kembali perlu dibuat, Anda sering dapat memperluas jenis yang ada, seperti jenis .NET atau CLR. Sebagai contoh, jika Anda tidak menggunakan metode ekstensi, Anda dapat membuat Engine kelas atau Query untuk melakukan pekerjaan menjalankan kueri di SQL Server yang mungkin dipanggil dari beberapa tempat dalam kode kami. Namun Anda dapat memperluas kelas menggunakan metode ekstensi untuk melakukan kueri tersebut System.Data.SqlClient.SqlConnection dari mana saja Anda memiliki koneksi ke SQL Server. Contoh lain mungkin adalah menambahkan fungsionalitas umum ke System.String kelas, memperluas kemampuan System.IO.Stream pemrosesan data objek, dan System.Exception objek untuk fungsionalitas penanganan kesalahan tertentu. Jenis kasus penggunaan ini hanya dibatasi oleh imajinasi dan akal sehat Anda.

Memperluas jenis yang telah ditentukan sebelumnya bisa sulit dengan struct karena dikirimkan sebagai nilai ke metode. Itu berarti setiap perubahan pada struktur dilakukan pada salinan struktur. Perubahan tersebut tidak terlihat setelah metode ekstensi keluar. Anda dapat menambahkan pengubah ref ke argumen pertama, yang akan membuatnya menjadi metode ekstensi. Kata ref kunci dapat muncul sebelum atau sesudah this kata kunci tanpa perbedaan semantik. Menambahkan pengubah ref menunjukkan bahwa argumen pertama diteruskan oleh referensi. Teknik ini memungkinkan Anda menulis metode ekstensi yang mengubah status struktur yang diperluas (perhatikan bahwa anggota privat tidak dapat diakses). Hanya jenis nilai atau jenis generik yang dibatasi untuk dibuat (Untuk informasi selengkapnya tentang aturan ini, lihat struct batasan untuk informasi selengkapnya) yang diizinkan sebagai parameter pertama metode ref ekstensi atau sebagai penerima blok ekstensi. Contoh berikut menunjukkan cara menggunakan ref metode ekstensi untuk memodifikasi jenis bawaan secara langsung tanpa perlu menetapkan ulang hasilnya atau meneruskannya melalui fungsi dengan ref kata kunci:

public static class IntExtensions
{
    public static void Increment(this int number)
        => number++;

    // Take note of the extra ref keyword here
    public static void RefIncrement(this ref int number)
        => number++;
}

Blok ekstensi yang setara ditampilkan dalam kode berikut:

public static class IntExtensions
{
    extension(int number)
    {
        public void Increment()
            => number++;
    }

    // Take note of the extra ref keyword here
    extension(ref int number)
    {
        public void RefIncrement()
            => number++;
    }
}

Blok ekstensi yang berbeda diperlukan untuk membedakan mode parameter by-value dan by-ref untuk penerima.

Anda dapat melihat perbedaan yang ditimbulkan ref pada penerima dalam contoh berikut:

int x = 1;

// Takes x by value leading to the extension method
// Increment modifying its own copy, leaving x unchanged
x.Increment();
Console.WriteLine($"x is now {x}"); // x is now 1

// Takes x by reference leading to the extension method
// RefIncrement changing the value of x directly
x.RefIncrement();
Console.WriteLine($"x is now {x}"); // x is now 2

Anda dapat menerapkan teknik yang sama dengan menambahkan ref anggota ekstensi ke tipe struct yang didefinisikan pengguna.

public struct Account
{
    public uint id;
    public float balance;

    private int secret;
}

public static class AccountExtensions
{
    // ref keyword can also appear before the this keyword
    public static void Deposit(ref this Account account, float amount)
    {
        account.balance += amount;

        // The following line results in an error as an extension
        // method is not allowed to access private members
        // account.secret = 1; // CS0122
    }
}

Sampel sebelumnya juga dapat dibuat menggunakan blok ekstensi di C# 14:

public static class AccountExtensions
{
    extension(ref Account account)
    {
        // ref keyword can also appear before the this keyword
        public void Deposit(float amount)
        {
            account.balance += amount;

            // The following line results in an error as an extension
            // method is not allowed to access private members
            // account.secret = 1; // CS0122
        }
    }
}

Anda dapat mengakses metode ekstensi ini sebagai berikut:

Account account = new()
{
    id = 1,
    balance = 100f
};

Console.WriteLine($"I have ${account.balance}"); // I have $100

account.Deposit(50f);
Console.WriteLine($"I have ${account.balance}"); // I have $150

Pedoman Umum

Lebih baik menambahkan fungsionalitas dengan memodifikasi kode objek atau mendapatkan jenis baru setiap kali masuk akal dan mungkin untuk melakukannya. Metode ekstensi adalah opsi penting untuk membuat fungsionalitas yang dapat digunakan kembali di seluruh ekosistem .NET. Anggota ekstensi lebih disukai ketika sumber asli tidak berada di bawah kontrol Anda, ketika objek turunan tidak pantas atau tidak mungkin, atau ketika fungsionalitas memiliki cakupan terbatas.

Untuk informasi selengkapnya tentang jenis turunan, lihat Warisan.

Jika Anda menerapkan metode ekstensi untuk jenis tertentu, ingat poin-poin berikut:

  • Metode ekstensi tidak dipanggil jika memiliki tanda tangan yang sama dengan metode yang ditentukan dalam jenis .
  • Metode ekstensi dibawa ke dalam cakupan di tingkat namespace. Misalnya, jika Anda memiliki beberapa kelas statis yang berisi metode ekstensi dalam satu namespace bernama Extensions, semuanya dibawa ke dalam cakupan oleh direktif using Extensions; .

Untuk perpustakaan kelas yang Anda terapkan, Anda sebaiknya tidak menggunakan metode ekstensi agar tidak menaikkan nomor versi rakitan. Jika Anda ingin menambahkan fungsionalitas signifikan ke pustaka tempat Anda memiliki kode sumber, ikuti panduan .NET untuk penerapan versi rakitan. Untuk informasi selengkapnya, lihat Penerapan Versi Rakitan.

Lihat juga