Sintaks apa yang paling halus dan paling menarik yang Anda temukan untuk menegaskan kebenaran parameter di c#?

Masalah umum dalam bahasa apa pun adalah menyatakan bahwa parameter yang dikirim ke suatu metode memenuhi persyaratan Anda, dan jika tidak, akan mengirimkan pesan kesalahan yang bagus dan informatif. Kode semacam ini diulangi berulang kali, dan kami sering mencoba membuat pembantu untuk kode tersebut. Namun, dalam C#, tampaknya para pembantu tersebut dipaksa untuk menangani beberapa duplikasi yang dipaksakan kepada kita oleh bahasa dan kompiler. Untuk menunjukkan apa yang saya maksud, izinkan saya menyajikan beberapa kode mentah tanpa pembantu, diikuti oleh satu kemungkinan pembantu. Kemudian, saya akan menunjukkan duplikasi di helper dan mengutarakan pertanyaan saya dengan tepat.

Pertama, kode tanpa bantuan apa pun:

public void SomeMethod(string firstName, string lastName, int age)
{
     if(firstName == null)
     {
          throw new WhateverException("The value for firstName cannot be null.");
     }

     if(lastName == null)
     {
          throw new WhateverException("The value for lastName cannot be null.");
     }

     // Same kind of code for age, making sure it is a reasonable range (< 150, for example).
     // You get the idea
}

}

Sekarang, kode dengan upaya yang masuk akal sebagai pembantu:

public void SomeMethod(string firstName, string lastName, int age)
{
      Helper.Validate( x=> x !=null, "firstName", firstName);
      Helper.Validate( x=> x!= null, "lastName", lastName);
}

Pertanyaan utamanya adalah ini: Perhatikan bagaimana kode harus meneruskan nilai parameter dan nama parameter ("firstName" dan nama depan). Hal ini agar pesan kesalahan dapat mengatakan, "Blah bla bla nilai parameter firstName." Sudahkah Anda menemukan cara untuk menyiasatinya menggunakan refleksi atau apa pun? Atau cara untuk mengurangi rasa sakitnya?

Dan yang lebih umum, apakah Anda menemukan cara lain untuk menyederhanakan tugas memvalidasi parameter sekaligus mengurangi duplikasi kode?

EDIT: Saya telah membaca orang berbicara tentang memanfaatkan properti Parameter, tetapi tidak pernah menemukan cara untuk mengatasi duplikasi tersebut. Adakah yang beruntung dengan itu?

Terima kasih!


person Charlie Flowers    schedule 21.03.2009    source sumber
comment
Penggunaan kelas ApplicationException tidak disarankan.   -  person mmx    schedule 21.03.2009
comment
Oke, saya mengubahnya menjadi Exception, hanya untuk menghindari gangguan apa pun. Selain itu, jika rekomendasi tersebut didasarkan pada komentar dari orang-orang yang menulis buku Pedoman Kerangka Kerja, maka saya tidak terlalu yakin (walaupun orang-orang tersebut telah menulis beberapa hal yang bagus). Tapi itu diskusi untuk hari lain.   -  person Charlie Flowers    schedule 21.03.2009
comment
Henk: Salah. Setiap penggunaan ApplicationException tidak disarankan. Lihat bagian Keterangan di sini, khususnya paragraf kedua: msdn.microsoft.com /en-us/library/   -  person Ryan Lundy    schedule 14.12.2009
comment
@Kyralessa jika itu satu-satunya kutipan untuk ini, itu adalah argumen yang tidak perlu dibuat. Tidak ada satu alasan substantif yang dibuat tentang mengapa Anda tidak boleh menggunakan pengecualian aplikasi di bagian komentar itu. Bahkan secara langsung dikatakan Awalnya diperkirakan bahwa pengecualian khusus harus berasal dari kelas ApplicationException; namun dalam praktiknya hal ini belum ditemukan memberikan nilai tambah yang signifikan. jadi pada dasarnya MS mengakui bahwa kelas ApplicationException tidak diperlukan, bukan berarti buruk untuk digunakan.   -  person Chris Marisic    schedule 16.03.2011


Jawaban (14)


Ini mungkin sedikit membantu:

Desain berdasarkan kontrak/C# 4.0/avoiding ArgumentNullException

person core    schedule 21.03.2009
comment
Terima kasih! Hal ini menghasilkan 2 pendekatan yang membuat saya terpesona! Dan saya bisa melihat menggunakan keduanya. - person Charlie Flowers; 22.03.2009
comment
Penerapan validasi lancar itu cukup apik ya. - person core; 22.03.2009
comment
Ya, itu sangat licin. Saya tidak menyadari bahwa Anda dapat memanggil metode ekstensi pada variabel yang nol. Saya tidak tahu apakah Anders bermaksud agar hal itu terjadi atau apakah itu merupakan efek samping. Apa pun itu, ini sangat membantu. - person Charlie Flowers; 22.03.2009
comment
Charlie, ya, aku terus berpikir, Oke, itu tidak benar. Bagaimana dengan NullReferenceException? sampai saya menggulir ke bawah untuk melihat metode ekstensi. Kesepakatan keren. - person core; 23.03.2009
comment
Tanggapan ini paling mengajari saya. Harus memberikan kredit pada saat kredit jatuh tempo. Terima kasih kepada semuanya atas tanggapan luar biasa! - person Charlie Flowers; 24.03.2009

Anda harus memeriksa Kontrak Kode; mereka melakukan persis seperti yang Anda minta. Contoh:

[Pure]
public static double GetDistance(Point p1, Point p2)
{
    CodeContract.RequiresAlways(p1 != null);
    CodeContract.RequiresAlways(p2 != null); 
    // ...
}
person John Feminella    schedule 21.03.2009
comment
Hei, keren... apalagi itu akan dibangun ke dalam bahasa di c# 4.0. Tapi tetap saja... apa yang bisa kita lakukan sementara ini? - person Charlie Flowers; 21.03.2009
comment
Ya, mereka akan dipanggang di .NET 4, tapi Anda masih bisa menggunakannya sekarang. Mereka tidak bergantung pada apa pun yang spesifik untuk .NET 4, jadi Anda dapat kembali ke 2.0 jika Anda mau. Proyeknya ada di sini: research.microsoft.com/en-us/projects/contracts - person John Feminella; 21.03.2009
comment
Anda dapat mengerjakan kelas Anda sendiri yang terlihat seperti itu, tetapi berikan pengecualian untuk perpustakaan. Cukup sederhana untuk menulisnya. - person user7116; 22.03.2009
comment
Namun pernahkah Anda mencoba menggunakan kontrak kode pada beberapa proyek besar? Sayangnya tidak dapat digunakan, waktu kompilasi bertambah terlalu banyak. - person Mic; 10.08.2012

Wow, saya menemukan sesuatu yang sangat menarik di sini. Chris di atas memberikan tautan ke pertanyaan Stack Overflow lainnya. Salah satu jawaban di sana menunjuk ke postingan blog yang menjelaskan cara mendapatkan kode seperti ini:

public static void Copy<T>(T[] dst, long dstOffset, T[] src, long srcOffset, long length)
{
    Validate.Begin()
            .IsNotNull(dst, “dst”)
            .IsNotNull(src, “src”)
            .Check()
            .IsPositive(length)
            .IsIndexInRange(dst, dstOffset, “dstOffset”)
            .IsIndexInRange(dst, dstOffset + length, “dstOffset + length”)
            .IsIndexInRange(src, srcOffset, “srcOffset”)
            .IsIndexInRange(src, srcOffset + length, “srcOffset + length”)
            .Check();

    for (int di = dstOffset; di < dstOffset + length; ++di)
        dst[di] = src[di - dstOffset + srcOffset];
}

Saya belum yakin ini adalah jawaban yang terbaik, namun tentu saja ini menarik. Berikut postingan blognya , dari Rick Brewster.

person Charlie Flowers    schedule 22.03.2009

Saya mengatasi masalah ini beberapa minggu yang lalu, setelah berpikir bahwa aneh bagaimana perpustakaan pengujian tampaknya memerlukan jutaan versi Assert yang berbeda untuk membuat pesan mereka deskriptif.

Inilah solusi saya.

Ringkasan singkat - diberikan sedikit kode ini:

int x = 3;
string t = "hi";
Assert(() => 5*x + (2 / t.Length) < 99);

Fungsi Assert saya dapat mencetak ringkasan berikut tentang apa yang diteruskan ke dalamnya:

(((5 * x) + (2 / t.Length)) < 99) == True where
{
  ((5 * x) + (2 / t.Length)) == 16 where
  {
    (5 * x) == 15 where
    {
      x == 3
    }
    (2 / t.Length) == 1 where
    {
      t.Length == 2 where
      {
        t == "hi"
      }
    }
  }
}

Jadi semua nama dan nilai pengidentifikasi, serta struktur ekspresi, dapat disertakan dalam pesan pengecualian, tanpa Anda harus menyatakannya kembali dalam string yang dikutip.

person Daniel Earwicker    schedule 21.03.2009
comment
Wah, bagus sekali. Saya tidak tahu mengapa saya tidak memikirkan hal itu (ide bagus sering kali memancing perasaan itu). Saya sedang menulis aplikasi perhitungan yang bisa sangat bagus dalam menjelaskan jawaban yang didapat. - person Charlie Flowers; 21.03.2009
comment
Kombinasi pohon refleksi dan ekspresi memungkinkan banyak hal yang mengejutkan Anda - terutama jika (seperti saya) Anda berasal dari latar belakang C/C++ yang runtimenya tidak ada. Kita harus menemukan kembali banyak hal yang telah dilakukan Lispers selama setengah abad! - person Daniel Earwicker; 21.03.2009
comment
Ide bagus, dan juga digunakan untuk aplikasi kalk! - person flq; 21.03.2009

Baiklah teman-teman, ini saya lagi, dan saya menemukan hal lain yang mencengangkan dan menyenangkan. Ini adalah postingan blog lain yang dirujuk dari pertanyaan SO lainnya yang disebutkan Chris di atas.

Pendekatan orang ini memungkinkan Anda menulis ini:

public class WebServer
    {
        public void BootstrapServer( int port, string rootDirectory, string serverName )
        {
            Guard.IsNotNull( () => rootDirectory );
            Guard.IsNotNull( () => serverName );

            // Bootstrap the server
        }
    }

Perhatikan bahwa tidak ada string yang berisi "rootDirectory" dan tidak ada string yang berisi "serverName"!! Namun pesan kesalahannya dapat mengatakan sesuatu seperti "Parameter rootDirectory tidak boleh nol."

Inilah yang saya inginkan dan lebih dari yang saya harapkan. Berikut tautan ke postingan blog pria tersebut.

Dan implementasinya cukup sederhana, sebagai berikut:

public static class Guard
    {
        public static void IsNotNull<T>(Expression<Func<T>> expr)
        {
            // expression value != default of T
            if (!expr.Compile()().Equals(default(T)))
                return;

            var param = (MemberExpression) expr.Body;
            throw new ArgumentNullException(param.Member.Name);
        }
    }

Perhatikan bahwa ini menggunakan "refleksi statis", jadi dalam lingkaran ketat atau semacamnya, Anda mungkin ingin menggunakan pendekatan Rick Brewster di atas.

Segera setelah saya memposting ini, saya akan memilih Chris, dan jawaban terhadap pertanyaan SO lainnya. Ini barang bagus!!!

person Charlie Flowers    schedule 22.03.2009
comment
Hai, agak terlambat dalam menyadari hal ini, tapi menurut saya ini sedikit kehilangan kekuatan ekspresi secara umum. Lihat jawaban saya lagi - Anda tidak perlu menulis fungsi terpisah untuk setiap jenis pernyataan yang ingin Anda buat. Cukup tulis ekspresi dalam sintaksis C# normal: X.Assert(() => serverName != null); Tidak ada alasan bagus untuk mengumpulkan sekumpulan metode khusus (IsNull, IsNotNull, AreEqual, dll.) satu untuk setiap jenis pernyataan, karena kita bisa mendapatkan deskripsi lengkap tentang ekspresi apa pun dan semuanya nilai-nilai di dalamnya, apa pun ekspresi yang terjadi. - person Daniel Earwicker; 06.08.2009
comment
Earwicker, itu pengamatan yang menarik. Saya menyukainya, meskipun ini bukan slam dunk dan mungkin ada kasus di mana saya menggunakan pendekatan banyak metode (IsNull, IsNotNull, dll.) Atau setidaknya saya berdebat dengan diri saya sendiri tentang hal itu. Pesan kesalahannya bisa lebih bagus dengan mengatakan NamaDepan tidak boleh nol daripada Pernyataan (NamaDepan!= null) gagal. Ditambah lagi jika Anda memiliki seratus tempat di mana Anda menyatakan (sesuatu! = null), itu terasa seperti duplikasi kode. Saya tidak yakin apakah itu duplikasi yang berbahaya atau tidak. Tergantung mungkin. Tapi observasi yang keren, terima kasih. - person Charlie Flowers; 17.08.2009

Menggunakan perpustakaan saya Tritunggal Pembantu:

public void SomeMethod(string firstName, string lastName, int age)
{
    firstName.AssertNotNull("firstName");
    lastName.AssertNotNull("lastName");

    ...
}

Juga mendukung penegasan bahwa parameter enumerasi sudah benar, koleksi dan isinya bukan-null, parameter string tidak kosong, dan sebagainya. Lihat dokumentasi pengguna di sini untuk contoh detail .

person Kent Boogaart    schedule 21.03.2009
comment
+1. Saya suka penggunaan metode ekstensi untuk meminimalkan jumlah kata. Saya akan memanggil metode MustNotBeNull atau semacamnya, tapi itu hanya preferensi pribadi - person Orion Edwards; 22.03.2009
comment
Apa yang terjadi dalam kode itu jika FirstName adalah null? Bukankah NullRef akan terjadi? Jangan pernah mencoba melihat apakah Metode Ekstensi masih dapat dilampirkan ke penunjuk nol. - person Jarrod Dixon♦; 22.03.2009
comment
@Jarrod, metode ekstensi bersifat statis, jadi dalam metode ekstensi Anda dapat menguji apakah target 'ini' adalah nol. - person ProfK; 22.03.2009
comment
@ProfK - luar biasa! Kalau begitu, beri +1 pada jawaban ini. - person Jarrod Dixon♦; 23.03.2009
comment
Saya juga tidak tahu Anda dapat memanggil metode ekstensi pada null sampai saya mulai melihat beberapa jawabannya di sini. Ini luar biasa, karena membuka banyak hal yang menghasilkan sintaksis yang lebih lancar. - person Charlie Flowers; 23.03.2009

Inilah jawaban saya untuk masalah ini. Saya menyebutnya "Guard Claws". Ia menggunakan parser IL dari Lokad Shared Libs tetapi memiliki pendekatan yang lebih mudah untuk menyatakan klausa penjaga sebenarnya:

string test = null;
Claws.NotNull(() => test);

Anda dapat melihat lebih banyak contoh penggunaannya di spesifikasi .

Karena menggunakan lambda asli sebagai masukan dan menggunakan IL Parser hanya untuk menghasilkan pengecualian jika terjadi pelanggaran, kinerjanya akan lebih baik di "jalur bahagia" daripada desain berbasis Ekspresi di tempat lain dalam jawaban ini.

Tautan tidak berfungsi, berikut URL-nya: http://github.com/littlebits/guard_claws/

person brendanjerwin    schedule 05.07.2009
comment
Dingin. Adakah rencana untuk menggunakan argumen params agar beberapa variabel diverifikasi? - person Charlie Flowers; 07.07.2009

Perpustakaan Bersama Lokad juga memiliki implementasi berbasis penguraian IL yang menghindari keharusan menduplikasi nama parameter dalam sebuah string.

Misalnya:

Enforce.Arguments(() => controller, () => viewManager,() => workspace);

Akan memunculkan pengecualian dengan nama parameter yang sesuai jika salah satu argumen yang terdaftar adalah null. Ia juga memiliki penerapan aturan berbasis kebijakan yang sangat rapi.

e.g.

Enforce.Argument(() => username,    StringIs.Limited(3, 64), StringIs.ValidEmail);
person Nick Cox    schedule 18.06.2009

Preferensi saya adalah hanya mengevaluasi kondisi dan meneruskan hasilnya daripada meneruskan ekspresi untuk dievaluasi dan parameter untuk mengevaluasinya. Selain itu, saya lebih suka memiliki kemampuan untuk menyesuaikan keseluruhan pesan. Perhatikan bahwa ini hanyalah preferensi -- saya tidak mengatakan bahwa sampel Anda salah -- namun ada beberapa kasus di mana ini sangat berguna.

Helper.Validate( firstName != null || !string.IsNullOrEmpty(directoryID),
                 "The value for firstName cannot be null if a directory ID is not supplied." );
person tvanfosson    schedule 21.03.2009

Tidak tahu apakah teknik ini ditransfer dari C/C++ ke C#, tapi saya sudah melakukannya dengan makro:


    #define CHECK_NULL(x)  { (x) != NULL || \
           fprintf(stderr, "The value of %s in %s, line %d is null.\n", \
           #x, __FILENAME__, __LINE__); }
 
person Adam Liss    schedule 21.03.2009
comment
Hal itulah yang saya harapkan, tetapi saya rasa hal itu tidak bisa langsung dilakukan di c#. - person Charlie Flowers; 21.03.2009
comment
C# tidak mendukung jenis trik praprosesor yang Anda temukan di kompiler C++. - person Brian Rasmussen; 21.03.2009

Dalam hal ini, daripada menggunakan tipe pengecualian Anda sendiri, atau tipe yang sangat umum seperti ApplicationException.. Saya rasa yang terbaik adalah menggunakan tipe pengecualian bawaan yang khusus ditujukan untuk penggunaan ini:

Diantaranya.. System.ArgumentException, System.ArgumentNullException...

person markt    schedule 21.03.2009
comment
Oke, tapi tolong... jangan fokus pada jenis pengecualian. Itu merupakan tambahan dari masalah inti di sini dan oleh karena itu saya hanya mengetikkan hal pertama yang muncul di kepala saya. - person Charlie Flowers; 21.03.2009
comment
Baik.. tapi saya percaya bahwa setiap kali Anda melemparkan pengecualian, melemparkan jenis pengecualian yang berguna sangatlah penting. - person markt; 21.03.2009

Postsharp atau AOP kerangka kerja.

person Kris Erickson    schedule 21.03.2009

Ini tidak berlaku di semua tempat, tetapi mungkin membantu dalam banyak kasus:

Saya kira "SomeMethod" sedang melakukan beberapa operasi perilaku pada data "nama belakang", "nama depan" dan "usia". Evaluasi desain kode Anda saat ini. Jika ketiga bagian data membutuhkan satu kelas, masukkan ke dalam satu kelas. Di kelas itu Anda juga dapat memberikan cek Anda. Ini akan membebaskan "SomeMethod" dari pemeriksaan input.

Hasil akhirnya akan menjadi seperti ini:

public void SomeMethod(Person person)
{
    person.CheckInvariants();
    // code here ...
}

Panggilannya akan seperti ini (jika Anda menggunakan .NET 3.5):

SomeMethod(new Person { FirstName = "Joe", LastName = "White", Age = 12 });

dengan asumsi bahwa kelasnya akan terlihat seperti ini:

public class Person
{
    public string FirstName { get; set; }
    public string LastName { get; set; }
    public int Age { get; set; }

    public void CheckInvariants()
    {
        assertNotNull(FirstName, "first name");
        assertNotNull(LastName, "last name");
    }

    // here are your checks ...
    private void assertNotNull(string input, string hint)
    {
        if (input == null)
        {
            string message = string.Format("The given {0} is null.", hint);
            throw new ApplicationException(message);
        }
    }

Alih-alih gula sintaksis .NET 3.5 Anda juga dapat menggunakan argumen konstruktor untuk membuat objek Person.

person Theo Lenndorff    schedule 22.03.2009

Sebaliknya, ini diposkan oleh Miško Hevery di Blog Pengujian Google berpendapat bahwa pemeriksaan parameter semacam ini mungkin tidak selalu merupakan hal yang baik. Perdebatan yang muncul dalam komentar tersebut juga memunculkan beberapa poin menarik.

person Henrik Gustafsson    schedule 22.03.2009
comment
Ada benarnya sudut pandang ini. Saya belum tentu berbicara tentang penegakan kontrak secara agama (saya pikir itu bagus dalam beberapa kasus, tetapi tidak semua). Namun tetap saja, beberapa metode perlu melakukan pemeriksaan tertentu, dan sering kali Anda membutuhkan bantuan untuk kasus tersebut. - person Charlie Flowers; 23.03.2009
comment
Tentu saja pemeriksaan semacam ini berguna dalam banyak situasi, saya hanya ingin pendapat yang berbeda untuk generasi mendatang :) - person Henrik Gustafsson; 23.03.2009