Hangisi daha iyi, dönüş değeri mi, çıkış parametresi mi?


151

Bir yöntemden değer almak istiyorsak, her iki dönüş değerini de kullanabiliriz, örneğin:

public int GetValue(); 

veya:

public void GetValue(out int x);

Aralarındaki farkları gerçekten anlamıyorum ve bu yüzden hangisinin daha iyi olduğunu bilmiyorum. Bunu bana açıklayabilir misin?

Teşekkür ederim.


3
Keşke C # 'ın Python gibi birden çok dönüş değeri olsaydı.
Trap

12
@Trap İsterseniz bir dönebilirsiniz Tuple, ancak genel fikir birliği, birden fazla şeyi döndürmeniz gerekiyorsa, işler genellikle bir şekilde ilişkilidir ve bu ilişki genellikle en iyi sınıf olarak ifade edilir.
Pharap

2
@Pharap Tuples mevcut haliyle C # sadece çirkin, ama bu sadece benim fikrim. Öte yandan, "genel fikir birliği", kullanılabilirlik ve üretkenlik açısından hiçbir şey ifade etmiyor. Ref / out parametresi olarak birkaç değer döndürmek için bir sınıf oluşturmamanızla aynı nedenle birkaç değer döndürmek için bir sınıf yaratamazsınız.
Trap

@Trap return Tuple.Create(x, y, z);O kadar çirkin değil. Ayrıca, onları dil seviyesinde tanıtmak için günün geç saatlerinde. Bir ref / out parametresinden değer döndürmek için bir sınıf oluşturmamamın nedeni, ref / out parametrelerinin yalnızca büyük değişken yapılar (matrisler gibi) veya isteğe bağlı değerler için gerçekten mantıklı olması ve ikincisinin tartışılabilir olmasıdır.
Pharap

@Pharap C # ekibi aktif olarak tuple'ları dil seviyesinde tanıtmak istiyor. Hoş karşılansa da, şimdi ezici bir şekilde .NET'teki tüm seçenek bolluğu - anonim tip, .NET Tuple<>ve C # tuples !. C # 'ın anonim türlerin, derleyici çıkarım türüne sahip yöntemlerden autodöndürülmesine izin vermesini diledim ( Dlang'daki gibi ).
nawfal

Yanıtlar:


156

Yöntemin döndürecek başka bir şeyi olmadığında dönüş değerleri neredeyse her zaman doğru seçimdir. (Aslında, ben istiyorum herhangi vakaların düşünemiyorum hiç bir bir boşluk yöntemi istiyorum outben seçim olsaydı, parametre. 7'nin C # Deconstructdili destekli yapıbozumu yöntemleri bu kuralın çok, çok nadir istisna olarak görür .)

Her şeyin yanı sıra, arayanın değişkeni ayrı ayrı bildirme zorunluluğunu ortadan kaldırır:

int foo;
GetValue(out foo);

vs

int foo = GetValue();

Çıkış değerleri ayrıca aşağıdaki gibi yöntem zincirlemesini önler:

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

(Gerçekten de, özellik belirleyicilerle ilgili sorunlardan biri de budur ve bu yüzden oluşturucu kalıbı, oluşturucuyu döndüren yöntemler kullanır, örn myStringBuilder.Append(xxx).Append(yyy).)

Ek olarak, dış parametrelerin yansıtma ile kullanılması biraz daha zordur ve genellikle testi de zorlaştırır. (Genellikle, dönüş değerlerinin alay edilmesini kolaylaştırmak için parametrelerden daha fazla çaba harcanır). Temelde kolaylaştırdıklarını düşündüğüm hiçbir şey yok ...

Dönüş değerleri FTW.

DÜZENLEME: Neler olduğu açısından ...

Bir "out" parametresi için bir tartışmaya geçtiğinde Temel olarak, sahip bir değişkene geçmek. (Dizi öğeleri de değişkenler olarak sınıflandırılır.) Çağrdığınız yöntemin, parametre için yığınında "yeni" bir değişken yoktur - depolama için değişkeninizi kullanır. Değişkendeki herhangi bir değişiklik hemen görülebilir. İşte farkı gösteren bir örnek:

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): ");
    }
}

Sonuçlar:

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

Fark "son" adımdadır - yani yerel değişken veya parametre değiştirildikten sonra. ReturnValue testinde bu, statik valuedeğişkende bir fark yaratmaz . OutParameter testinde valuedeğişken satır ile değiştirilir.tmp = 10;


2
Geri dönüş değerinin çok daha iyi olduğuna inandırdın :). Ama yine de "derinlemesine" ne olduğunu merak ediyorum. Demek istediğim, dönüş değeri ve çıkış parametresi, yaratılma, atama ve döndürülme şekillerinde farklı mı?
Quan Mai

1
TryParse, out parametresini kullanmanın uygun ve temiz olduğunun en iyi örneğidir. Her ne kadar temelde TryParse ile aynı model olan if (WorkSucceeded (out List <string> errors) gibi özel durumlarda kullanmış olsam da
Chad Grant

3
Zavallı, yararsız bir cevap. Bu soruya ilişkin yorumlarda açıklandığı gibi , out parametrelerinin birçok yararlı avantajı vardır. Geri dönüş ile geri dönüş arasındaki tercih duruma bağlı olmalıdır.
aaronsnoswell

2
@aaronsnoswell: Hangi kesin yorumlar? Akılda Ayı örneği olduğunu Dictionary.TryGetValueolduğunu değil bir boşluk yöntem değil gibi burada uygulanabilir. Neden bir dönüş değeri yerine bir outparametre istediğinizi açıklayabilir misiniz ? (Hatta kişisel olarak tüm çıktı bilgilerini içeren bir dönüş değerini tercih ederim. Nasıl tasarladığıma dair bir örnek için NodaTime'a bakın .)TryGetValueParseResult<T>
Jon Skeet

2
@Jim: Sanırım katılmama konusunda anlaşmamız gerekecek. Bunun kafasına insanları çekiçle ilgili noktaya bakın, aynı zamanda olanlar için daha az dost yapar ne yaptığını biliyorum. Bir dönüş değerinden ziyade bir istisnanın güzel yanı, onu kolayca görmezden gelemeyeceğiniz ve hiçbir şey olmamış gibi devam edemeyeceğinizdir ... oysa hem bir dönüş değeri hem de bir outparametre ile, değerle hiçbir şey yapamazsınız.
Jon Skeet

28

Daha iyisi, özel durumunuza bağlıdır. Bunun nedenlerinden outbiri, bir yöntem çağrısından birden çok değer döndürmeyi kolaylaştırmaktır:

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

    return input;
}

Yani biri tanımı gereği diğerinden daha iyi değil. Ancak, örneğin yukarıdaki durumunuz yoksa, genellikle basit bir iade kullanmak istersiniz.

DÜZENLE: Bu, anahtar kelimenin var olma nedenlerinden birini gösteren bir örnektir. Yukarıdakiler hiçbir şekilde bir en iyi uygulama olarak görülmemelidir.


2
Buna katılmıyorum, neden 4 inçlik bir veri yapısı döndürmüyorsunuz? Bu gerçekten kafa karıştırıcı.
Chad Grant

1
Açıkçası, birden çok değeri döndürmenin daha fazla (ve daha iyi) yolu var, ben sadece OP'ye neden ilk etapta var olduğuna dair bir neden veriyorum.
pyrocumulus

2
@Cloud'a katılıyorum. En iyi yol olmadığı için, var olmaması gerektiği anlamına gelmez.
Cerebrus

Eh, kod bir tane için bile derlenmeyecek ... ve en azından bunun kötü bir uygulama olarak kabul edildiğini / tercih edilmediğini belirtmeli.
Chad Grant

1
Derlenmeyecek mi? Ve neden böyle? Bu örnek mükemmel bir derleme yapabilir. Ve lütfen, en iyi uygulamalarla ilgili bir ders değil, neden belirli bir sözdiziminin / anahtar kelimenin var olduğuna dair bir örnek veriyorum.
pyrocumulus

24

Genellikle bir çıkış parametresine göre bir dönüş değeri tercih etmelisiniz. Kendinizi 2 şey yapması gereken bir kod yazarken bulursanız, paramlar gerekli bir kötülüktür. Buna iyi bir örnek, Try modelidir (Int32. TryParse gibi).

İki yönteminizi arayan kişinin ne yapması gerektiğini düşünelim. İlk örnek için şunu yazabilirim ...

int foo = GetValue();

Bir değişken tanımlayabileceğime ve onu yönteminiz aracılığıyla tek satırda atayabileceğime dikkat edin. Veya 2. örnek için şuna benziyor ...

int foo;
GetValue(out foo);

Şimdi değişkenimi önceden bildirmek ve kodumu iki satıra yazmak zorunda kalıyorum.

Güncelleme

Bu tür soruları sorarken bakmak için iyi bir yer .NET Framework Tasarım Yönergeleridir. Kitap sürümüne sahipseniz, Anders Hejlsberg ve diğerlerinin bu konudaki açıklamalarını görebilirsiniz (sayfa 184-185) ancak çevrimiçi sürüm burada ...

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

Kendinizi bir API'den iki şey döndürmeniz gerektiğini fark ederseniz, bunları bir yapı / sınıf içine sarmak, bir dış parametreden daha iyi olacaktır.


Harika cevap, özellikle geliştiricileri (yaygın olmayan) değişkenleri kullanmaya zorlayan (ortak) bir işlev olan TryParse'a referans.
Cerebrus

12

Daha outönce belirtilmemiş bir param kullanmanın bir nedeni vardır: çağıran yöntem onu ​​almak zorundadır. Yönteminiz, arayanın atmaması gereken bir değer üretirse, bu, arayanı outözellikle kabul etmeye zorlar:

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

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

Elbette arayan kişi bir parametredeki değeri görmezden gelebilir out, ancak siz dikkatini buna çektiniz.

Bu nadir bir ihtiyaçtır; daha sık olarak, gerçek bir sorun için bir istisna kullanmalı veya bir "FYI" için durum bilgisi içeren bir nesneyi iade etmelisiniz, ancak bunun önemli olduğu durumlar olabilir.


8

Esas olarak tercih

İadeleri tercih ederim ve birden fazla iadeniz varsa, bunları bir Sonuç DTO'suna sarabilirsiniz

public class Result{
  public Person Person {get;set;}
  public int Sum {get;set;}
}

5

Birden fazla çıkış parametreniz olabilirken yalnızca bir dönüş değeriniz olabilir.

Bu durumlarda sadece parametreleri göz önünde bulundurmanız gerekir.

Bununla birlikte, yönteminizden birden fazla parametre döndürmeniz gerekiyorsa, muhtemelen bir OO yaklaşımından ne döndürdüğünüze bakmak ve bu parametrelerle bir nesne veya yapı döndürmenin daha iyi olup olmadığını düşünmek istersiniz. Bu nedenle tekrar bir dönüş değerine döndünüz.


5

Neredeyse her zaman bir dönüş değeri kullanmalısınız. ' out' parametreler, birçok API, kompozisyon, vb. için biraz sürtünme yaratır.

Akla gelen en dikkat çekici istisna, birden fazla değer döndürmek istediğiniz zamandır (.Net Framework 4.0'a kadar tuple'lere sahip değildir), TryParseörüntü gibi.


Bunun iyi bir uygulama olup olmadığından emin değilim, ancak Arraylist birden fazla değer döndürmek için de kullanılabilir.
BA

@BA hayır, bu iyi bir uygulama değil, diğer kullanıcılar bu Dizi Listesinin ne içerdiğini veya hangi konumda olduklarını bilemezler. Örneğin, okunan bayt miktarını ve ayrıca değeri döndürmek istiyorum. out veya adlandırılmış tuples çok daha iyi olurdu. DiziListesi veya başka herhangi bir liste, yalnızca nesnelerin bir birleşimini, örneğin bir kişi listesini döndürmek istiyorsanız yararlı olacaktır.
sLw

2

Bu basit örnektekilerin yerine aşağıdakileri tercih ederim.

public int Value
{
    get;
    private set;
}

Ama hepsi hemen hemen aynı. Genellikle, yöntemden birden çok değeri geri göndermeleri gerekiyorsa, yalnızca 'out' kullanılır. Yöntemin içine ve dışına bir değer göndermek isterseniz, 'ref' seçilir. Yöntemim en iyisidir, yalnızca bir değer döndürüyorsanız, ancak bir parametreyi geçirmek ve bir değer geri almak istiyorsanız, muhtemelen ilk seçiminizi seçersiniz.


2

Bunun yararlı olacağı birkaç senaryodan birinin yönetilmeyen bellekle çalışırken olacağını düşünüyorum ve "döndürülen" değerin kendi başına elden çıkarılmasını beklemek yerine manuel olarak atılması gerektiğini açıkça belirtmek istiyorsunuz .


2

Ek olarak, dönüş değerleri zaman uyumsuz tasarım paradigmalarıyla uyumludur.

Ref veya out parametreleri kullanıyorsa bir işlevi "eşzamansız" belirleyemezsiniz.

Özetle, Dönüş Değerleri yöntem zincirine, daha temiz sözdizimine (arayanın ek değişkenler bildirme zorunluluğunu ortadan kaldırarak) ve gelecekte önemli değişikliklere gerek kalmadan asenkron tasarımlara izin verir.


"Eşzamansız" tanımında harika bir nokta. Başka birinin bundan bahsedeceğini düşünmüştüm. Zincirleme - ayrıca dönüş değerini (aslında işlevin kendisini) bir ifade olarak kullanmak başka bir önemli avantajdır. Bu gerçekten sadece bir süreçten ("kova" - Tuple / sınıf / yapı tartışması) bir şeyler nasıl geri alınacağını düşünmekle sürecin kendisini tek bir değerle ikame edilebilecek bir ifade gibi ele almak arasındaki farktır ( işlevin kendisi, çünkü yalnızca 1 değer döndürür).
galaxis

1

Her ikisinin de farklı bir amacı vardır ve derleyici tarafından aynı şekilde ele alınmaz. Yönteminizin bir değer döndürmesi gerekiyorsa, o zaman return kullanmalısınız. Out, yönteminizin birden çok değer döndürmesi gerektiğinde kullanılır.

Return kullanırsanız, veriler önce yöntem yığınına ve ardından çağıran yöntemin. Çıkış durumunda, doğrudan çağıran yöntemler yığınına yazılır. Başka farklılıklar olup olmadığından emin değilim.


Yöntemler yığın mı? Ben c # uzmanı değilim, ancak x86 iş parçacığı başına yalnızca bir yığını destekliyor. Yöntemin "çerçevesi" dönüş sırasında serbest bırakılır ve bir bağlam anahtarı gerçekleşirse, ayrılan yığının üzerine yazılabilir. C'de tüm dönüş değerleri kayıt defteri eaxına gider. Nesneleri / yapıları döndürmeyi seviyorsanız, bunların yığına atanması gerekir ve işaretçi eax'a konur.
Stefan Lundström

1

Diğerlerinin de söylediği gibi: dönüş değeri, param dışında değil.

Size "Çerçeve Tasarım Yönergeleri" (2. baskı) kitabını tavsiye edebilir miyim? 184-185. Sayfalarda, parametrelerden kaçınmanın nedenleri anlatılmaktadır. Kitabın tamamı, her tür .NET kodlama sorununda sizi doğru yöne yönlendirecektir.

Statik analiz aracı FxCop'un kullanımı Çerçeve Tasarım Yönergeleri ile birlikte gelir. Bunu Microsoft'un sitelerinde ücretsiz olarak indirebileceksiniz. Bunu derlenmiş kodunuzda çalıştırın ve ne yazdığını görün. Yüzlerce ve yüzlerce şeyden şikayet ediyorsa ... panik yapmayın! Her vaka hakkında söylediklerine sakince ve dikkatlice bakın. Bir an önce düzeltmek için acele etmeyin. Size söylediklerinden öğrenin. Üstatlığa giden yola çıkacaksınız.


1

Out anahtar sözcüğünü bir dönüş türü bool ile kullanmak bazen kod şişmesini azaltabilir ve okunabilirliği artırabilir. (Öncelikle, çıkış parametresindeki ekstra bilgi genellikle göz ardı edildiğinde.) Örneğin:

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;
        }
    }
}

1

Gerçek bir fark yok. Yöntemin birden fazla değer döndürmesine izin vermek için çıkış parametreleri C # içindedir, hepsi bu.

Bununla birlikte, bazı küçük farklılıklar vardır, ancak hiçbiri gerçekten önemli değildir:

Out parametresini kullanmak sizi aşağıdaki gibi iki satır kullanmaya zorlar:

int n;
GetValue(n);

dönüş değerini kullanırken bunu tek satırda yapmanıza izin verir:

int n = GetValue();

Diğer bir fark (yalnızca değer türleri için ve yalnızca C # işlevi satır içi yapmıyorsa doğrudur), işlev döndürüldüğünde dönüş değerinin kullanılması zorunlu olarak değerin bir kopyasını oluşturacağı, ancak OUT parametresinin kullanılması zorunlu olmayacağıdır.


0

yöntemde bildirdiğiniz bir nesneyi döndürmeye çalıştığınızda out daha kullanışlıdır.

Misal

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;
}

0

dönüş değeri , yönteminiz tarafından döndürülen normal değerdir.

Gibi olduğu üzerinden parametresi, iyi ve ref C # 2 anahtar kelimeler bu gibi değişkenleri geçmesine izin referans .

Arasındaki büyük fark ref ve out olduğu ref önce başlatıldı ve gereken dışarı yok


-2

Bu soruya bir göz atmayacağımdan şüpheleniyorum, ama ben çok deneyimli bir programcıyım ve umarım daha açık fikirli okuyuculardan bazıları ilgilenir.

Değer döndürme prosedürlerinin (VRP'ler) deterministik ve saf olması için nesne yönelimli programlama dillerine daha uygun olduğuna inanıyorum.

'VRP', bir ifadenin parçası olarak adlandırılan ve ifadenin değerlendirilmesi sırasında çağrının kavramsal olarak yerini alan bir dönüş değerine sahip bir işlevin modern akademik adıdır. Örneğin x = 1 + f(y), işlev gibi bir ifadede fVRP olarak hizmet veriyor.

'Belirleyici', işlevin sonucunun yalnızca parametrelerinin değerlerine bağlı olduğu anlamına gelir. Aynı parametre değerleri ile tekrar çağırırsanız, aynı sonucu alacağınız kesindir.

'Saf', yan etki olmadığı anlamına gelir: işlevi çağırmak , sonucu hesaplamak dışında hiçbir şey yapmaz . Bu önemli olmadığı şeklinde yorumlanabilir pratikte yan etkinin , bu nedenle eğer VRP her çağrıldığında bir hata giderme mesajı verirse, örneğin, bu muhtemelen göz ardı edilebilir.

Dolayısıyla, C # 'da işleviniz deterministik ve saf değilse, onu bir voidişlev yapmanız gerektiğini (başka bir deyişle, bir VRP değil) ve döndürmesi gereken herhangi bir değerin bir outveya bir refparametrede döndürülmesi gerektiğini söylüyorum .

Örneğin, bir veritabanı tablosundan bazı satırları silmek için bir işleviniz varsa ve sildiği satır sayısını döndürmesini istiyorsanız, bunu şöyle bir şekilde açıklamalısınız:

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

Bazen bu işlevi çağırmak istiyor ama alamıyorsanız, counther zaman bir aşırı yükleme bildirebilirsiniz.

Bu tarzın neden nesne yönelimli programlamaya daha çok uyduğunu bilmek isteyebilirsiniz . Genel olarak, (biraz kesin olmayan bir şekilde) 'prosedürel programlama' olarak adlandırılabilecek bir programlama tarzına uyar ve nesne yönelimli programlamaya daha iyi uyan bir prosedürel programlama stilidir.

Neden? Nesnelerin klasik modeli, özelliklere (diğer adıyla özniteliklere) sahip olmaları ve nesneyi (esas olarak) bu özellikleri okuyup güncelleyerek sorgulayıp manipüle etmenizdir. Bir yordamsal programlama stili bunu yapmayı kolaylaştırma eğilimindedir, çünkü özellikleri alan ve ayarlayan işlemler arasında rastgele kod çalıştırabilirsiniz.

Prosedürel programlamanın dezavantajı, her yerde rastgele kod çalıştırabildiğiniz için, küresel değişkenler ve yan etkiler yoluyla bazı çok geniş kapsamlı ve hatalara açık etkileşimler elde edebilirsiniz.

Bu nedenle, oldukça basit bir şekilde, kodunuzu okuyan birine bir işlevin, onu değersiz hale getirerek yan etkilere sahip olabileceği sinyalini vermek iyi bir uygulamadır .


> Bazen bu işlevi çağırmak istiyor ama sayıyı alamıyorsanız, her zaman bir aşırı yükleme bildirebilirsiniz. C # sürüm 7'de (sanırım) ve sonrasında, _bir out parametresini yok saymak için at sembolünü kullanabilirsiniz, örneğin: DeleteBasketItems (kategori, dışarı _);
tartışmacı
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.