Mana yang lebih baik, mengembalikan nilai atau parameter keluar?

147

Jika kita ingin mendapatkan nilai dari suatu metode, kita bisa menggunakan nilai balik, seperti ini:

public int GetValue(); 

atau:

public void GetValue(out int x);

Saya tidak begitu mengerti perbedaan di antara mereka, jadi, saya tidak tahu mana yang lebih baik. Bisakah Anda jelaskan ini?

Terima kasih.

Quan Mai
sumber
3
Saya berharap C # memiliki beberapa nilai pengembalian seperti Python, misalnya.
Perangkap
12
@Trap Anda dapat mengembalikan a Tuplejika Anda mau, tetapi konsensus umum adalah bahwa jika Anda perlu mengembalikan lebih dari satu hal, hal-hal tersebut biasanya terkait entah bagaimana dan hubungan itu biasanya paling baik dinyatakan sebagai kelas.
Pharap
2
@Pharap Tuples dalam C # dalam bentuk saat ini hanya jelek, tapi itu hanya pendapat saya. Di sisi lain, "konsensus umum" tidak ada artinya dari sudut pandang kegunaan dan produktivitas. Anda tidak akan membuat kelas untuk mengembalikan beberapa nilai karena alasan yang sama Anda tidak akan membuat kelas untuk mengembalikan beberapa nilai sebagai parameter ref / out.
Perangkap
@Rap return Tuple.Create(x, y, z);bukankah itu jelek. Selain itu, sudah terlambat untuk memperkenalkan mereka di tingkat bahasa. Alasan saya tidak akan membuat kelas untuk mengembalikan nilai dari parameter ref / out adalah karena par ref / out hanya benar-benar masuk akal untuk struct yang bisa berubah besar (seperti matriks) atau nilai opsional, dan yang terakhir masih bisa diperdebatkan.
Pharap
Tim @Pharap C # secara aktif mencari untuk memperkenalkan tuple di tingkat bahasa. Sementara itu disambut seluruh sejumlah besar pilihan di. NET luar biasa sekarang - tipe anonim, .NET Tuple<>dan C # tuple !. Saya hanya berharap C # diperbolehkan mengembalikan tipe anonim dari metode dengan tipe penyusun kompiler (seperti autodi Dlang).
nawfal

Jawaban:

153

Nilai pengembalian hampir selalu merupakan pilihan yang tepat ketika metode tidak memiliki hal lain untuk dikembalikan. (Faktanya, saya tidak bisa memikirkan kasus mana pun yang saya inginkan metode void dengan outparameter, jika saya punya pilihan. DeconstructMetode C # 7 untuk dekonstruksi yang didukung bahasa bertindak sebagai pengecualian yang sangat, sangat langka untuk aturan ini .)

Selain dari hal lain, itu menghentikan penelepon dari harus mendeklarasikan variabel secara terpisah:

int foo;
GetValue(out foo);

vs.

int foo = GetValue();

Nilai-nilai keluar juga mencegah metode chaining seperti ini:

Console.WriteLine(GetValue().ToString("g"));

(Memang, itulah salah satu masalah dengan setter properti juga, dan itulah sebabnya pola pembangun menggunakan metode yang mengembalikan pembangun, misalnya myStringBuilder.Append(xxx).Append(yyy).)

Selain itu, parameter keluar sedikit lebih sulit untuk digunakan dengan refleksi dan biasanya membuat pengujian lebih sulit juga. (Lebih banyak usaha biasanya dimasukkan ke dalam membuatnya mudah untuk mengejek nilai kembali daripada parameter keluar). Pada dasarnya tidak ada yang dapat saya pikirkan bahwa mereka membuat lebih mudah ...

Mengembalikan nilai FTW.

EDIT: Dalam hal apa yang terjadi ...

Pada dasarnya ketika Anda memberikan argumen untuk parameter "out", Anda harus memasukkan sebuah variabel. (Elemen array juga diklasifikasikan sebagai variabel.) Metode yang Anda panggil tidak memiliki variabel "baru" pada tumpukannya untuk parameter - ia menggunakan variabel Anda untuk penyimpanan. Setiap perubahan dalam variabel langsung terlihat. Berikut ini contoh yang menunjukkan perbedaan:

using System;

class Test
{
    static int value;

    static void ShowValue(string description)
    {
        Console.WriteLine(description + value);
    }

    static void Main()
    {
        Console.WriteLine("Return value test...");
        value = 5;
        value = ReturnValue();
        ShowValue("Value after ReturnValue(): ");

        value = 5;
        Console.WriteLine("Out parameter test...");
        OutParameter(out value);
        ShowValue("Value after OutParameter(): ");
    }

    static int ReturnValue()
    {
        ShowValue("ReturnValue (pre): ");
        int tmp = 10;
        ShowValue("ReturnValue (post): ");
        return tmp;
    }

    static void OutParameter(out int tmp)
    {
        ShowValue("OutParameter (pre): ");
        tmp = 10;
        ShowValue("OutParameter (post): ");
    }
}

Hasil:

Return value test...
ReturnValue (pre): 5
ReturnValue (post): 5
Value after ReturnValue(): 10
Out parameter test...
OutParameter (pre): 5
OutParameter (post): 10
Value after OutParameter(): 10

Perbedaannya ada pada langkah "post" - yaitu setelah variabel atau parameter lokal diubah. Dalam tes ReturnValue, ini tidak ada bedanya dengan valuevariabel statis . Dalam tes OutParameter, valuevariabel diubah oleh garistmp = 10;

Jon Skeet
sumber
2
Anda membuat saya percaya bahwa nilai pengembalian jauh lebih baik :). Tapi saya masih bertanya-tanya apa yang terjadi "secara mendalam". Maksud saya, nilai kembali, dan parameter keluar, apakah mereka berbeda dalam cara mereka dibuat, ditugaskan dan dikembalikan?
Quan Mai
1
TryParse adalah contoh terbaik saat menggunakan param yang sesuai & bersih. Meskipun saya telah menggunakannya dalam kasus-kasus khusus seperti if (WorkSucceeded (out List <string> errors) yang pada dasarnya pola yang sama seperti TryParse
Chad Grant
2
Jawaban yang buruk dan tidak membantu. Seperti yang dijelaskan dalam komentar pada pertanyaan ini , parameter out memiliki beberapa manfaat yang bermanfaat. Preferensi antara out vs return harus tergantung pada situasi.
aaronsnoswell
2
@ aaronsnoswell: Komentar tepat manakah? Ingatlah bahwa contoh Dictionary.TryGetValueadalah tidak berlaku di sini, karena itu bukan metode void. Bisakah Anda menjelaskan mengapa Anda menginginkan outparameter alih - alih nilai balik? (Bahkan untuk TryGetValue, saya lebih suka nilai pengembalian yang berisi semua informasi keluaran, secara pribadi. Lihat NodaTime ParseResult<T>untuk contoh bagaimana saya mendesainnya.)
Jon Skeet
2
@ Jim: Saya kira kita harus setuju untuk tidak setuju. Sementara saya mengerti maksud Anda tentang memalu orang di kepala dengan ini, juga membuat kurang ramah bagi mereka yang tidak tahu apa yang mereka lakukan. Hal yang menyenangkan tentang pengecualian daripada nilai kembali adalah bahwa Anda tidak dapat dengan mudah mengabaikannya dan melanjutkan seolah-olah tidak ada yang terjadi ... sedangkan dengan nilai balik dan outparameter, Anda tidak bisa melakukan apa pun dengan nilai tersebut.
Jon Skeet
26

Apa yang lebih baik, tergantung pada situasi khusus Anda. Salah satu alasan yang outada adalah untuk memfasilitasi pengembalian beberapa nilai dari satu pemanggilan metode:

public int ReturnMultiple(int input, out int output1, out int output2)
{
    output1 = input + 1;
    output2 = input + 2;

    return input;
}

Jadi satu tidak secara definisi lebih baik dari yang lain. Tetapi biasanya Anda ingin menggunakan pengembalian sederhana, kecuali jika Anda memiliki situasi di atas misalnya.

EDIT: Ini adalah contoh yang menunjukkan salah satu alasan bahwa kata kunci itu ada. Hal di atas sama sekali tidak dianggap sebagai praktik terbaik.

pirokumulus
sumber
2
Saya tidak setuju dengan ini, mengapa Anda tidak mengembalikan struktur data dengan 4 int? Ini sangat membingungkan.
Chad Grant
1
Jelas ada lebih banyak (dan lebih baik) cara untuk mengembalikan beberapa nilai, saya hanya memberi OP alasan mengapa pertama-tama ada.
pyrocumulus
2
Saya setuju dengan @Cloud. Hanya karena itu bukan cara terbaik bukan berarti itu tidak seharusnya ada.
Cerebrus
Yah kodenya bahkan tidak bisa dikompilasi untuk satu ... dan harus setidaknya menyebutkan bahwa itu dianggap praktik buruk / tidak disukai.
Chad Grant
1
Itu tidak akan dikompilasi? Dan mengapa begitu? Sampel ini dapat dikompilasi dengan sempurna. Dan tolong, saya memberikan contoh mengapa sintaks / kata kunci tertentu ada, bukan pelajaran dalam praktik terbaik.
pyrocumulus
23

Anda umumnya harus lebih suka nilai pengembalian daripada param. Keluar params adalah kejahatan yang diperlukan jika Anda menemukan diri Anda menulis kode yang perlu melakukan 2 hal. Contoh yang baik dari ini adalah pola Coba (seperti Int32.TryParse).

Mari kita pertimbangkan apa yang harus dilakukan oleh penelepon dari dua metode Anda. Sebagai contoh pertama saya bisa menulis ini ...

int foo = GetValue();

Perhatikan bahwa saya dapat mendeklarasikan variabel dan menetapkannya melalui metode Anda dalam satu baris. Untuk contoh ke-2 terlihat seperti ini ...

int foo;
GetValue(out foo);

Saya sekarang terpaksa mendeklarasikan variabel saya di muka dan menulis kode saya melalui dua baris.

memperbarui

Tempat yang tepat untuk melihat ketika mengajukan pertanyaan semacam ini adalah .NET Framework Design Guidelines. Jika Anda memiliki versi bukunya maka Anda dapat melihat anotasi dari Anders Hejlsberg dan lainnya tentang hal ini (halaman 184-185) tetapi versi online ada di sini ...

http://msdn.microsoft.com/en-us/library/ms182131(VS.80).aspx

Jika Anda menemukan diri Anda perlu mengembalikan dua hal dari API maka membungkusnya dalam struct / kelas akan lebih baik daripada param.

Martin Peck
sumber
Jawaban yang bagus, terutama referensi untuk TryParse, fungsi (umum) yang memaksa pengembang untuk menggunakan variabel (tidak umum) keluar.
Cerebrus
12

Ada satu alasan untuk menggunakan outparam yang belum disebutkan: metode panggilan wajib menerimanya. Jika metode Anda menghasilkan nilai yang tidak boleh dibuang oleh penelepon, menjadikannya outmemaksa penelepon untuk menerimanya secara khusus:

 Method1();  // Return values can be discard quite easily, even accidentally

 int  resultCode;
 Method2(out resultCode);  // Out params are a little harder to ignore

Tentu saja penelepon masih dapat mengabaikan nilai dalam sebuah outparam, tetapi Anda telah meminta perhatian mereka untuk itu.

Ini adalah kebutuhan yang langka; lebih sering, Anda harus menggunakan pengecualian untuk masalah asli atau mengembalikan objek dengan informasi status untuk "FYI", tetapi mungkin ada keadaan di mana ini penting.


sumber
8

Itu preferensi terutama

Saya lebih suka pengembalian dan jika Anda memiliki banyak pengembalian, Anda dapat membungkusnya dalam DTO Hasil

public class Result{
  public Person Person {get;set;}
  public int Sum {get;set;}
}
Scott Cowan
sumber
5

Anda hanya dapat memiliki satu nilai kembali sedangkan Anda dapat memiliki beberapa parameter keluar.

Anda hanya perlu mempertimbangkan parameter dalam kasus tersebut.

Namun, jika Anda perlu mengembalikan lebih dari satu parameter dari metode Anda, Anda mungkin ingin melihat apa yang Anda kembali dari pendekatan OO dan mempertimbangkan apakah Anda lebih baik mengembalikan objek atau struct dengan parameter ini. Karenanya Anda kembali ke nilai pengembalian lagi.

Robin Day
sumber
5

Anda hampir selalu harus menggunakan nilai kembali. ' out' Parameter membuat sedikit gesekan ke banyak API, komposisi, dll.

Pengecualian yang paling penting yang muncul dalam pikiran adalah ketika Anda ingin mengembalikan beberapa nilai (.Net Framework tidak memiliki tupel hingga 4.0), seperti dengan TryParsepola.

Brian
sumber
Tidak yakin apakah ini praktik yang baik tetapi Arraylist juga dapat digunakan untuk mengembalikan beberapa nilai.
BA
@BA tidak, ini bukan praktik yang baik, pengguna lain tidak akan tahu apa isi Arraylist ini, atau di posisi apa mereka berada. Misalnya saya ingin mengembalikan jumlah byte yang dibaca dan juga nilainya. tuple atau yang diberi nama akan jauh lebih baik. ArrayList, atau daftar apa pun lainnya hanya akan berguna jika Anda ingin mengembalikan collaction of things, misalnya daftar orang.
sLw
2

Saya lebih suka yang berikut daripada yang ada dalam contoh sederhana ini.

public int Value
{
    get;
    private set;
}

Tapi, semuanya sama saja. Biasanya, seseorang hanya akan menggunakan 'keluar' jika mereka perlu melewati beberapa nilai kembali dari metode. Jika Anda ingin mengirim nilai masuk dan keluar dari metode, orang akan memilih 'ref'. Metode saya adalah yang terbaik, jika Anda hanya mengembalikan nilai, tetapi jika Anda ingin melewatkan parameter dan mendapatkan nilai kembali, salah satu kemungkinan akan memilih pilihan pertama Anda.

kenny
sumber
2

Saya pikir salah satu dari beberapa skenario di mana itu akan berguna ketika bekerja dengan memori yang tidak dikelola, dan Anda ingin membuatnya jelas bahwa nilai "yang dikembalikan" harus dibuang secara manual, daripada mengharapkannya akan dibuang dengan sendirinya .

xian
sumber
2

Selain itu, nilai kembali kompatibel dengan paradigma desain asinkron.

Anda tidak dapat menetapkan fungsi "async" jika menggunakan parameter ref atau out.

Singkatnya, Nilai Pengembalian memungkinkan metode chaining, sintaks yang lebih bersih (dengan menghilangkan keharusan bagi pemanggil untuk mendeklarasikan variabel tambahan), dan memungkinkan desain asinkron tanpa perlu modifikasi substansial di masa depan.

firetiger77
sumber
Poin bagus pada penunjukan "async". Kupikir orang lain akan menyebutkan itu. Chaining - serta menggunakan nilai kembali (memang fungsi itu sendiri) sebagai ekspresi adalah kunci lain di baliknya. Ini benar-benar perbedaan antara hanya mempertimbangkan cara mendapatkan barang kembali dari suatu proses ("ember" - diskusi Tuple / kelas / struct) dan memperlakukan proses itu sendiri seperti ekspresi yang dapat diganti dengan nilai tunggal (kegunaan dari berfungsi sendiri, karena hanya mengembalikan 1 nilai).
user1172173
1

Keduanya memiliki tujuan yang berbeda dan tidak diperlakukan sama oleh kompiler. Jika metode Anda perlu mengembalikan nilai, maka Anda harus menggunakan pengembalian. Out digunakan di mana metode Anda perlu mengembalikan beberapa nilai.

Jika Anda menggunakan kembali, maka data pertama kali ditulis ke tumpukan metode dan kemudian di metode panggilan. Sementara dalam kasus keluar, itu langsung ditulis ke tumpukan metode panggilan. Tidak yakin apakah ada perbedaan lagi.

Orang Denmark
sumber
Metode tumpukan? Saya bukan pakar c #, tetapi x86 hanya mendukung satu tumpukan per utas. "Frame" metode ini adalah deallocated selama kembali, dan jika konteks beralih happend maka stack deallocated dapat ditimpa. Dalam c semua nilai kembali masuk dalam registry eax. Jika Anda ingin mengembalikan objek / struct, mereka perlu dialokasikan ke heap dan pointer akan diletakkan di eax.
Stefan Lundström
1

Seperti yang orang lain katakan: nilai balik, bukan parameter.

Bolehkah saya merekomendasikan kepada Anda buku "Pedoman Desain Kerangka Kerja" (edisi kedua)? Halaman 184-185 membahas alasan menghindari params. Seluruh buku akan mengarahkan Anda ke arah yang benar pada semua jenis masalah .NET coding.

Sekutu dengan Kerangka Desain Pedoman adalah penggunaan alat analisis statis, FxCop. Anda akan menemukan ini di situs Microsoft sebagai unduhan gratis. Jalankan ini pada kode kompilasi Anda dan lihat apa yang dikatakannya. Jika itu mengeluh tentang ratusan dan ratusan hal ... jangan panik! Lihatlah dengan tenang dan hati-hati pada apa yang dikatakan tentang setiap kasus. Jangan terburu-buru memperbaiki hal-hal secepatnya. Belajarlah dari apa yang dikatakannya kepada Anda. Anda akan ditempatkan di jalan menuju penguasaan.

Andrew Webb
sumber
1

Menggunakan kata kunci keluar dengan tipe pengembalian bool, terkadang dapat mengurangi kode mengasapi dan meningkatkan keterbacaan. (Terutama ketika info tambahan di param sering diabaikan). Misalnya:

var result = DoThing();
if (result.Success)
{
    result = DoOtherThing()
    if (result.Success)
    {
        result = DoFinalThing()
        if (result.Success)
        {
            success = true;
        }
    }
}

vs:

var result;
if (DoThing(out result))
{
    if (DoOtherThing(out result))
    {
        if (DoFinalThing(out result))
        {
            success = true;
        }
    }
}
Paul Smith
sumber
1

Tidak ada perbedaan nyata. Parameter keluar dalam C # untuk memungkinkan metode mengembalikan lebih dari satu nilai, itu saja.

Namun Ada beberapa perbedaan kecil, tetapi tidak ada yang benar-benar penting:

Menggunakan parameter keluar akan memaksa Anda untuk menggunakan dua baris seperti:

int n;
GetValue(n);

saat menggunakan nilai balik akan memungkinkan Anda melakukannya dalam satu baris:

int n = GetValue();

Perbedaan lain (hanya benar untuk tipe nilai dan hanya jika C # tidak inline fungsi) adalah bahwa menggunakan nilai kembali tentu akan membuat salinan nilai ketika fungsi kembali, saat menggunakan parameter OUT tidak harus melakukannya.

pengguna88637
sumber
0

keluar lebih berguna ketika Anda mencoba mengembalikan objek yang Anda nyatakan dalam metode.

Contoh

public BookList Find(string key)
{
   BookList book; //BookList is a model class
   _books.TryGetValue(key, out book) //_books is a concurrent dictionary
                                     //TryGetValue gets an item with matching key and returns it into book.
   return book;
}
Taera Kwon
sumber
0

nilai kembali adalah nilai normal yang dikembalikan oleh metode Anda.

Dimana sebagai parameter out , well out, dan ref adalah 2 kata kunci dari C # yang diijinkan untuk meneruskan variabel sebagai referensi .

Perbedaan besar antara ref dan keluar adalah, ref harus dijalankan sebelum dan keluar tidak

bizimunda
sumber
-2

Saya curiga saya tidak akan membahas pertanyaan ini, tetapi saya seorang programmer yang sangat berpengalaman, dan saya berharap beberapa pembaca yang lebih berpikiran terbuka akan memperhatikan.

Saya percaya bahwa itu sesuai dengan bahasa pemrograman berorientasi objek lebih baik untuk prosedur pengembalian nilai (VRPs) mereka menjadi deterministik dan murni.

'VRP' adalah nama akademis modern untuk fungsi yang disebut sebagai bagian dari ekspresi, dan memiliki nilai balik yang secara tidak sengaja menggantikan panggilan selama evaluasi ekspresi. Misalnya dalam pernyataan seperti x = 1 + f(y)fungsi fberfungsi sebagai VRP.

'Deterministik' berarti bahwa hasil fungsi hanya bergantung pada nilai-nilai parameternya. Jika Anda memanggilnya lagi dengan nilai parameter yang sama, Anda yakin untuk mendapatkan hasil yang sama.

'Murni' berarti tidak ada efek samping: memanggil fungsi tidak melakukan apa-apa selain menghitung hasilnya. Ini dapat diartikan sebagai tidak ada efek samping yang penting , dalam praktiknya, jadi jika VRP mengeluarkan pesan debugging setiap kali disebut, misalnya, yang mungkin dapat diabaikan.

Jadi, jika, dalam C #, fungsi Anda tidak deterministik dan murni, saya katakan Anda harus menjadikannya voidfungsi (dengan kata lain, bukan VRP), dan nilai apa pun yang perlu dikembalikan harus dikembalikan dalam parameter outatau refparameter.

Misalnya, jika Anda memiliki fungsi untuk menghapus beberapa baris dari tabel database, dan Anda ingin mengembalikan jumlah baris yang dihapus, Anda harus mendeklarasikannya seperti ini:

public void DeleteBasketItems(BasketItemCategory category, out int count);

Jika kadang-kadang Anda ingin memanggil fungsi ini tetapi tidak mendapatkan count, Anda selalu dapat menyatakan kelebihan beban.

Anda mungkin ingin tahu mengapa gaya ini lebih cocok untuk pemrograman berorientasi objek. Secara umum, itu cocok dengan gaya pemrograman yang bisa (sedikit tidak tepat) disebut 'pemrograman prosedural', dan itu adalah gaya pemrograman prosedural yang lebih cocok pemrograman berorientasi objek.

Mengapa? Model klasik objek adalah bahwa mereka memiliki properti (alias atribut), dan Anda menginterogasi dan memanipulasi objek (terutama) melalui membaca dan memperbarui properti tersebut. Gaya pemrograman prosedural cenderung membuatnya lebih mudah untuk melakukan ini, karena Anda dapat mengeksekusi kode arbitrer di antara operasi yang mendapatkan dan mengatur properti.

Kelemahan dari pemrograman prosedural adalah bahwa, karena Anda dapat mengeksekusi kode arbitrer di semua tempat, Anda bisa mendapatkan beberapa interaksi yang sangat tumpul dan rentan bug melalui variabel global dan efek samping.

Jadi, cukup sederhana, itu adalah praktik yang baik untuk memberi sinyal kepada seseorang yang membaca kode Anda bahwa suatu fungsi dapat memiliki efek samping dengan membuatnya kembali tidak bernilai.

pendebat
sumber
> Jika Anda terkadang ingin memanggil fungsi ini tetapi tidak mendapatkan hitungan, Anda selalu dapat menyatakan kelebihan beban. Di C # versi 7 (saya pikir) dan kemudian, Anda dapat menggunakan _simbol buang untuk mengabaikan parameter keluar, misalnya: DeleteBasketItems (kategori, keluar _);
debat