Mengembalikan dua nilai, Tuple vs 'out' vs 'struct'

88

Pertimbangkan fungsi yang mengembalikan dua nilai. Kita bisa menulis:

// Using out:
string MyFunction(string input, out int count)

// Using Tuple class:
Tuple<string, int> MyFunction(string input)

// Using struct:
MyStruct MyFunction(string input)

Manakah yang merupakan praktik terbaik dan mengapa?

Xaqron
sumber
String bukanlah tipe nilai. Saya pikir Anda bermaksud mengatakan "pertimbangkan fungsi yang mengembalikan dua nilai".
Eric Lippert
@ Eric: Anda benar. Maksudku tipe yang tidak bisa diubah.
Xaqron
dan apa yang salah dengan kelas?
Lukasz Madon
1
@lukas: Tidak ada, tapi yang pasti ini bukan praktik terbaik. Ini adalah nilai yang ringan (<16 KB) dan jika saya akan menambahkan kode khusus, saya akan menggunakan structseperti yang Ericdisebutkan.
Xaqron
1
Saya akan mengatakan hanya menggunakan ketika Anda membutuhkan nilai kembali untuk memutuskan apakah Anda harus memproses data yang dikembalikan sama sekali, seperti di TryParse, jika tidak, Anda harus selalu mengembalikan objek terstruktur, seperti apakah objek terstruktur harus berupa tipe nilai atau referensi jenis tergantung pada penggunaan tambahan apa yang Anda buat dari data
MikeT

Jawaban:

94

Mereka masing-masing memiliki pro dan kontra.

Parameter keluar cepat dan murah tetapi mengharuskan Anda meneruskan variabel, dan mengandalkan mutasi. Hampir tidak mungkin untuk menggunakan parameter out dengan benar dengan LINQ.

Tuple membuat tekanan pengumpulan sampah dan tidak mendokumentasikan diri sendiri. "Item1" tidak terlalu deskriptif.

Struct kustom bisa lambat untuk disalin jika besar, tetapi mendokumentasikan diri sendiri dan efisien jika kecil. Namun juga merepotkan untuk menentukan sejumlah besar struct kustom untuk penggunaan sepele.

Saya akan cenderung ke solusi struct kustom semua hal lain dianggap sama. Bahkan lebih baik adalah membuat fungsi yang hanya mengembalikan satu nilai . Mengapa Anda mengembalikan dua nilai pada awalnya?

PEMBARUAN: Perhatikan bahwa tupel di C # 7, yang dikirim enam tahun setelah artikel ini ditulis, adalah tipe nilai dan karenanya kecil kemungkinannya untuk menciptakan tekanan pengumpulan.

Eric Lippert
sumber
2
Mengembalikan dua nilai sering kali menggantikan tidak memiliki tipe opsi atau ADT.
Anton Tykhyy
2
Dari pengalaman saya dengan bahasa lain, saya akan mengatakan bahwa umumnya tupel digunakan untuk pengelompokan item yang cepat dan kotor. Biasanya lebih baik membuat kelas atau struct hanya karena memungkinkan Anda memberi nama setiap item. Saat menggunakan tupel, arti dari setiap nilai bisa sulit ditentukan. Tapi itu menyelamatkan Anda dari meluangkan waktu untuk membuat kelas / struct yang bisa berlebihan jika kelas / struct tersebut tidak akan digunakan di tempat lain.
Kevin Cathcart
23
@Xaqron: Jika Anda menemukan bahwa gagasan "data dengan batas waktu" adalah umum dalam program Anda, maka Anda dapat mempertimbangkan untuk membuat jenis umum "TimeLimited <T>" sehingga Anda dapat meminta metode Anda mengembalikan <string> atau TimeLimited TimeLimited <Uri> atau apapun. Kelas <T> TimeLimited kemudian dapat memiliki metode pembantu yang memberi tahu Anda "berapa lama waktu yang tersisa?" atau "apakah sudah kadaluwarsa?" atau terserah. Cobalah untuk menangkap semantik menarik seperti ini dalam sistem tipe.
Eric Lippert
3
Tentu saja, saya tidak akan pernah menggunakan Tuple sebagai bagian dari antarmuka publik. Tetapi bahkan untuk kode 'pribadi', saya mendapatkan keterbacaan yang sangat besar dari tipe yang tepat daripada menggunakan Tuple (terutama betapa mudahnya membuat tipe dalam pribadi dengan Properti Otomatis).
SolutionYogi
2
Apa arti tekanan pengumpulan?
gulungan
27

Menambah jawaban sebelumnya, C # 7 menghadirkan tupel tipe nilai, tidak seperti System.Tupletipe referensi dan juga menawarkan semantik yang ditingkatkan.

Anda masih dapat membiarkannya tanpa nama dan menggunakan .Item*sintaks:

(string, string, int) getPerson()
{
    return ("John", "Doe", 42);
}

var person = getPerson();
person.Item1; //John
person.Item2; //Doe
person.Item3;   //42

Tapi yang benar-benar hebat dari fitur baru ini adalah kemampuannya untuk menamai tuple. Jadi kita bisa menulis ulang seperti ini:

(string FirstName, string LastName, int Age) getPerson()
{
    return ("John", "Doe", 42);
}

var person = getPerson();
person.FirstName; //John
person.LastName; //Doe
person.Age;   //42

Destrukturisasi juga didukung:

(string firstName, string lastName, int age) = getPerson()

dimlucas
sumber
2
Apakah saya benar dalam berpikir ini mengembalikan pada dasarnya sebuah struct dengan referensi sebagai anggota di bawah tenda?
Austin_Anderson
4
apakah kita tahu bagaimana performanya dibandingkan dengan menggunakan parameter?
SpaceMonkey
20

Saya pikir jawabannya tergantung pada semantik dari apa yang dilakukan fungsi tersebut, dan hubungan antara kedua nilai tersebut.

Misalnya, TryParsemetode mengambil outparameter untuk menerima nilai parsing, dan mengembalikan a booluntuk menunjukkan apakah parse berhasil atau tidak. Kedua nilai tidak benar-benar dimiliki bersama, jadi, secara semantik, ini lebih masuk akal, dan maksud kode lebih mudah dibaca, untuk menggunakan outparameter.

Namun, jika fungsi Anda mengembalikan koordinat X / Y dari beberapa objek di layar, maka kedua nilai tersebut secara semantik menjadi satu dan akan lebih baik menggunakan a struct.

Saya pribadi menghindari penggunaan a tupleuntuk apa pun yang akan terlihat oleh kode eksternal karena sintaks yang canggung untuk mengambil anggota.

Andrew Cooper
sumber
1 untuk semantik. Jawaban Anda lebih sesuai dengan jenis referensi ketika kami dapat meninggalkan outparameter null. Ada beberapa jenis yang tidak dapat diubah di luar sana.
Xaqron
3
Sebenarnya, dua nilai dalam TryParse sangat cocok, jauh lebih banyak daripada yang tersirat dengan memiliki satu sebagai nilai kembali dan yang lainnya sebagai parameter ByRef. Dalam banyak hal, hal logis untuk dikembalikan adalah tipe yang dapat dibatalkan. Ada beberapa kasus di mana pola TryParse berfungsi dengan baik, dan beberapa di mana itu menyakitkan (bagus karena dapat digunakan dalam pernyataan "jika", tetapi ada banyak kasus di mana mengembalikan nilai nullable atau dapat menentukan nilai default akan lebih nyaman).
supercat
@supercat saya setuju dengan Andrew, mereka tidak dimiliki bersama. meskipun mereka terkait, pengembaliannya memberi tahu Anda jika Anda perlu peduli dengan nilai sama sekali bukan sesuatu yang perlu diproses bersama-sama. jadi setelah Anda memproses pengembaliannya tidak lagi diperlukan untuk pemrosesan lain yang berkaitan dengan nilai keluar, ini berbeda dengan mengatakan mengembalikan KeyValuePair dari kamus di mana ada tautan yang jelas dan sedang berlangsung antara kunci dan nilainya. meskipun saya setuju jika jenis nullable telah masuk Net 1.1 mereka mungkin akan menggunakannya sebagai null akan menjadi cara yang tepat untuk menandai tidak ada nilai
MikeT
@MikeT: Saya menganggap sangat disayangkan bahwa Microsoft telah menyarankan bahwa struktur hanya boleh digunakan untuk hal-hal yang mewakili satu nilai, padahal sebenarnya struktur bidang terbuka adalah media yang ideal untuk menyatukan sekelompok variabel independen yang terikat bersama dengan lakban . Indikator keberhasilan dan nilai memiliki arti bersama pada saat dikembalikan , meskipun setelah itu akan lebih berguna sebagai variabel terpisah. Bidang struktur bidang terpapar yang disimpan dalam variabel itu sendiri dapat digunakan sebagai variabel terpisah. Bagaimanapun ...
supercat
@MikeT: Karena cara kovarian dan tidak didukung dalam kerangka kerja, satu-satunya trypola yang bekerja dengan antarmuka kovarian adalah T TryGetValue(whatever, out bool success); Pendekatan itu akan memungkinkan antarmuka IReadableMap<in TKey, out TValue> : IReadableMap<out TValue>, dan membiarkan kode yang ingin memetakan instance Animalke instance Caruntuk menerima Dictionary<Cat, ToyotaCar>penggunaan TryGetValue<TKey>(TKey key, out bool success). Varians seperti itu tidak dimungkinkan jika TValuedigunakan sebagai refparameter.
supercat
2

Saya akan pergi dengan pendekatan menggunakan parameter Out karena dalam pendekatan kedua Anda akan perlu membuat dan objek kelas Tuple dan kemudian menambah nilai, yang menurut saya merupakan operasi yang mahal dibandingkan dengan mengembalikan nilai parameter keluar. Meskipun jika Anda ingin mengembalikan beberapa nilai di Tuple Class (yang tidak dapat dicapai hanya dengan mengembalikan satu parameter keluar) maka saya akan menggunakan pendekatan kedua.

Sumit
sumber
Saya setuju dengan out. Selain itu, ada paramskata kunci yang tidak saya sebutkan untuk menjaga pertanyaan tetap lurus.
Xaqron
2

Anda tidak menyebutkan satu opsi lagi, yaitu memiliki kelas khusus, bukan struct. Jika data memiliki semantik yang terkait dengannya yang dapat dioperasikan oleh fungsi, atau ukuran instance cukup besar (> 16 byte sebagai aturan praktis), kelas khusus mungkin lebih disukai. Penggunaan "keluar" tidak direkomendasikan dalam API publik karena hubungannya dengan pointer dan membutuhkan pemahaman tentang cara kerja jenis referensi.

https://msdn.microsoft.com/en-us/library/ms182131.aspx

Tuple bagus untuk penggunaan internal, tetapi penggunaannya canggung di API publik. Jadi, pilihan saya adalah antara struct dan class untuk API publik.

JeanP
sumber
1
Jika suatu tipe ada murni untuk tujuan mengembalikan agregasi nilai, saya akan mengatakan bahwa tipe nilai bidang terbuka sederhana adalah yang paling cocok untuk semantik tersebut. Jika tidak ada apa pun dalam tipe selain bidangnya, tidak akan ada pertanyaan tentang jenis validasi data apa yang dilakukannya (tidak ada, jelas), apakah itu mewakili tampilan yang diambil atau langsung (struktur bidang terbuka tidak dapat bertindak sebagai tampilan langsung), dll. Kelas yang tidak dapat diubah kurang nyaman untuk digunakan, dan hanya menawarkan manfaat kinerja jika instans dapat diteruskan beberapa kali.
supercat
1

Tidak ada "praktik terbaik". Ini adalah apa yang membuat Anda nyaman dan apa yang paling berhasil dalam situasi Anda. Selama Anda konsisten dengan ini, tidak ada masalah dengan solusi apa pun yang Anda posting.

licik
sumber
4
Tentu saja semuanya bekerja. Jika tidak ada keuntungan teknis, saya masih penasaran ingin tahu apa yang paling banyak digunakan oleh para ahli.
Xaqron