İki değer döndürmek, Tuple - "out" - "struct"


88

İki değer döndüren bir işlevi düşünün. Yazabiliriz:

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

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

// Using struct:
MyStruct MyFunction(string input)

En iyi uygulama hangisi ve neden?


String bir değer türü değil. Sanırım "iki değer döndüren bir işlevi düşünün" demek istediniz.
Eric Lippert

@Eric: Haklısın. Değişmez türleri kastetmiştim.
Xaqron

ve bir sınıfın nesi var?
Lukasz Madon

1
@lukas: Hiçbir şey, ama kesinlikle en iyi uygulamalarda değil. Bu hafif bir değerdir (<16 KB) ve özel bir kod ekleyeceksem, belirtildiği structgibi devam edeceğim Eric.
Xaqron

1
TryParse'da olduğu gibi, dönüş verilerini işlemeniz gerekip gerekmediğine karar vermek için yalnızca dönüş değerine ihtiyacınız olduğunda kullanın derim, aksi takdirde yapılandırılmış nesnenin değer türü veya referans olması gerektiği gibi her zaman yapılandırılmış bir nesne döndürmelisiniz. tür, verilerden ne tür ek kullanım yaptığınıza bağlıdır
MikeT

Yanıtlar:


94

Her birinin artıları ve eksileri var.

Çıkış parametreleri hızlı ve ucuzdur, ancak bir değişkeni aktarmanızı ve mutasyona güvenmenizi gerektirir. LINQ ile bir out parametresini doğru şekilde kullanmak neredeyse imkansızdır.

Tuplelar, çöp toplama baskısı yapar ve kendi kendini belgelemez. "Öğe1" çok açıklayıcı değil.

Özel yapılar, büyükse kopyalanması yavaş olabilir, ancak kendi kendini belgelendirir ve küçükse etkilidir. Bununla birlikte, önemsiz kullanımlar için bir sürü özel yapı tanımlamak da bir acıdır.

Diğer her şeyin eşit olduğu özel yapı çözümüne meyilli olurdum. Daha da iyisi , yalnızca bir değer döndüren bir işlev yapmaktır . Neden ilk etapta iki değer döndürüyorsunuz?

GÜNCELLEME: Bu makalenin yazılmasından altı yıl sonra gönderilen C # 7'deki tupleların değer türleri olduğunu ve dolayısıyla toplama baskısı yaratma olasılığının düşük olduğunu unutmayın.


2
İki değer döndürmek genellikle seçenek türlerine veya ADT'ye sahip olmamanın yerine geçer.
Anton Tykhyy

2
Diğer dillerle olan deneyimlerime göre, genellikle tupleların öğelerin hızlı ve kirli gruplandırılması için kullanıldığını söyleyebilirim. Her öğeyi adlandırmanıza izin verdiği için bir sınıf veya yapı oluşturmak genellikle daha iyidir. Demetleri kullanırken, her bir değerin anlamını belirlemek zor olabilir. Ancak, eğer söz konusu sınıf / yapı başka bir yerde kullanılmazsa, aşırı olabilen sınıf / yapı oluşturmak için zaman ayırmaktan sizi kurtarır.
Kevin Cathcart

23
@Xaqron: Programınızda "zaman aşımına sahip veri" fikrinin yaygın olduğunu fark ederseniz, yönteminizin bir TimeLimited <string> veya TimeLimited döndürmesini sağlamak için genel bir "TimeLimited <T>" türü oluşturmayı düşünebilirsiniz. <Uri> veya her neyse. TimeLimited <T> sınıfı, size "ne kadar kaldık?" Diyen yardımcı yöntemlere sahip olabilir. veya "süresi doldu mu?" ya da her neyse. Yazı sisteminde bunun gibi ilginç semantikleri yakalamaya çalışın.
Eric Lippert

3
Kesinlikle, Tuple'ı asla genel arayüzün bir parçası olarak kullanmam. Ancak 'özel' kod için bile, Tuple kullanmak yerine uygun bir türden muazzam bir okunabilirlik elde ediyorum (özellikle Otomatik Özellikler ile özel bir iç tip oluşturmak ne kadar kolay).
SolutionYogi

2
Toplama basıncı ne anlama geliyor?
rulo

27

Önceki cevaplara ek olarak, C # 7 System.Tuple, bir referans türünden farklı olarak değer türü demetleri getirir ve ayrıca gelişmiş anlambilim sunar.

Yine de adsız bırakabilir ve .Item*sözdizimini kullanabilirsiniz :

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

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

Ancak bu yeni özellik hakkında gerçekten güçlü olan şey, isimlendirilmiş tupllara sahip olma yeteneğidir. Böylece yukarıdakileri şu şekilde yeniden yazabiliriz:

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

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

Yıkım da desteklenmektedir:

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


2
Bunun temelde kaputun altındaki üyeler olarak referanslar içeren bir yapı döndürdüğünü düşünmekte haklı mıyım?
Austin_Anderson

4
Bunun performansının, parametreleri kullanmayla karşılaştırıldığında nasıl olduğunu biliyor muyuz?
SpaceMonkey

20

Cevabın, fonksiyonun ne yaptığına ve iki değer arasındaki ilişkiye bağlı olduğunu düşünüyorum.

Örneğin, TryParseyöntemler outayrıştırılmış değeri kabul etmek için bir parametre alır boolve ayrıştırmanın başarılı olup olmadığını belirtmek için bir a döndürür . İki değer gerçekten birbirine ait değildir, bu nedenle anlamsal olarak daha mantıklıdır ve outparametrenin kullanılması, kodun amacının okunması daha kolaydır .

Bununla birlikte, işleviniz ekrandaki bir nesnenin X / Y koordinatlarını döndürürse, bu durumda iki değer anlamsal olarak birbirine aittir ve a kullanmak daha iyi olacaktır struct.

tupleÜyeleri almak için garip sözdizimi nedeniyle harici kod tarafından görülebilecek herhangi bir şey için kişisel olarak bir kullanmaktan kaçınırdım.


Anlambilim için +1. outParametreden ayrılabildiğimizde cevabınız daha uygun beyaz referans türleridir null. Orada birkaç boş değer atanabilir değişmez tür var.
Xaqron

3
Aslında, TryParse'daki iki değer, birinin bir dönüş değeri ve diğerinin bir ByRef parametresi olması nedeniyle ima edilenden çok daha fazla birbirine aittir. Birçok yönden, döndürülecek mantıksal şey null yapılabilir bir tür olacaktır. TryParse deseninin güzel çalıştığı bazı durumlar vardır ve bazılarının acı olduğu bazı durumlar vardır ("if" ifadesinde kullanılması güzeldir, ancak null yapılabilir bir değer döndürmenin veya varsayılan bir değer belirtebilmenin birçok durumda vardır. daha uygun olur).
supercat

@supercat andrew'a katılıyorum, birbirlerine ait değiller. birbirleriyle ilişkili olsalar da, geri dönüş, art arda işlenmesi gereken bir şey değil, değerle uğraşmanız gerekip gerekmediğini size söyler. bu nedenle, dönüşü işledikten sonra, artık çıkış değeriyle ilgili başka herhangi bir işlem için gerekli değildir, bu, anahtar ile değer arasında açık ve sürekli bir bağlantının olduğu bir sözlükten bir KeyValuePair döndürmekten farklıdır. Null atanabilir türler .Net 1.1'de olsaydı kabul
etsem

@MikeT: Microsoft'un, yapıların yalnızca tek bir değeri temsil eden şeyler için kullanılması gerektiğini önermesinin son derece talihsiz olduğunu düşünüyorum, aslında açık alan yapısı, koli bandı ile birbirine bağlanmış bir grup bağımsız değişkeni bir araya getirmek için ideal bir ortamdır. . Başarı göstergesi ve değer, döndürüldükleri anda anlamlıdır , bundan sonra ayrı değişkenler olarak daha kullanışlı olsalar bile. Bir değişkende depolanan açık alan yapısının alanları ayrı değişkenler olarak kullanılabilir. Her durumda ...
supercat

@MikeT: Kovaryansın çerçeve içinde desteklenip desteklenmemesi nedeniyle, trykovaryant arayüzlerle çalışan tek model T TryGetValue(whatever, out bool success); bu yaklaşım arabirimlere izin verecekti IReadableMap<in TKey, out TValue> : IReadableMap<out TValue>ve Animalörneklerini Carbir Dictionary<Cat, ToyotaCar>[kullanımını kabul etmek için örnekleriyle eşlemek isteyen koda izin verecekti TryGetValue<TKey>(TKey key, out bool success). Parametre TValueolarak kullanılırsa böyle bir varyans mümkün değildir ref.
supercat

2

Out parametresini kullanma yaklaşımına gideceğim çünkü ikinci yaklaşımda Tuple sınıfını oluşturup nesneye eklemeniz ve daha sonra ona değer katmanız gerekir ki bu, in out parametresini döndürmeye kıyasla maliyetli bir işlemdir. Tuple Sınıfında birden çok değer döndürmek istiyorsanız (ki bu sadece bir parametre döndürerek gerçekleştirilemez), o zaman ikinci yaklaşıma geçeceğim.


Katılıyorum out. Ek paramsolarak soruyu açıklığa kavuşturmak için bahsetmediğim bir anahtar kelime var .
Xaqron

2

Struct yerine özel bir sınıfa sahip olan bir seçenekten daha bahsetmediniz. Veriler, işlevler tarafından çalıştırılabilen kendisiyle ilişkilendirilmiş anlambilimlere sahipse veya örnek boyutu yeterince büyükse (genel kural olarak> 16 bayt), özel bir sınıf tercih edilebilir. İşaretçilerle ilişkilendirilmesi ve referans türlerinin nasıl çalıştığının anlaşılmasını gerektirmesi nedeniyle genel API'de "out" kullanımı önerilmez.

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

Tuple, dahili kullanım için iyidir, ancak genel API'de kullanımı gariptir. Yani, oyum yapı ve genel API sınıfı arasında.


1
Bir tür, yalnızca bir değer toplamasını döndürmek amacıyla mevcutsa, basit bir açık alan değer türünün bu anlambilim için en açık uygunluk olduğunu söyleyebilirim. Türde alanları dışında hiçbir şey yoksa, yakalanmış bir görüntüyü mü yoksa canlı görüntüyü mi temsil ettiği (açık alan yapısı harekete geçemez), ne tür veri doğrulamasını gerçekleştirdiği (hiçbiri, açıkçası) hakkında herhangi bir soru olmayacaktır. canlı görüntü olarak), vb. Değişmez sınıflarla çalışmak daha az uygundur ve yalnızca örnekler birden çok kez aktarılabiliyorsa bir performans avantajı sunar.
supercat

1

"En iyi uygulama" yoktur. Rahat olduğunuz ve sizin durumunuzda en iyi olan şeydir. Bununla tutarlı olduğunuz sürece, gönderdiğiniz çözümlerin hiçbirinde sorun yoktur.


4
Elbette hepsi çalışıyor. Teknik bir avantaj yoksa, uzmanlar tarafından çoğunlukla neyin kullanıldığını merak ediyorum.
Xaqron
Sitemizi kullandığınızda şunları okuyup anladığınızı kabul etmiş olursunuz: Çerez Politikası ve Gizlilik Politikası.
Licensed under cc by-sa 3.0 with attribution required.