C # 4.0: Bir TimeSpan'i varsayılan bir değere sahip isteğe bağlı bir parametre olarak kullanabilir miyim?


125

Bunların her ikisi de bir derleme zamanı sabiti olmaları gerektiğini söyleyen bir hata oluşturur:

void Foo(TimeSpan span = TimeSpan.FromSeconds(2.0))
void Foo(TimeSpan span = new TimeSpan(2000))

Her şeyden önce, birisi bu değerlerin neden derleme zamanında belirlenemediğini açıklayabilir mi? Ve isteğe bağlı bir TimeSpan nesnesi için varsayılan bir değer belirlemenin bir yolu var mı?


11
Ne sorduğunuzla ilgili değil, ancak bunun new TimeSpan(2000)2000 milisaniye anlamına gelmediğini unutmayın, bu, 0,2 milisaniye veya iki saniyenin 10.000'de biri olan 2000 "tıklama" anlamına gelir.
Jeppe Stig Nielsen

Yanıtlar:


173

İmzanızı değiştirerek bu sorunu kolayca çözebilirsiniz.

void Foo(TimeSpan? span = null) {

   if (span == null) { span = TimeSpan.FromSeconds(2); }

   ...

}

Detaylandırmalıyım - örneğinizdeki bu ifadelerin derleme zamanı sabitleri olmamasının nedeni derleme zamanında derleyicinin sadece TimeSpan.FromSeconds (2.0) 'ı çalıştırıp sonucun baytlarını derlenmiş kodunuza yapıştıramamasıdır.

Örnek olarak bunun yerine DateTime.Now kullanmayı denediğinizi düşünün. DateTime.Now'un değeri her çalıştırıldığında değişir. Veya TimeSpan.FromSeconds'ın yerçekimini hesaba kattığını varsayalım. Bu saçma bir örnek, ancak derleme zamanı sabitlerinin kuralları özel durumlar oluşturmaz çünkü TimeSpan.FromSeconds'un deterministik olduğunu biliyoruz.


15
<param>İmzada görünmediği için şimdi varsayılan değeri olarak belgeleyin .
Albay Panic

3
Bunu yapamam, başka bir şey için özel null değerini kullanıyorum.
Albay Panic

4
@MattHickford - O zaman aşırı yüklenmiş bir yöntem sağlamanız veya parametre olarak milisaniyeleri almanız gerekir.
Josh

19
span = span ?? TimeSpan.FromSeconds(2.0);Yöntem gövdesinde null yapılabilir türle de kullanılabilir . Veya var realSpan = span ?? TimeSpan.FromSeconds(2.0);boş değer atanamayan yerel bir değişken almak için.
Jeppe Stig Nielsen

5
Bundan hoşlanmadığım şey, işlevin kullanıcısına bu işlevin boş bir aralıkla "çalıştığını" ima etmesidir. Ama bu doğru değil! Null, işlevin gerçek mantığı söz konusu olduğunda span için geçerli bir değer değildir . Keşke kod kokusu gibi görünmeyen daha iyi bir yol olsaydı ...
JoeCool

31

VB6 mirasım, "boş değer" ve "eksik değer" in eşdeğer olduğunu düşünme fikri beni rahatsız ediyor. Çoğu durumda, muhtemelen iyidir, ancak istenmeyen bir yan etkiniz olabilir veya istisnai bir koşulu yutabilirsiniz (örneğin, kaynağı spanboş olmaması gereken bir özellik veya değişkense).

Bu nedenle yöntemi aşırı yüklerim:

void Foo()
{
    Foo(TimeSpan.FromSeconds(2.0));
}
void Foo(TimeSpan span)
{
    //...
}

1
Bu harika teknik için +1. Varsayılan parametreler gerçekten yalnızca const türleriyle kullanılmalıdır. Aksi halde güvenilmezdir.
Lazlo

2
Bu, varsayılan değerlerin değiştirildiği 'zamana saygı duyulan' yaklaşımdır ve bu durum için bunun en az çirkin cevap olduğunu düşünüyorum;) Kendi başına, arayüzler için o kadar iyi çalışmayabilir, çünkü gerçekten varsayılan değeri bir yer. Bu durumda, uzantı yöntemlerinin yararlı bir araç olduğunu buldum: arabirim, tüm parametreleri içeren bir yönteme sahiptir, ardından arabirimin yanında statik bir sınıfta bildirilen bir dizi uzantı yöntemi, çeşitli aşırı yüklemelerde varsayılanları uygular.
OlduwanSteve

23

Bu iyi çalışıyor:

void Foo(TimeSpan span = default(TimeSpan))


4
Stack Overflow'a hoş geldiniz. Cevabınız bunu görünmektedir olabilir yeter ki derleyici izin verdiğini biri çok özel bir değer olduğu gibi, varsayılan bir parametre değeri sağlarlar. Bunu doğru anladım mı? (Bunu yapabilirsiniz düzenlemek netleştirmek için cevabınızı.) O derleyici soru başlangıçta keyfi var olan, aranan şeyi getirmeye izin verdiklerini yararlanmak nasıl gösterdi, bu daha iyi bir yanıt olacağını diğer TimeSpan tür tarafından verilen bu gibi değerleri, new TimeSpan(2000).
Rob Kennedy

2
Belirli bir varsayılan değer kullanan bir alternatif, bir zaman aralığı alan varsayılan kurucu ve yapıcı ile birlikte özel bir statik salt okunur TimeSpan defaultTimespan = Timespan.FromSeconds (2) kullanmaktır. public Foo (): this (defaultTimespan) ve public Foo (
Timespan

15

Varsayılan değer olarak kullanılabilen değerler kümesi, bir öznitelik bağımsız değişkeni için kullanılabilenlerle aynıdır. Bunun nedeni, varsayılan değerlerin meta verilere kodlanmış DefaultParameterValueAttribute.

Derleme zamanında neden belirlenemediğine gelince. Derleme zamanında izin verilen bu tür değerlerin üzerindeki değerler ve ifadeler, resmi C # dil spesifikasyonunda listelenmiştir :

C # 6.0 - Öznitelik parametresi türleri :

Bir öznitelik sınıfı için konumsal ve adlandırılmış parametrelerin türleri , öznitelik parametresi türleri ile sınırlıdır , bunlar:

  • Aşağıdaki türlerden biri: bool, byte, char, double, float, int, long, sbyte, short, string, uint, ulong, ushort.
  • Tür object.
  • Tür System.Type.
  • Enum türü.
    (genel erişilebilirliğe sahip olması ve iç içe geçtiği türlerin (varsa) de genel erişilebilirliğe sahip olması koşuluyla)
  • Yukarıdaki türlerin tek boyutlu dizileri.

Tür TimeSpan, bu listelerin hiçbirine uymadığından sabit olarak kullanılamaz.


2
Hafif nit-pick: Statik bir yöntemi çağırmak listelerin hiçbirine uymaz. TimeSpanbu listedeki sonuncuya sığabilir default(TimeSpan)geçerlidir.
CodesInChaos

12
void Foo(TimeSpan span = default(TimeSpan))
{
    if (span == default(TimeSpan)) 
        span = TimeSpan.FromSeconds(2); 
}

sağlanan default(TimeSpan), işlev için geçerli bir değer değil.

Veya

//this works only for value types which TimeSpan is
void Foo(TimeSpan span = new TimeSpan())
{
    if (span == new TimeSpan()) 
        span = TimeSpan.FromSeconds(2); 
}

sağlanan new TimeSpan()geçerli bir değer değil.

Veya

void Foo(TimeSpan? span = null)
{
    if (span == null) 
        span = TimeSpan.FromSeconds(2); 
}

Bu null, işlev için geçerli bir değer olma şansının nadir olduğu düşünüldüğünde daha iyi olmalıdır .


4

TimeSpaniçin özel bir durumdur DefaultValueAttributeve TimeSpan.Parseyöntem aracılığıyla ayrıştırılabilen herhangi bir dizge kullanılarak belirtilir .

[DefaultValue("0:10:0")]
public TimeSpan Duration { get; set; }

3

Benim önerim:

void A( long spanInMs = 2000 )
{
    var ts = TimeSpan.FromMilliseconds(spanInMs);

    //...
}

BTW TimeSpan.FromSeconds(2.0)eşit değildir new TimeSpan(2000)- kurucu işaretler alır.


2

Diğer yanıtlar , isteğe bağlı bir parametrenin neden dinamik bir ifade olamayacağına dair harika açıklamalar verdi. Ancak, anlatmak gerekirse, varsayılan parametreler derleme zamanı sabitleri gibi davranır. Bu, derleyicinin bunları değerlendirip bir cevap bulabilmesi gerektiği anlamına gelir. Sürekli bildirimlerle karşılaşıldığında dinamik ifadeleri değerlendiren derleyiciye C # desteği eklemesini isteyen bazı insanlar var — bu tür bir özellik, yöntemleri "saf" olarak işaretlemeyle ilgili olabilir, ancak bu şu anda bir gerçeklik değildir ve asla olmayabilir.

Böyle bir yöntem için bir C # varsayılan parametresi kullanmanın bir alternatifi, ile örneklenen kalıbı kullanmak olacaktır XmlReaderSettings. Bu modelde, parametresiz bir kurucuya ve herkese açık olarak yazılabilir özelliklere sahip bir sınıf tanımlayın. Ardından, yönteminizdeki tüm seçenekleri varsayılanlarla bu tür bir nesneyle değiştirin. Hatta nullbunun için bir varsayılan belirterek bu nesneyi isteğe bağlı hale getirin. Örneğin:

public class FooSettings
{
    public TimeSpan Span { get; set; } = TimeSpan.FromSeconds(2);

    // I imagine that if you had a heavyweight default
    // thing you’d want to avoid instantiating it right away
    // because the caller might override that parameter. So, be
    // lazy! (Or just directly store a factory lambda with Func<IThing>).
    Lazy<IThing> thing = new Lazy<IThing>(() => new FatThing());
    public IThing Thing
    {
        get { return thing.Value; }
        set { thing = new Lazy<IThing>(() => value); }
    }

    // Another cool thing about this pattern is that you can
    // add additional optional parameters in the future without
    // even breaking ABI.
    //bool FutureThing { get; set; } = true;

    // You can even run very complicated code to populate properties
    // if you cannot use a property initialization expression.
    //public FooSettings() { }
}

public class Bar
{
    public void Foo(FooSettings settings = null)
    {
        // Allow the caller to use *all* the defaults easily.
        settings = settings ?? new FooSettings();

        Console.WriteLine(settings.Span);
    }
}

Çağrı yapmak için, özellikleri tek bir ifadede örneklemek ve atamak için bu garip sözdizimini kullanın:

bar.Foo(); // 00:00:02
bar.Foo(new FooSettings { Span = TimeSpan.FromDays(1), }); // 1.00:00:00
bar.Foo(new FooSettings { Thing = new MyCustomThing(), }); // 00:00:02

Downsides

Bu, bu sorunu çözmek için gerçekten ağır bir yaklaşımdır. Hızlı ve kirli bir dahili arabirim yazıyorsanız ve null yapılabilir hale getiriyorsanız ve TimeSpannull'u istediğiniz varsayılan değerin iyi çalışacağı gibi davranıyorsanız , bunun yerine bunu yapın.

Ayrıca, çok sayıda parametreniz varsa veya yöntemi sıkı bir döngüde çağırıyorsanız, bu, sınıf somutlaştırmalarının ek yüküne sahip olacaktır. Elbette, böyle bir yöntemi sıkı bir döngüde çağırıyorsanız, FooSettingsnesnenin bir örneğini yeniden kullanmak doğal ve hatta çok kolay olabilir .

Yararları

Örnekteki yorumda belirttiğim gibi, bu modelin genel API'ler için harika olduğunu düşünüyorum. Bir sınıfa yeni özellikler eklemek, kesintiye uğramayan bir ABI değişikliğidir, bu nedenle bu kalıbı kullanarak yönteminizin imzasını değiştirmeden yeni isteğe bağlı parametreler ekleyebilirsiniz - daha yakın zamanda derlenmiş koda daha fazla seçenek sunarken, eski derlenmiş kodu fazladan çalışma olmadan desteklemeye devam edin .

Ayrıca, C # yerleşik varsayılan yöntem parametreleri derleme süresi sabitleri olarak değerlendirildiğinden ve çağrı sitesinde pişirildiğinden, varsayılan parametreler yalnızca yeniden derlendikten sonra kod tarafından kullanılacaktır. Çağıran, bir ayarlar nesnesinin örneğini oluşturarak, yönteminizi çağırırken varsayılan değerleri dinamik olarak yükler. Bu, yalnızca ayarlar sınıfınızı değiştirerek varsayılanları güncelleyebileceğiniz anlamına gelir. Bu nedenle, bu kalıp, istenirse, arayanları yeni değerleri görmek için yeniden derlemek zorunda kalmadan varsayılan değerleri değiştirmenize olanak tanır.

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.