atanması gerekmeyen yapı tipi çıkış parametreleri


9

Yanlışlıkla kod inceleme sırasında bir işlevde bir satır yorum yaparken benim kod bazı tuhaf davranış fark ettim. Çoğaltmak çok zordu ama burada benzer bir örneği anlatacağım.

Bu test sınıfını aldım:

public class Test
{
    public void GetOut(out EmailAddress email)
    {
        try
        {
            Foo(email);
        }
        catch
        {
        }
    }

    public void Foo(EmailAddress email)
    {
    }
}

E-postaya GetOutnormalde bir hata atayacak bir atama yoktur :

Denetim geçerli yöntemden ayrılmadan önce out parametresi 'email' olarak atanmalıdır

Bununla birlikte, EmailAddress ayrı bir derlemede bir yapıdaysa, oluşturulan hata yoktur ve her şey iyi derlenir.

public struct EmailAddress
{
    #region Constructors

    public EmailAddress(string email)
        : this(email, string.Empty)
    {
    }

    public EmailAddress(string email, string name)
    {
        this.Email = email;
        this.Name = name;
    }

    #endregion

    #region Properties

    public string Email { get; private set; }
    public string Name { get; private set; }

    #endregion
}

Derleyici neden E-posta'nın atanması gerektiğini zorlamıyor? Yapı ayrı bir derlemede oluşturulmuşsa bu kod neden derlenir, ancak yapı var olan derlemede tanımlanmışsa derlenmez?


2
Bir sınıf kullanıyorsanız, nesnenin bir örneğini 'yeni' yapmanız gerekir. Yapılar için gerekli değildir. docs.microsoft.com/en-us/dotnet/csharp/programming-guide/… (bu sayfada özellikle bu metni arayın: Sınıflardan farklı olarak, yapılar yeni operato kullanılmadan somutlaştırılabilir)
Dortimer

1
Köpek yapınız bir değişken alır almaz derlenmeyecek :)
André Sanson

Bu örnekte, struct Dog{}her şey yolunda.
Henk Holterman

2
@ johnny5 Sonra örneği gösterin.
André Sanson

1
Tamam, bu ilginç. Bir Core 3 Konsol uygulaması ve bir .Standart sınıf lib ile üretilmiştir.
Henk Holterman

Yanıtlar:


12

TLDR: Bu uzun süredir bilinen bir hatadır. İlk olarak 2010'da yazdım:

https://blogs.msdn.microsoft.com/ericlippert/2010/01/18/a-definite-assignment-anomaly/

Zararsızdır ve güvenle göz ardı edebilir ve biraz belirsiz bir hata bulduğunuz için kendinizi tebrik edebilirsiniz.

Derleyici neden Emailkesinlikle atanmalıdır?

Oh, öyle, moda. Göreceğimiz gibi, değişkenin kesinlikle atandığını hangi koşulda ima ettiği konusunda yanlış bir fikri vardır.

Yapı ayrı bir derlemede oluşturulmuşsa bu kod neden derlenir, ancak yapı var olan derlemede tanımlanmışsa derlenmez?

Bu böceğin temel noktası. Hata, C # derleyicisinin yapılar üzerinde nasıl kesin atama denetimi yaptığını ve derleyicinin kütüphanelerden meta verileri nasıl yüklediğinin kesişiminin bir sonucudur.

Bunu düşün:

struct Foo 
{ 
  public int x; 
  public int y; 
}
// Yes, public fields are bad, but this is just 
// to illustrate the situation.
void M(out Foo f)
{

Tamam, bu noktada ne biliyoruz? ftürü bir değişkenin takma adıdır Foo, bu nedenle depolama alanı zaten ayrılmıştır ve kesinlikle en azından depolama ayırıcısından çıktığı durumdadır. Arayan tarafından değişkene bir değer yerleştirilmişse, bu değer oradadır.

Neye ihtiyacımız var? Biz gerektirir fkesinlikle nerede kontrol yaprakları herhangi bir noktada atanabilir Mnormalde. Yani şöyle bir şey beklersiniz:

void M(out Foo f)
{
  f = new Foo();
}

hangi setleri f.xve f.yvarsayılan değerlerine. Peki buna ne dersiniz?

void M(out Foo f)
{
  f = new Foo();
  f.x = 123;
  f.y = 456;
}

Bu da iyi olmalı. Ancak, işte başlatıcı, neden sadece bir an sonra onları uçurmak için varsayılan değerleri atamamız gerekiyor? C # 'ın kesin atama denetleyicisi her alanın atanıp atanmadığını kontrol eder ! Bu yasal:

void M(out Foo f)
{
  f.x = 123;
  f.y = 456;
}

Ve bu neden yasal olmamalı? Bu bir değer türüdür. fbir değişkendir ve zaten geçerli bir tür değeri içerir Foo, bu yüzden alanları ayarlayalım ve işimiz bitti, değil mi?

Sağ. Peki hata nedir?

Bulduğunuz hata: maliyet tasarrufu olarak, C # derleyicisi başvurulan kitaplıklarda bulunan yapıların özel alanları için meta verileri yüklemez . Bu meta veriler çok büyük olabilir ve her seferinde hepsini belleğe yüklemek için çok az kazanmak için derleyiciyi yavaşlatır.

Ve şimdi bulduğunuz hatanın nedenini çıkarabilmelisiniz. Derleyici, out parametresinin kesinlikle atanıp atanmadığını kontrol ettiğinde, bilinen alanların sayısını kesin olarak başlatılmış alanlarla karşılaştırır ve sizin durumunuzda özel alan meta verileri yüklenmediğinden yalnızca sıfır ortak alanı bilir . Derleyici "gereken sıfır alan, sıfır alan başlatıldı, biz iyiyiz" sonucuna varıyor.

Dediğim gibi, bu hata on yıldan fazla bir süredir var ve sizin gibi insanlar ara sıra yeniden keşfediyor ve bildiriyor. Zararsızdır ve düzeltilmesi pek olası değildir çünkü düzeltmenin neredeyse sıfır faydası vardır, ancak büyük bir performans maliyeti vardır.

Ve elbette hata, projenizdeki kaynak kodunda bulunan yapıların özel alanlarını çoğaltmaz, çünkü derleyici zaten eldeki özel alanlar hakkında bilgi sahibidir.


@ johnny5: Hata almamalısın. Bkz. Dotnetfiddle.net/ZEKiUk . Basit bir repro gönderebilir misiniz?
Eric Lippert

1
Keman için teşekkürler çünkü ben üyeleri yerine özellikleri olarak x ve y tanımladı
johnny 5

1
@ johnny5: Normal bir C # 1.0 stil özelliği tanımladıysanız, kesin atama denetleyicisinin perspektifinden, bu bir alan değil, bir yöntemdir. Bir C # 3.0+ stili otomatik özellik tanımladıysanız, derleyici onu destekleyen özel bir alan olduğunu bilir; o şeyin kesin tahsisi için kurallar yıllar içinde değiştirildi ve şimdi kesin kuralları hatırlamıyorum.
Eric Lippert

System.TimeSpanBunun yerine bir hata kullanırsanız hatalar gelir: error CS0269: Use of unassigned out parameter 'email've error CS0177: The out parameter 'email' must be assigned to before control leaves the current method. Sadece bir statik olmayan alan vardır TimeSpanyani _ticks. Öyle internalkendi montaj mscorlib için. Bu meclis özel mi? Aynı System.DateTime, ve alanıprivate
Jeppe Stig Nielsen

@JeppeStigNielsen: Bunun ne olduğunu bilmiyorum! Eğer çözerseniz, lütfen bana bildirin.
Eric Lippert

1

Bir hata gibi görünse de mantıklı geliyor.

'Eksik hata' sadece bir sınıf kütüphanesi kullanılırken görünür. Ve bir sınıf kütüphanesi başka bir .net dilinde yazılmış olabilir, örneğin VB.Net. 'Belirli atama izleme', çerçevenin değil C # özelliğinin bir özelliğidir.

Genel olarak bunun bir hata olduğunu düşünmüyorum ama bunun için yetkili bir açıklama bilmiyorum.


Bir derleme C # içine aktarıyorsanız, yapı başka bir dilde yazılmış bir derlemede yer alsa bile, bunu kullanan kod hala c # dilinde olduğundan, belirli atama izlemeyi kullanmak zorunda kalmamalıdır.
johnny 5

1
Şart değil. C #, birimselleştirilmiş (yerel) bir değişken kullanmanıza izin vermez, ancak aynı zamanda çerçeve 0 ( default(T)) olarak ayarlanacağını garanti eder . Dolayısıyla, bellek güvenliğinin veya benzer bir şeyin ihlali söz konusu değildir.
Henk Holterman

3
Böyle yetkili bir beyanda bulunabilirim. :) Uzun zamandır bilinen bir böcek.
Eric Lippert

1
@EricLippert Teşekkürler, bunu bir noktada görmenizi umuyordum
johnny 5
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.