Değer türlerini null ile karşılaştırarak C # tamam


85

Bugün bununla karşılaştım ve C # derleyicisinin neden bir hata atmadığına dair hiçbir fikrim yok.

Int32 x = 1;
if (x == null)
{
    Console.WriteLine("What the?");
}

X'in nasıl muhtemelen boş olabileceği konusunda kafam karıştı. Özellikle bu atama kesinlikle bir derleyici hatası verdiğinden:

Int32 x = null;

X'in boş olması mümkün mü, Microsoft bu denetimi derleyiciye koymamaya mı karar verdi yoksa tamamen gözden mi kaçtı?

Güncelleme: Bu makaleyi yazmak için kodla uğraştıktan sonra, derleyici aniden ifadenin asla doğru olmayacağına dair bir uyarı ile geldi. Şimdi gerçekten kayboldum. Nesneyi bir sınıfa koydum ve şimdi uyarı ortadan kalktı, ancak soruyla bırakıldı, bir değer türü boş olabilir mi?

public class Test
{
    public DateTime ADate = DateTime.Now;

    public Test ()
    {
        Test test = new Test();
        if (test.ADate == null)
        {
            Console.WriteLine("What the?");
        }
    }
}

9
Sen de yazabilirsin if (1 == 2). Kod yolu analizi yapmak derleyicinin işi değildir; statik analiz araçları ve birim testleri bunun içindir.
Aaronaught

Uyarının neden gittiğini öğrenmek için cevabıma bakın; ve hayır - boş olamaz.
Marc Gravell

1
(1 == 2) konusunda anlaştım, durumu daha çok merak ediyordum (1 == null)
Joshua Belden

Cevap veren herkese teşekkürler. Şimdi hepsi mantıklı.
Joshua Belden

Uyarı veya uyarı yok sorunu ile ilgili olarak: Söz konusu yapı sözde "basit tip" ise, intderleyici hoş uyarılar üretir. Basit tipler için ==operatör C # dil spesifikasyonu ile tanımlanır. Diğer (basit tip olmayan) yapılar için, derleyici bir uyarı vermeyi unutur . Ayrıntılar için struct ile null karşılaştırılırken yanlış derleyici uyarısına bakın. Basit türler olmayan yapılar için, ==işlecin opeartor ==yapının bir üyesi olan bir yöntemle aşırı yüklenmesi gerekir (aksi takdirde hayır ==yapılmasına izin verilmez).
Jeppe Stig Nielsen

Yanıtlar:


119

Bu yasaldır çünkü operatör aşırı yük çözümünün seçilebilecek benzersiz bir en iyi operatörü vardır. İki boş değer atanabilir girdi alan bir == operatörü vardır. İnt local, null yapılabilir int türüne dönüştürülebilir. Null değişmez değeri, null yapılabilir bir int'e dönüştürülebilir. Bu nedenle bu, == operatörünün yasal kullanımıdır ve her zaman yanlışla sonuçlanır.

Benzer şekilde, her zaman yanlış olan "if (x == 12.6)" demenize de izin veriyoruz. İnt local bir double'a dönüştürülebilir, literal bir double'a dönüştürülebilir ve açıkçası asla eşit olmayacaklar.



5
@James: (Sildiğim önceki hatalı yorumumu geri çekiyorum.) Kullanıcı tanımlı bir eşitlik operatörünün varsayılan olarak tanımlandığı kullanıcı tanımlı değer türleri , kendileri için oluşturulmuş kaldırılmış bir kullanıcı tanımlı eşitlik operatörüne sahiptir . Kaldırılmış kullanıcı tanımlı eşitlik operatörü, belirttiğiniz nedenle uygulanabilir: tüm değer türleri, boş değişmez değerde olduğu gibi, karşılık gelen null yapılabilir türlerine örtük olarak dönüştürülebilir. Öyle olmayan bir kullanıcı tanımlı değer türü bu durumda yoksun bir kullanıcı tanımlı bir operatör boş sabitin karşılaştırılabilir.
Eric Lippert

3
@James: Elbette, null yapılabilir yapılar alan kendi operatörünüzü == ve! = Operatörünüzü uygulayabilirsiniz. Bunlar mevcutsa, derleyici bunları sizin için otomatik olarak oluşturmak yerine kullanacaktır. (Ve bu arada ben olmayan null operand ile anlamsız kaldırdı operatör için uyarı bir uyarı üretmez pişman, biz sabitleme etrafında kazanılmış değil o derleyici bir hata var.)
Eric Lippert

2
Uyarımızı istiyoruz! Hak ettik.
Jeppe Stig Nielsen

3
@JamesDunne: Bir tanımlamaya static bool operator == (SomeID a, String b)ve onu etiketlemeye ne dersiniz Obsolete? İkinci işlenen türsüz bir değişmez ise null, bu, kaldırılmış işleçlerin kullanılmasını gerektiren herhangi bir formdan daha iyi bir eşleşme olur, ancak SomeID?eşitse null, kaldırılan operatör kazanır.
supercat

17

Bir ( int?) dönüşümü olduğu için bu bir hata değildir ; verilen örnekte bir uyarı oluşturur:

İfadenin sonucu her zaman 'yanlış'tır, çünkü' int 'türündeki bir değer hiçbir zaman' int? 'Türündeki' boş 'değerine eşit değildir.

IL'yi kontrol ederseniz , ulaşılamayan dalı tamamen kaldırdığını görürsünüz - bir sürüm yapısında mevcut değildir.

O Ancak unutmayın gelmez eşitlik operatörleri ile özel yapılar için bu uyarıyı oluşturabilir. 2.0'da kullanılıyordu, ancak 3.0 derleyicisinde değildi. Kod hala kaldırılır (bu nedenle kodun erişilemez olduğunu bilir), ancak hiçbir uyarı üretilmez:

using System;

struct MyValue
{
    private readonly int value;
    public MyValue(int value) { this.value = value; }
    public static bool operator ==(MyValue x, MyValue y) {
        return x.value == y.value;
    }
    public static bool operator !=(MyValue x, MyValue y) {
        return x.value != y.value;
    }
}
class Program
{
    static void Main()
    {
        int i = 1;
        MyValue v = new MyValue(1);
        if (i == null) { Console.WriteLine("a"); } // warning
        if (v == null) { Console.WriteLine("a"); } // no warning
    }
}

IL (için Main) ile - (yan etkileri olabilecek) kaldırılmış olanlar dışındaki her şeyi not edin MyValue(1):

.method private hidebysig static void Main() cil managed
{
    .entrypoint
    .maxstack 2
    .locals init (
        [0] int32 i,
        [1] valuetype MyValue v)
    L_0000: ldc.i4.1 
    L_0001: stloc.0 
    L_0002: ldloca.s v
    L_0004: ldc.i4.1 
    L_0005: call instance void MyValue::.ctor(int32)
    L_000a: ret 
}

bu temelde:

private static void Main()
{
    MyValue v = new MyValue(1);
}

1
Birisi bunu yakın zamanda bana da bildirdi. Bu uyarıyı vermeyi neden bıraktığımızı bilmiyorum. Bunu bir hata olarak girdik.
Eric Lippert


5

Bir karşılaştırmanın asla doğru olamayacağı gerçeği, yasadışı olduğu anlamına gelmez. Yine de hayır, bir değer türü asla olabilir null.


1
Ama bir değer türü olabilir eşit için null. Bir değer türü olan int?sözdizimsel şeker olanı düşünün Nullable<Int32>. Bir tür değişkeni int?kesinlikle eşit olabilir null.
Greg

1
@Greg: Evet, atıfta bulunduğunuz "eşitliğin" ==operatörün sonucu olduğunu varsayarak null değerine eşit olabilir . Bununla birlikte, örneğin aslında boş olmadığına dikkat etmek önemlidir .
Adam Robinson


1

Bir değer türü olamaz null, ancak eşit olabilir null(dikkate alın Nullable<>). Sizin durumunuzda intdeğişken ve nullörtülü olarak dönüştürülür Nullable<Int32>ve karşılaştırılır.


0

Test asla yanlış olmayacağından, sizin özel testinizin IL'yi oluştururken derleyici tarafından optimize edildiğinden şüpheleniyorum.

Yan Not: Null yapılabilir bir Int32'nin Int32 kullanması mümkündür? bunun yerine x.


0

Sanırım bunun nedeni, "==" parametresini System.Object.Equalskabul eden yönteme çağrıyı temsil eden bir sözdizimi şekeri olmasıdır System.Object. Null by ECMA spesifikasyonu, elbette türetilen özel bir türdür System.Object.

Bu yüzden sadece bir uyarı var.


Bu iki nedenden dolayı doğru değildir. İlk olarak, == bağımsız değişkenlerinden biri bir başvuru türü olduğunda Object.Equals ile aynı semantiğe sahip değildir. İkincisi, boş bir tür değildir. Referans eşitlik operatörünün nasıl çalıştığını anlamak istiyorsanız, spesifikasyonun 7.9.6 bölümüne bakın.
Eric Lippert

"Boş değişmez değeri (§9.4.4.6), herhangi bir nesneye veya diziye işaret etmeyen bir başvuruyu veya bir değerin yokluğunu belirtmek için kullanılan boş değer olarak değerlendirilir. Boş tip, boş olan tek bir değere sahiptir. Bu nedenle, türü boş tür olan bir ifade yalnızca boş değeri değerlendirebilir. Boş türü açıkça yazmanın bir yolu yoktur ve bu nedenle, bildirilen bir türde kullanmanın bir yolu yoktur. " - bu ECMA'dan alıntıdır. Neden bahsediyorsun? Ayrıca ECMA'nın hangi sürümünü kullanıyorsunuz? Benimkinde 7.9.6 görmüyorum.
Vitaly

0

[DÜZENLENMİŞ: hatalara yönelik uyarılar yaptı ve operatörleri dize saldırısı yerine null yapılabilir konusunda açık hale getirdi.]

@ Supercat'ın yukarıdaki bir yorumdaki akıllıca önerisine göre, aşağıdaki operatör aşırı yüklemeleri, özel değer türünüzün null ile karşılaştırılması hakkında bir hata oluşturmanıza olanak tanır.

Türünüzün null yapılabilir sürümleriyle karşılaştıran işleçler uygulayarak, bir karşılaştırmada null kullanımı işlecin null yapılabilir sürümüyle eşleşir ve bu da Hatayı Obsolete özniteliği aracılığıyla oluşturmanıza olanak tanır.

Microsoft derleyicimizi bize geri verene kadar, bu geçici çözümü uygulayacağım, teşekkürler @supercat!

public struct Foo
{
    private readonly int x;
    public Foo(int x)
    {
        this.x = x;
    }

    public override string ToString()
    {
        return string.Format("Foo {{x={0}}}", x);
    }

    public override int GetHashCode()
    {
        return x.GetHashCode();
    }

    public override bool Equals(Object obj)
    {
        return x.Equals(obj);
    }

    public static bool operator ==(Foo a, Foo b)
    {
        return a.x == b.x;
    }

    public static bool operator !=(Foo a, Foo b)
    {
        return a.x != b.x;
    }

    [Obsolete("The result of the expression is always 'false' since a value of type 'Foo' is never equal to 'null'", true)]
    public static bool operator ==(Foo a, Foo? b)
    {
        return false;
    }
    [Obsolete("The result of the expression is always 'true' since a value of type 'Foo' is never equal to 'null'", true)]
    public static bool operator !=(Foo a, Foo? b)
    {
        return true;
    }
    [Obsolete("The result of the expression is always 'false' since a value of type 'Foo' is never equal to 'null'", true)]
    public static bool operator ==(Foo? a, Foo b)
    {
        return false;
    }
    [Obsolete("The result of the expression is always 'true' since a value of type 'Foo' is never equal to 'null'", true)]
    public static bool operator !=(Foo? a, Foo b)
    {
        return true;
    }
}

Bir şeyi kaçırmadıysam, yaklaşımınız derleyicinin Foo a; Foo? b; ... if (a == b)..., böyle bir karşılaştırmanın tamamen meşru olmasına rağmen, ciyaklamasına neden olacaktır . "String hack" i önermemin nedeni, yukarıdaki karşılaştırmaya izin vermesi, ancak ciyaklamasıdır if (a == null). Kullanmak yerine, veya stringdışındaki herhangi bir referans türü ikame edilebilir ; istenirse, asla çağrılamayacak ve ona yetki verilemeyecek özel bir kurucu ile sahte bir sınıf tanımlanabilir . ObjectValueTypeReferenceThatCanOnlyBeNull
supercat

Kesinlikle haklısın. Önerimin, çalıştığım kod tabanında zaten günahkar kabul edilen boş değerlerin kullanımını kırdığını açıklığa kavuşturmalıydım (istenmeyen boks, vb.). ;)
yoyo

0

Derleyicinin bunu neden kabul ettiğine dair en iyi cevabın jenerik sınıflar için olduğunu düşünüyorum . Aşağıdaki sınıfı düşünün ...

public class NullTester<T>
{
    public bool IsNull(T value)
    {
        return (value == null);
    }
}

Derleyici, nulldeğer türleri için karşılaştırmaları kabul etmediyse , esasen bu sınıfı, kendi tür parametresine örtük bir kısıtlama ekleyerek bozar (yani, yalnızca değere dayalı olmayan türlerle çalışır).


0

Derleyici, ==null 'a uygulayan herhangi bir yapıyı karşılaştırmanıza izin verir . Hatta bir int ile null'u karşılaştırmanıza izin verir (yine de bir uyarı alırsınız).

Ancak kodu parçalara ayırırsanız, kod derlendiğinde karşılaştırmanın çözüldüğünü göreceksiniz. Yani, örneğin, bu kod (burada Foobir yapı uygulanıyor ==):

public static void Main()
{
    Console.WriteLine(new Foo() == new Foo());
    Console.WriteLine(new Foo() == null);
    Console.WriteLine(5 == null);
    Console.WriteLine(new Foo() != null);
}

Bu IL'yi oluşturur:

.method public hidebysig static void  Main() cil managed
{
  .entrypoint
  // Code size       45 (0x2d)
  .maxstack  2
  .locals init ([0] valuetype test3.Program/Foo V_0)
  IL_0000:  nop
  IL_0001:  ldloca.s   V_0
  IL_0003:  initobj    test3.Program/Foo
  IL_0009:  ldloc.0
  IL_000a:  ldloca.s   V_0
  IL_000c:  initobj    test3.Program/Foo
  IL_0012:  ldloc.0
  IL_0013:  call       bool test3.Program/Foo::op_Equality(valuetype test3.Program/Foo,
                                                           valuetype test3.Program/Foo)
  IL_0018:  call       void [mscorlib]System.Console::WriteLine(bool)
  IL_001d:  nop
  IL_001e:  ldc.i4.0
  IL_001f:  call       void [mscorlib]System.Console::WriteLine(bool)
  IL_0024:  nop
  IL_0025:  ldc.i4.1
  IL_0026:  call       void [mscorlib]System.Console::WriteLine(bool)
  IL_002b:  nop
  IL_002c:  ret
} // end of method Program::Main

Gördüğün gibi:

Console.WriteLine(new Foo() == new Foo());

Tercüme edilir:

IL_0013:  call       bool test3.Program/Foo::op_Equality(valuetype test3.Program/Foo,
                                                               valuetype test3.Program/Foo)

Buna karşılık:

Console.WriteLine(new Foo() == null);

Yanlışa çevrildi:

IL_001e:  ldc.i4.0
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.