İf ifadesinde atama


142

Bir sınıfım Animalve onun alt sınıfı var Dog. Sık sık kendimi aşağıdaki satırları kodlarken buluyorum:

if (animal is Dog)
{
    Dog dog = animal as Dog;    
    dog.Name;    
    ... 
}

Değişken için Animal animal;.

Böyle bir şey yazmama izin veren bazı sözdizimi var mı:

if (Dog dog = animal as Dog)
{    
    dog.Name;    
    ... 
}

1
Bu ne anlama geliyor? boolDurum ne olurdu ?
Kirk Woll

Hiçbiri bilmiyorum. Adı Hayvan'a taşımamak için bir sebep var mı?
AIG

22
Sadece bir not, kod gibi genellikle SOLID Prensiplerinden birini kırmanın sonucu olabilir . L - Liskov değiştirme prensibi . Her zaman yaptığınızı yapmanın yanlış olduğunu söylememek, ama düşünmeye değer olabilir.
ckittel

Lütfen @ckittel'in ne yaptığını not edin, muhtemelen bunu yapmak istemezsiniz
khebbie

1
@Solo no null,! = falseC # 'de; C # sadece gerçek bools veya ifkoşullardaki bools dolaylı olarak dönüştürülebilir şeyler izin verir . Ne nulls ne de tamsayı türlerinin hiçbiri dolaylı olarak boole dönüştürülemez.
Roman Starkov

Yanıtlar:


323

Aşağıdaki cevap yıllar önce yazılmış ve zaman içinde güncellenmiştir. C # 7'den itibaren, desen eşleştirmeyi kullanabilirsiniz:

if (animal is Dog dog)
{
    // Use dog here
}

dogİfadeden sonra hala kapsamda olduğunu if, ancak kesinlikle atanmadığını unutmayın.


Hayır, yok. Yine de bunu yazmak daha deyimsel:

Dog dog = animal as Dog;
if (dog != null)
{
    // Use dog
}

Hemen hemen her zaman bu şekilde kullanıldığı takdirde, "ardından gelen" ifadesi göz önüne alındığında, her iki parçayı tek seferde gerçekleştiren bir operatör olması daha mantıklı olabilir. Bu şu anda C # 6'da değil, ancak desen eşleştirme teklifi uygulanmışsa C # 7'nin bir parçası olabilir .

Sorun, 1 ifadesinin koşul bölümünde bir değişkeni bildirememenizdir . Aklıma gelen en yakın yaklaşım şudur:if

// EVIL EVIL EVIL. DO NOT USE.
for (Dog dog = animal as Dog; dog != null; dog = null)
{
    ...
}

Bu sadece kötü ... (Sadece denedim ve işe yarıyor. Ama lütfen, lütfen bunu yapma. Oh, ve elbette dogkullanarak beyan edebilirsin var.)

Tabii ki bir uzantı yöntemi yazabilirsiniz:

public static void AsIf<T>(this object value, Action<T> action) where T : class
{
    T t = value as T;
    if (t != null)
    {
        action(t);
    }
}

Sonra şununla ara:

animal.AsIf<Dog>(dog => {
    // Use dog in here
});

Alternatif olarak, ikisini birleştirebilirsiniz:

public static void AsIf<T>(this object value, Action<T> action) where T : class
{
    // EVIL EVIL EVIL
    for (var t = value as T; t != null; t = null)
    {
        action(t);
    }
}

Ayrıca, lambda ifadesi olmadan for döngüsüne göre daha temiz bir şekilde bir genişletme yöntemi kullanabilirsiniz:

public static IEnumerable<T> AsOrEmpty(this object value)
{
    T t = value as T;
    if (t != null)
    {
        yield return t;
    }
}

Sonra:

foreach (Dog dog in animal.AsOrEmpty<Dog>())
{
    // use dog
}

1 Şunları yapabilirsiniz atamak değerler ifNadiren bunu da, ifadeleri. Bu değişkenleri bildirmekle aynı şey değildir. Veri akışlarını okurken bunu yapmak benim için çok sıradışı değil while. Örneğin:

string line;
while ((line = reader.ReadLine()) != null)
{
    ...
}

Bu günlerde normalde kullanmama izin veren bir sarıcı kullanmayı tercih ediyorum foreach (string line in ...)ama yukarıdakileri oldukça deyimsel bir desen olarak görüyorum. Bu var genellikle bir koşul içinde yan etkileri olması güzel değil ama alternatifleri genellikle kod tekrarını içerir ve bu desen bildiğinde doğru almak kolaydır.


76
Cevap vermek ve ayrıca OP'nin kullanmadığı için yalvarmak için +1. Anında klasik.
ckittel

8
@ Paul: Eğer kimseye satmaya çalışsaydım, kullanmamalarını şiddetle tavsiye etmem. Sadece neyin mümkün olduğunu gösteriyorum .
Jon Skeet

12
@ Paul: Bence bu arkasındaki motivasyon olabilir EVIL EVIL EVIL, ama pozitif değilim.
Adam Robinson

18
Bir süre önce benzer bir uzatma yöntemi (bir sürü aşırı yük ile) yaptım ve onları aradım AsEither(...), sanırım biraz daha net AsIf(...), bu yüzden yazabilirim myAnimal.AsEither(dog => dog.Woof(), cat => cat.Meeow(), unicorn => unicorn.ShitRainbows()).
herzmeister

97
Bir süredir gördüğüm C # 'ın en iyi istismarı bu. Açıkçası sen kötü bir dahisin.
Eric Lippert

48

Eğer asbaşarısız döndürür null.

Dog dog = animal as Dog;

if (dog != null)
{
    // do stuff
}

İlk olarak, teşekkür ederim. İkincisi, ifdış değişkente değil , ifade kapsamında köpek değişkeni yaratmak istiyorum .
michael

@ Michael bunu if ifadesinde yapamazsınız. If bir atama değil bool sonucu olması gerekir. Jon Skeet, göz önünde bulundurmanız gereken güzel jenerik ve lambda kombinasyonları sunar.
Rodney S. Foley

ifbool sonucu ve ödevi olabilir. Dog dog; if ((dog = animal as Dog) != null) { // Use Dog }ancak bu yine de değişkeni dış kapsama sokar.
Tom Mayfield

12

Sen edebilirsiniz sürece değişken zaten var gibi değişkene değer atamak. Değişkeni, adın daha sonra aynı yöntemde tekrar kullanılmasına izin verecek şekilde de değiştirebilirsiniz, bu bir sorunsa.

public void Test()
{
    var animals = new Animal[] { new Dog(), new Duck() };

    foreach (var animal in animals)
    {
        {   // <-- scopes the existence of critter to this block
            Dog critter;
            if (null != (critter = animal as Dog))
            {
                critter.Name = "Scopey";
                // ...
            }
        }

        {
            Duck critter;
            if (null != (critter = animal as Duck))
            {
                critter.Fly();
                // ...
            }
        }
    }
}

varsayarak

public class Animal
{
}

public class Dog : Animal
{
    private string _name;
    public string Name
    {
        get { return _name; }
        set
        {
            _name = value;
            Console.WriteLine("Name is now " + _name);
        }
    }
}

public class Duck : Animal
{
    public void Fly()
    {
        Console.WriteLine("Flying");
    }
}

çıktı alır:

Name is now Scopey
Flying

Testteki değişken atama modeli, akışlardaki bayt bloklarını okurken de kullanılır, örneğin:

int bytesRead = 0;
while ((bytesRead = fs.Read(buffer, 0, buffer.Length)) > 0) 
{
    // ...
}

Bununla birlikte, yukarıda kullanılan değişken kapsam paterni özellikle yaygın bir kod kalıbı değildir ve eğer her yerde kullanıldığını görürsem, onu yeniden düzenlemenin bir yolunu ararım.


11

Böyle bir şey yazmama izin veren bazı sözdizimi var mı:

if (Dog dog = animal as Dog) { ... dog ... }

?

Muhtemelen C # 6.0'da olacak. Bu özelliğe "bildirim ifadeleri" denir. Görmek

https://roslyn.codeplex.com/discussions/565640

detaylar için.

Önerilen sözdizimi:

if ((var i = o as int?) != null) {  i  }
else if ((var s = o as string) != null) {  s  }
else if ...

Daha genel olarak, önerilen özellik, bir yerel değişken bildiriminin bir ifade olarak kullanılabilmesidir . Bu ifsözdizimi, daha genel özelliğin sadece güzel bir sonucudur.


1
Bir bakışta bu, bugünkü gibi değişkeni bildirmekten daha az okunabilir görünüyor. Bu özelliğin neden -100 nokta çubuğunu geçmeyi başardığını biliyor musunuz?
asawyer

3
@asawyer: Birincisi, bu çok sık talep edilen bir özellik. İkinci olarak, diğer diller "if" uzantısına sahiptir; Örneğin gcc, C ++ 'da eşdeğerine izin verir. Üçüncüsü, özellik belirttiğim gibi sadece "if" den daha genel. Dördüncüsü, C # 3.0'da bir ifade bağlamı gerektiren bir ifade bağlamı gerektiren daha fazla şey yapmak için C # 'dan beri bir eğilim vardır; bu fonksiyonel tarzda programlamaya yardımcı olur. Daha fazla ayrıntı için dil tasarımı notlarına bakın.
Eric Lippert

2
@asawyer: Rica ederim! Daha fazla yorumunuz varsa Roslyn.codeplex.com'daki tartışmaya katılmaktan çekinmeyin. Ayrıca şunu ekleyeceğim: Beşinci olarak, yeni Roslyn altyapısı, bu tür küçük deneysel özellikleri yapmak için uygulama ekibine marjinal maliyetleri düşürüyor, yani "eksi 100" puanlarının büyüklüğü azalıyor. Ekip bu fırsatı uzun zamandır talep edilen, ancak daha önce -100 nokta bariyerinin üzerine çıkmayan mükemmel nezih özellikleri keşfetmek için kullanıyor.
Eric Lippert

1
Bahsettiğimiz "noktalar" hakkında kafası karışan bu yorumların okuyucuları, eski C # tasarımcısı Eric Gunnerson'un bu konudaki blog gönderisini okumalıdır : blogs.msdn.com/b/ericgu/archive/2004/01/12/57985. aspx . Bu bir benzetmedir; sayılan gerçek bir "nokta" yoktur.
Eric Lippert

@asawyer: Bence bu özellik Try*(ör TryParse.) çağrılarında gerçekten parlıyor . Bu özellik sadece bu tür çağrıları tek bir ifadeye dönüştürmekle kalmaz (olması gerektiği gibi, IMO), aynı zamanda bu değişkenlerin daha net bir şekilde kapsamlanmasını sağlar. outBir Tryyöntemin parametresinin koşullu olarak belirlenmesine hevesliyim ; bu, belirli türdeki hataları tanıtmayı zorlaştırır.
Brian

9

Kendimi sık sık * yazarken ve kullandığımda bulduğum uzantı yöntemlerinden biri

public static TResult IfNotNull<T,TResult>(this T obj, Func<T,TResult> func)
{
    if(obj != null)
    {
        return func(obj);
    }
    return default(TResult);
}

Bu durumda hangi kullanılabilir

string name = (animal as Dog).IfNotNull(x => x.Name);

Ve sonra nameköpeğin adı (eğer bir köpekse), aksi takdirde null.

* Bunun performans olup olmadığı hakkında hiçbir fikrim yok. Profil oluşturmada hiçbir zaman bir darboğaz olarak ortaya çıkmadı.


2
Not için +1. Profil oluşturmada hiçbir zaman bir darboğaz olarak ortaya çıkmadıysa, bu yeterince performans gösterdiğinin oldukça iyi bir işaretidir .
Cody Gray

Neden defaultValue'yu bağımsız değişken olarak alıp arayanın varsayılana (....) dönmek yerine neyi seçtiğime karar verdiniz?
Trident D'Gao

5

Buradaki tahıllara karşı çıkmak, ama belki de ilk başta yanlış yapıyorsunuz. Bir nesnenin türünü kontrol etmek neredeyse her zaman bir kod kokusudur. Örneğinizdeki tüm Hayvanların bir Adı yok mu? O zaman sadece bir köpek olup olmadığını kontrol etmeden Animal.name'i arayın.

Alternatif olarak, Hayvan üzerinde, Hayvanın beton tipine bağlı olarak farklı bir şey yapan bir yöntem çağırmak için yöntemi ters çevirin. Ayrıca bakınız: Polimorfizm.


4

Daha Kısa Açıklama

var dog = animal as Dog
if(dog != null) dog.Name ...;

3

İşte bazı ek kirli kod (Jon kadar kirli değil ama :-)) temel sınıf değiştirmeye bağlıdır. Bence belki de amacı kaçırırken amacı yakalar:

class Animal
{
    public Animal() { Name = "animal";  }
    public List<Animal> IfIs<T>()
    {
        if(this is T)
            return new List<Animal>{this};
        else
            return new List<Animal>();
    }
    public string Name;
}

class Dog : Animal
{
    public Dog() { Name = "dog";  }
    public string Bark { get { return "ruff"; } }
}


class Program
{
    static void Main(string[] args)
    {
        var animal = new Animal();

        foreach(Dog dog in animal.IfIs<Dog>())
        {
            Console.WriteLine(dog.Name);
            Console.WriteLine(dog.Bark);
        }
        Console.ReadLine();
    }
}


3

C # 'daki atama işleci geçerli bir ifade olduğundan, sorun (sözdiziminde) atamayla ilgili değildir . Aksine, beyanlar ifadeler olduğu için istenen beyandadır .

Böyle bir kod yazmanız gerekiyorsa bazen (daha büyük bağlama bağlı olarak) böyle kodu yazacağım:

Dog dog;
if ((dog = animal as Dog) != null) {
    // use dog
}

Yukarıdaki sözdizimine (istenen sözdizimine yakın) sahip olan avantajlar vardır, çünkü:

  1. Kullanılması dog dışındaif başka bir yerde bir değer atanmamış olarak bir derleme hata ile sonuçlanır. (Yani, yok atamak dogbaşka yerde.)
  2. Bu yaklaşım da güzelce genişletilebilir if/else if/...( asUygun bir dal seçmek için sadece gerektiği kadar vardır; bu, gerektiğinde bu formda yazdığım büyük durumdur .)
  3. Çoğaltmasını önler is/as. (Ama aynı zamanda Dog dog = ...formla da yapılır .)
  4. "Deyimsel süre" den farklı değildir. (Sadece taşınmayın: koşulu tutarlı bir biçimde ve basit tutun.)

dogDünyanın geri kalanından gerçekten izole etmek için yeni bir blok kullanılabilir:

{
  Dog dog = ...; // or assign in `if` as per above
}
Bite(dog); // oops! can't access dog from above

Mutlu kodlama.


Aklınıza ilk gelen şey sunduğunuz # 1. Değişkeni bildirin ancak yalnızca if içinde atayın. Daha sonra değişken derleyici hatası olmadan if dışından referans alınamaz - mükemmel!
Ian Yates

1

böyle bir şey kullanabilirsin

// Değişken bildir bool temp = false;

 if (previousRows.Count > 0 || (temp= GetAnyThing()))
                                    {
                                    }

0

Uzantı yöntemleriyle başka bir EVIL çözümü :)

public class Tester
{
    public static void Test()
    {
        Animal a = new Animal();

        //nothing is printed
        foreach (Dog d in a.Each<Dog>())
        {
            Console.WriteLine(d.Name);
        }

        Dog dd = new Dog();

        //dog ID is printed
        foreach (Dog dog in dd.Each<Dog>())
        {
            Console.WriteLine(dog.ID);
        }
    }
}

public class Animal
{
    public Animal()
    {
        Console.WriteLine("Animal constructued:" + this.ID);
    }

    private string _id { get; set; }

    public string ID { get { return _id ?? (_id = Guid.NewGuid().ToString());} }

    public bool IsAlive { get; set; }
}

public class Dog : Animal 
{
    public Dog() : base() { }

    public string Name { get; set; }
}

public static class ObjectExtensions
{
    public static IEnumerable<T> Each<T>(this object Source)
        where T : class
    {
        T t = Source as T;

        if (t == null)
            yield break;

        yield return t;
    }
}

Ben şahsen temiz yolu tercih ederim:

Dog dog = animal as Dog;

if (dog != null)
{
    // do stuff
}

0

Bir if ifadesi buna izin vermez, ancak bir for döngüsü olacaktır.

Örneğin

for (Dog dog = animal as Dog; dog != null; dog = null)
{
    dog.Name;    
    ... 
}

Çalışma şekli hemen belli değilse, işte sürecin adım adım açıklaması:

  • Değişken köpek tip köpek olarak yaratılır ve Köpeğe dökülen değişken hayvan atanır.
  • Atama başarısız olursa, köpek null olur, bu da for döngüsünün içeriğinin çalışmasını önler, çünkü hemen ayrılır.
  • Atama başarılı olursa, for döngüsü
    yineleme yoluyla çalışır .
  • Yinelemenin sonunda, köpek değişkenine for döngüsünden kopan bir null değeri atanır.

0
using(Dog dog = animal as Dog)
{
    if(dog != null)
    {
        dog.Name;    
        ... 

    }

}

0

IDK bu herkese yardımcı olursa, ancak değişkeninizi atamak için her zaman bir TryParse kullanmaya çalışabilirsiniz. İşte bir örnek:

if (int.TryParse(Add(Value1, Value2).ToString(), out total))
        {
            Console.WriteLine("I was able to parse your value to: " + total);
        } else
        {
            Console.WriteLine("Couldn't Parse Value");
        }


        Console.ReadLine();
    }

    static int Add(int value1, int value2)
    {
        return value1 + value2;
    }

Toplam değişken senin eğer deyimi önce ilan edilirdi.


0

Ben sadece ilgilendiğiniz gibi görünüyor bir kod satırı oluşturmak için if deyimi inline var. Sadece bazı kodu sıkıştırmak yardımcı olur ve özellikle atamaları yuvalama sırasında daha okunabilir bulundu:

var dog = animal as Dog; if (dog != null)
{
    Console.WriteLine("Parent Dog Name = " + dog.name);

    var purebred = dog.Puppy as Purebred; if (purebred != null)
    {
         Console.WriteLine("Purebred Puppy Name = " + purebred.Name);
    }

    var mutt = dog.Puppy as Mongrel; if (mutt != null)
    {
         Console.WriteLine("Mongrel Puppy Name = " + mutt.Name);
    }
 }

0

Partiye süper kandırıldığımı biliyorum, ama henüz burada (veya bu konuda herhangi bir yerde) görmediğim için bu ikileme kendi geçici çözümümü yayınlayacağımı düşündüm.

/// <summary>
/// IAble exists solely to give ALL other Interfaces that inherit IAble the TryAs() extension method
/// </summary>
public interface IAble { }

public static class IAbleExtension
{
    /// <summary>
    /// Attempt to cast as T returning true and out-ing the cast if successful, otherwise returning false and out-ing null
    /// </summary>
    /// <typeparam name="T"></typeparam>
    /// <param name="able"></param>
    /// <param name="result"></param>
    /// <returns></returns>
    public static bool TryAs<T>(this IAble able, out T result) where T : class
    {
        if (able is T)
        {
            result = able as T;
            return true;
        }
        else
        {
            result = null;
            return false;
        }
    }

    /// <summary>
    /// Attempt to cast as T returning true and out-ing the cast if successful, otherwise returning false and out-ing null
    /// </summary>
    /// <typeparam name="T"></typeparam>
    /// <param name="obj"></param>
    /// <param name="result"></param>
    /// <returns></returns>
    public static bool TryAs<T>(this UnityEngine.Object obj, out T result) where T : class
    {
        if (obj is T)
        {
            result = obj as T;
            return true;
        }
        else
        {
            result = null;
            return false;
        }
    }
}

Bununla, aşağıdakileri yapabilirsiniz:

if (animal.TryAs(out Dog dog))
{
    //Do Dog stuff here because animal is a Dog
}
else
{
    //Cast failed! animal is not a dog
}

ÖNEMLİ NOT: Bir Arabirim kullanarak TryAs () kullanmak istiyorsanız, IAble'ı devralan bu arabirime sahip olmalısınız.

Zevk almak! 🙂

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.