Yöntemleri C # 4.0'da aşırı yüklemeleri veya isteğe bağlı parametreleri kullanarak mı bildirmelisiniz?


94

Anders'in C # 4.0 hakkında konuşmasını ve C # 5.0'ın gizli bir önizlemesini izliyordum ve bu, C # ' da isteğe bağlı parametrelerin ne zaman mevcut olduğunu düşünmemi sağladı, belirtilen tüm parametrelere ihtiyaç duymayan yöntemleri bildirmek için önerilen yol nedir?

Örneğin, FileStreamsınıf gibi bir şey , mantıksal 'ailelere' bölünebilen yaklaşık on beş farklı kurucuya sahiptir, örneğin aşağıdakiler bir dizeden, bir'den olanlar IntPtrve a'dan olanlar SafeFileHandle.

FileStream(string,FileMode);
FileStream(string,FileMode,FileAccess);
FileStream(string,FileMode,FileAccess,FileShare);
FileStream(string,FileMode,FileAccess,FileShare,int);
FileStream(string,FileMode,FileAccess,FileShare,int,bool);

Bana öyle geliyor ki, bu tür bir model yerine üç kurucuya sahip olmak ve varsayılan olabilenler için isteğe bağlı parametreler kullanmak suretiyle basitleştirilebilir, bu da farklı kurucu ailelerini daha belirgin hale getirir [not: Bu değişikliğin olmayacağını biliyorum BCL'de yapıldı, bu tür durumlar için varsayımsal olarak konuşuyorum].

Ne düşünüyorsun? C # 4.0'dan itibaren, yakından ilişkili kurucu gruplarını ve yöntemleri isteğe bağlı parametrelerle tek bir yöntem haline getirmek daha mantıklı olacak mı, yoksa geleneksel çok yükleme mekanizmasına bağlı kalmak için iyi bir neden var mı?

Yanıtlar:


123

Aşağıdakileri düşünürdüm:

  • Kodunuzun isteğe bağlı parametreleri desteklemeyen dillerde kullanılmasına mı ihtiyacınız var? Eğer öyleyse, aşırı yüklenmeleri dahil etmeyi düşünün.
  • Ekibinizde isteğe bağlı parametrelere şiddetle karşı çıkan üyeleriniz var mı? (Bazen hoşunuza gitmeyen bir kararla yaşamak, davayı tartışmaktan daha kolaydır.)
  • Varsayılanlarınızın kodunuzun yapıları arasında değişmeyeceğinden emin misiniz, yoksa arayanlar buna razı olacak mı?

Varsayılanların nasıl çalışacağını kontrol etmedim, ancak varsayılan değerlerin constalanlara yapılan başvurularla aynı şekilde arama koduna ekleneceğini varsayıyorum . Bu genellikle sorun değildir - varsayılan bir değere yapılan değişiklikler yine de oldukça önemlidir - ancak dikkate alınması gereken şeyler bunlar.


22
Pragmatizm hakkındaki bilgelik için +1: Bazen hoşunuza gitmeyen bir kararla yaşamak, durumu tartışmaktan daha kolaydır.
legends2k

13
@romkyns: Hayır, aşırı yüklemelerin etkisi 3. nokta ile aynı şey değildir. Varsayılanları sağlayan aşırı yüklemelerle, varsayılanlar kitaplık kodundadır - bu nedenle, varsayılanı değiştirirseniz ve kitaplığın yeni bir sürümünü sağlarsanız, arayanlar yeniden derleme olmadan yeni varsayılanı görün. Oysa isteğe bağlı parametrelerle, yeni varsayılanları "görmek" için yeniden derlemeniz gerekir. Çok zaman bu önemli bir ayrımdır değil, ama olan bir ayrım.
Jon Skeet

merhaba @JonSkeet, hem ie işlevini isteğe bağlı paramater ile hem de aşırı yükleme ile diğerini kullanıp kullanmayacağımızı bilmek istiyorum hangi yöntemin çağrılacağı ?? örneğin Add (int a, int b) ve Add (int a, int b, int c = 0) ve fonksiyon çağrısı şöyle diyor: Add (5,10); hangi yöntem aşırı yüklenmiş işlev veya isteğe bağlı parametre işlevi olarak adlandırılır? teşekkürler :)
SHEKHAR SHETE

@Shekshar: Bunu denedin mi? Ayrıntılar için spesifikasyonu okuyun, ancak temelde bir bağ kırıcıda, derleyicinin herhangi bir isteğe bağlı parametreyi doldurmak zorunda olmadığı bir yöntem kazanır.
Jon Skeet

@JonSkeet az önce yukarıda denedim ... aşırı fonksiyon isteğe bağlı parametreyi kazanır :)
SHEKHAR SHETE

19

Bir yöntem aşırı yüklemesi normalde aynı şeyi farklı sayıda bağımsız değişkenle gerçekleştirdiğinde varsayılanlar kullanılacaktır.

Bir yöntem aşırı yüklemesi, parametrelerine bağlı olarak bir işlevi farklı şekilde gerçekleştirdiğinde, aşırı yükleme kullanılmaya devam edecektir.

VB6 günlerimde isteğe bağlı olarak geri kullandım ve o zamandan beri kaçırdım, C # 'da çok sayıda XML yorum kopyasını azaltacak.


11

Delphi'yi sonsuza kadar isteğe bağlı parametrelerle kullanıyorum. Onun yerine aşırı yük kullanmaya geçtim.

Çünkü daha fazla aşırı yükleme oluşturmaya gittiğinizde, her zaman isteğe bağlı bir parametre formuyla çakışacaksınız ve sonra bunları isteğe bağlı olmayana dönüştürmeniz gerekecek.

Ve genel olarak bir süper yöntemin olduğu fikrini seviyorum ve geri kalanı bunun etrafında daha basit sarmalayıcılar.


1
Buna çok katılıyorum, ancak birden fazla (3+) parametre alan, doğası gereği tamamen "isteğe bağlı" (bir varsayılanla ikame edilebilir) bir yönteme sahip olduğunuzda birçok permütasyon elde edebileceğiniz konusunda uyarı var. daha fazla fayda sağlamayan yöntem imzası. Düşünün Foo(A, B, C)gerektirir Foo(A), Foo(B), Foo(C), Foo(A, B), Foo(A, C), Foo(B, C).
Dan Lugg

7

4.0'ın opsiyonel parametreler özelliğini kesinlikle kullanacağım. Saçma sapan şeylerden kurtulur ...

public void M1( string foo, string bar )
{
   // do that thang
}

public void M1( string foo )
{
  M1( foo, "bar default" ); // I have always hated this line of code specifically
}

... ve değerleri arayanın görebileceği yerlere koyar ...

public void M1( string foo, string bar = "bar default" )
{
   // do that thang
}

Çok daha basit ve hataya daha az meyilli. Aslında bunu aşırı yük durumunda bir hata olarak gördüm ...

public void M1( string foo )
{
   M2( foo, "bar default" );  // oops!  I meant M1!
}

Henüz 4.0 derleyicisi ile oynamadım, ancak derleyicinin basitçe sizin için aşırı yükleri yaydığını öğrenmek beni şaşırtmaz.


6

İsteğe bağlı parametreler, temelde, çağrı sitesine uygun varsayılanları eklemek için bir yöntem çağrısını işleyen bir derleyiciyi yönlendiren bir meta veri parçasıdır. Aksine, aşırı yüklemeler, bir derleyicinin, bazıları varsayılan değerleri kendileri sağlayabilecek birkaç yöntemden birini seçebileceği bir yol sağlar. Biri, onları desteklemeyen bir dilde yazılmış koddan isteğe bağlı parametreleri belirten bir yöntemi çağırmaya çalışırsa, derleyicinin "isteğe bağlı" parametrelerin belirtilmesini gerektireceğini, ancak isteğe bağlı bir parametre belirtmeden bir yöntemi çağırmanın varsayılan değere eşit bir parametreyle çağırmaya eşdeğer, bu tür dillerin bu tür yöntemleri çağırmasının önünde bir engel yoktur.

Çağrı sitesinde isteğe bağlı parametrelerin bağlanmasının önemli bir sonucu, bunlara derleyici için mevcut olan hedef kod sürümüne dayalı olarak değerler atanacak olmasıdır. Bir derlemenin varsayılan değeri 5 Fooolan bir yöntemi varsa Boo(int)ve derleme için Barbir çağrı varsa Foo.Boo(), derleyici bunu bir Foo.Boo(5). Varsayılan değer 6 olarak değiştirilirse ve derleme Fooyeniden derlenirse, yeni sürümüyle yeniden derlenmedikçe veya derlenene kadar Bararamaya devam eder . Bu nedenle, değişebilecek şeyler için isteğe bağlı parametreleri kullanmaktan kaçınılmalıdır.Foo.Boo(5)Foo


Ynt: "Bu nedenle, değişebilecek şeyler için isteğe bağlı parametreleri kullanmaktan kaçınılmalıdır." Değişikliğin müşteri kodu tarafından fark edilmemesi durumunda bunun sorunlu olabileceğini kabul ediyorum. Varsayılan değer bir yöntem aşırı içine gizlenmiş Ancak, aynı sorun var: void Foo(int value) … void Foo() { Foo(42); }. Dışarıdan arayan kişi hangi varsayılan değerin kullanıldığını veya ne zaman değişebileceğini bilmez; bunun için yazılı belgeleri izlemek gerekir. İsteğe bağlı parametreler için varsayılan değerler sadece şu şekilde görülebilir: kod içinde dokümantasyon varsayılan değer nedir.
stakx -

@stakx: Parametresiz bir aşırı yük, bir parametreyle bir aşırı yüklemeye zincirlenirse, bu parametrenin "varsayılan" değerini değiştirmek ve aşırı yük tanımını yeniden derlemek , çağıran kod yeniden derlenmemiş olsa bile kullandığı değeri değiştirecektir .
süper araba

Doğru ama bu onu alternatiften daha sorunlu yapmaz. Bir durumda (yöntem aşırı yüklemesi), çağıran kodun varsayılan değerde söz hakkı yoktur. Bu, arayan kodun isteğe bağlı parametre ve ne anlama geldiğini gerçekten umursamıyorsa uygun olabilir. Diğer durumda (varsayılan değere sahip isteğe bağlı parametre), önceden derlenmiş çağrı kodu, varsayılan değer değiştiğinde etkilenmez. Bu, çağıran kod gerçekten parametreyi önemsediğinde de uygun olabilir; bunu kaynakta çıkarmak, "şu anda önerilen varsayılan değer benim için uygundur" demek gibidir.
stakx -

Burada belirtmeye çalıştığım nokta, her iki yaklaşımın da sonuçları olsa da (sizin belirttiğiniz gibi), doğası gereği avantajlı veya dezavantajlı olmadıklarıdır. Bu aynı zamanda arayan kodun ihtiyaçlarına ve hedeflerine de bağlıdır. Bu görüşe göre, cevabınızın son cümlesindeki kararı biraz fazla katı buldum.
stakx -

@stakx: "Asla kullanma" yerine "kullanmaktan kaçının" dedim. X'in değiştirilmesi Y'nin bir sonraki yeniden derlemesinin Y'nin davranışını değiştireceği anlamına geliyorsa, bu ya bir derleme sisteminin, X'in her yeniden derlemesinin de Y'yi yeniden derleyeceği şekilde yapılandırılmasını gerektirecek (işleri yavaşlatacak) ya da bir programcının değişmesi riskini yaratacaktır. X, bir dahaki sefere derlendiğinde Y'yi kıracak bir şekilde ve bu kırılmayı ancak daha sonra Y tamamen ilgisiz bir nedenle değiştirildiğinde keşfeder. Varsayılan parametreler, yalnızca avantajları bu tür maliyetlere ağır bastığında kullanılmalıdır.
supercat

4

İsteğe bağlı argümanların veya aşırı yüklemelerin kullanılıp kullanılmayacağı tartışılabilir, ancak en önemlisi, her birinin yeri doldurulamayacakları kendi alanları vardır.

İsteğe bağlı bağımsız değişkenler, adlandırılmış bağımsız değişkenlerle birlikte kullanıldığında, COM çağrılarının bazı uzun bağımsız değişken listeleri ile tümü isteğe bağlı olarak birleştirildiğinde son derece yararlıdır.

Aşırı yükler, yöntem birçok farklı argüman türünde çalışabildiğinde (örneklerden sadece biri) ve örneğin dökümleri dahili olarak yaptığında son derece yararlıdır; sadece mantıklı olan herhangi bir veri türü ile beslersiniz (bu, bazı mevcut aşırı yükler tarafından kabul edilir). Bunu isteğe bağlı argümanlarla yenemezsiniz.


3

İsteğe bağlı parametreleri dört gözle bekliyorum çünkü varsayılanları yönteme daha yakın tutuyor. Dolayısıyla, yalnızca "genişletilmiş" yöntemi çağıran aşırı yükler için düzinelerce satır yerine, yöntemi yalnızca bir kez tanımlarsınız ve yöntem imzasında isteğe bağlı parametrelerin varsayılan olarak ne olduğunu görebilirsiniz. Bakmayı tercih ederim:

public Rectangle (Point start = Point.Zero, int width, int height)
{
    Start = start;
    Width = width;
    Height = height;
}

Bunun yerine:

public Rectangle (Point start, int width, int height)
{
    Start = start;
    Width = width;
    Height = height;
}

public Rectangle (int width, int height) :
    this (Point.Zero, width, height)
{
}

Açıkçası bu örnek gerçekten çok basit, ancak 5 aşırı yüklemeli OP'deki durum, işler çok hızlı bir şekilde kalabalıklaşabilir.


7
İsteğe bağlı parametrelerin en son olması gerektiğini duydum, değil mi?
Ilya Ryzhenkov

Tasarımınıza bağlıdır. Belki de 'başlangıç' argümanı, olmadığı zamanlar dışında normalde önemlidir. Belki başka bir yerde aynı imzaya sahipsiniz, yani farklı bir şey. Uydurma bir örnek için, public Rectangle (int width, int height, Point innerSquareStart, Point innerSquareEnd) {}
Robert P

13
Konuşmada söylediklerine göre, isteğe bağlı parametreler gerekli parametrelerden sonra gelmelidir.
Greg Beech

3

İsteğe bağlı parametrelerin en sevdiğim yönlerinden biri, yöntem tanımına gitmeden bile, bunları sağlamazsanız parametrelerinize ne olacağını görmenizdir. Visual Studio , yöntem adını yazdığınızda size parametrenin varsayılan değerini gösterecektir . Bir aşırı yükleme yöntemiyle, belgeleri okumak (mevcut olsa bile) veya doğrudan yöntemin tanımına (varsa) ve aşırı yüklemenin sardığı yönteme gitmek zorunda kalırsınız.

Özellikle: aşırı yükleme miktarıyla birlikte dokümantasyon çabası hızla artabilir ve muhtemelen mevcut aşırı yüklemelerden zaten var olan yorumları kopyalamakla sonuçlanacaksınız. Bu, herhangi bir değer üretmediği ve KURU prensibini ihlal ettiği için oldukça can sıkıcıdır ). Öte yandan, isteğe bağlı bir parametre ile, tüm parametrelerin belgelendiği tam olarak tek bir yer vardır ve yazarken anlamlarının yanı sıra varsayılan değerlerini de görürsünüz .

Son olarak, en az değil, bir API'nin tüketicisiyseniz, uygulama ayrıntılarını inceleme seçeneğiniz bile olmayabilir (kaynak kodunuz yoksa) ve bu nedenle, aşırı yüklenmiş olanların hangi süper yönteme yönelik olduğunu görme şansınız olmayabilir. sarılıyor. Bu nedenle, belgeyi okumakta ve tüm varsayılan değerlerin burada listelenmesini umuyorsunuz, ancak bu her zaman böyle değildir.

Elbette bu, tüm yönleri ele alan bir cevap değil, ancak şimdiye kadar ele alınmayan bir cevabı eklediğini düşünüyorum.


1

Bunlar (sözde?) API'nizi sıfırdan modellemeniz için kavramsal olarak eşdeğer iki yol olsa da, ne yazık ki eski istemcileriniz için çalışma zamanı geriye dönük uyumluluğunu vahşi doğada düşünmeniz gerektiğinde bazı ince farkları var. Meslektaşım (teşekkürler Brent!) Beni şu harika gönderiye işaret etti : İsteğe bağlı argümanlarla sorunları versiyonlama . Ondan bazı alıntılar:

İsteğe bağlı parametrelerin ilk etapta C # 4'e tanıtılmasının nedeni COM birlikte çalışmayı desteklemekti. Bu kadar. Ve şimdi, bu gerçeğin tüm sonuçlarını öğreniyoruz. İsteğe bağlı parametrelere sahip bir yönteminiz varsa, derleme zamanı bozulma değişikliğine neden olma korkusuyla ek isteğe bağlı parametrelerle asla aşırı yük ekleyemezsiniz. Ve mevcut bir aşırı yüklemeyi asla kaldıramazsınız, çünkü bu her zaman bir çalışma zamanı değişikliği değişikliği olmuştur. Hemen hemen bir arayüz gibi davranmanız gerekiyor. Bu durumda tek başvurunuz, yeni bir isimle yeni bir yöntem yazmaktır. API'lerinizde isteğe bağlı bağımsız değişkenler kullanmayı planlıyorsanız, bunun farkında olun.


1

İsteğe bağlı parametrelerin bir uyarısı, bir refaktörün istenmeyen sonuçlara sahip olduğu sürümlemedir. Bir örnek:

İlk kod

public string HandleError(string message, bool silent=true, bool isCritical=true)
{
  ...
}

Bunun, yukarıdaki yöntemi arayan birçok kişiden biri olduğunu varsayalım:

HandleError("Disk is full", false);

Burada olay sessiz değildir ve kritik olarak değerlendirilir.

Şimdi diyelim ki, bir refaktörden sonra, tüm hataların yine de kullanıcıyı yönlendirdiğini gördük, bu yüzden artık sessiz bayrağa ihtiyacımız yok. Bu yüzden onu kaldırıyoruz.

Yeniden düzenlemeden sonra

Önceki çağrı hala derleniyor ve diyelim ki refaktörden değişmeden kayıyor:

public string HandleError(string message, /*bool silent=true,*/ bool isCritical=true)
{
  ...
}

...

// Some other distant code file:
HandleError("Disk is full", false);

Artık falseistenmeyen bir etkiye sahip olacak, olay artık kritik olarak değerlendirilmeyecek.

Derleme veya çalışma zamanı hatası olmayacağından ( bu veya bunun gibi isteğe bağlı diğer bazı uyarıların aksine) bu, ince bir kusurla sonuçlanabilir .

Bu aynı sorunun birçok şekli olduğunu unutmayın. Bir başka form burada özetlenmiştir .

Böyle böyle olduğu gibi, yöntem sorunu önlemek olacaktır çağrılırken kesinlikle adlandırılmış parametreleri kullanarak da unutmayın: HandleError("Disk is full", silent:false). Ancak, diğer tüm geliştiricilerin (veya genel bir API'nin kullanıcılarının) bunu yapacağını varsaymak pratik olmayabilir.

Bu nedenlerden ötürü, başka zorlayıcı hususlar olmadıkça, genel bir API'de isteğe bağlı parametreleri kullanmaktan kaçınırım (veya yaygın olarak kullanılıyorsa halka açık bir yöntem bile).


0

Her iki İsteğe bağlı parametre, Yöntem aşırı yüklemesinin kendi avantajı veya dezavantajı vardır. Bunlar arasında seçim yapma tercihinize bağlıdır.

İsteğe Bağlı Parametre: yalnızca .Net 4.0'da mevcuttur. isteğe bağlı parametre kod boyutunu azaltır. Dışarı ve ref parametresini tanımlayamazsınız

aşırı yüklenmiş yöntemler: Out ve ref parametrelerini tanımlayabilirsiniz. Kod boyutu artacaktır ancak aşırı yüklenmiş yöntemlerin anlaşılması kolaydır.


0

Çoğu durumda yürütmeyi değiştirmek için isteğe bağlı parametreler kullanılır. Örneğin:

decimal GetPrice(string productName, decimal discountPercentage = 0)
{

    decimal basePrice = CalculateBasePrice(productName);

    if (discountPercentage > 0)
        return basePrice * (1 - discountPercentage / 100);
    else
        return basePrice;
}

Buradaki indirim parametresi, if-then-else ifadesini beslemek için kullanılır. Tanınmayan polimorfizm var ve sonra eğer-ise-değilse ifadesi olarak uygulandı. Bu tür durumlarda, iki kontrol akışını iki bağımsız yönteme ayırmak çok daha iyidir:

decimal GetPrice(string productName)
{
    decimal basePrice = CalculateBasePrice(productName);
    return basePrice;
}

decimal GetPrice(string productName, decimal discountPercentage)
{

    if (discountPercentage <= 0)
        throw new ArgumentException();

    decimal basePrice = GetPrice(productName);

    decimal discountedPrice = basePrice * (1 - discountPercentage / 100);

    return discountedPrice;

}

Böylelikle sınıfı sıfır indirimle çağrı almaktan bile koruduk. Bu arama, arayanın indirim olduğunu düşündüğü anlamına gelir, ancak aslında hiçbir indirim yoktur. Böyle bir yanlış anlama, kolaylıkla bir hataya neden olabilir.

Bu gibi durumlarda, isteğe bağlı parametrelere sahip olmayı tercih etmiyorum, ancak arayanı mevcut durumuna uygun yürütme senaryosunu açıkça seçmeye zorlamak istiyorum.

Durum, boş olabilen parametrelere sahip olmaya çok benzer. Uygulama gibi ifadeler kaynadığında bu eşit derecede kötü bir fikir if (x == null).

Bu bağlantılarda ayrıntılı analiz bulabilirsiniz: İsteğe Bağlı Parametrelerden Kaçınma ve Boş Parametrelerden Kaçınma


0

İsteğe bağlı seçenekler yerine bir aşırı yükün ne zaman kullanılacağını düşünmeden eklemek için:

Yalnızca birlikte anlamlı olan bir dizi parametreniz olduğunda, bunlara isteğe bağlı özellikler eklemeyin.

Veya daha genel olarak, yöntem imzalarınız anlamsız kullanım modellerini etkinleştirdiğinde, olası çağrıların permütasyon sayısını kısıtlayın. Örneğin, isteğe bağlı ifadeler yerine aşırı yüklemeler kullanarak (bu kural, aynı veri türünde birkaç parametreniz olduğunda da geçerlidir; burada fabrika yöntemleri veya özel veri türleri gibi cihazlar yardımcı olabilir).

Misal:

enum Match {
    Regex,
    Wildcard,
    ContainsString,
}

// Don't: This way, Enumerate() can be called in a way
//         which does not make sense:
IEnumerable<string> Enumerate(string searchPattern = null,
                              Match match = Match.Regex,
                              SearchOption searchOption = SearchOption.TopDirectoryOnly);

// Better: Provide only overloads which cannot be mis-used:
IEnumerable<string> Enumerate(SearchOption searchOption = SearchOption.TopDirectoryOnly);
IEnumerable<string> Enumerate(string searchPattern, Match match,
                              SearchOption searchOption = SearchOption.TopDirectoryOnly);
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.