Penugasan dalam pernyataan if

142

Saya punya kelas Animal, dan subkelasnya Dog. Saya sering menemukan diri saya mengkodekan baris berikut:

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

Untuk variabel Animal animal;.

Apakah ada beberapa sintaks yang memungkinkan saya untuk menulis sesuatu seperti:

if (Dog dog = animal as Dog)
{    
    dog.Name;    
    ... 
}
michael
sumber
1
Apa artinya itu? Bagaimana boolkondisinya?
Kirk Woll
Tidak ada yang saya ketahui. Adakah alasan untuk tidak memindahkan Nama menjadi Hewan?
AlG
22
Sekadar catatan, kode suka sering kali merupakan hasil dari melanggar salah satu Prinsip SOLID . The L - Liskov Pergantian Prinsip . Tidak mengatakan itu salah untuk melakukan apa yang Anda lakukan sepanjang waktu, tetapi mungkin patut dipikirkan.
ckittel
harap perhatikan apa yang dilakukan @ckittel, Anda mungkin tidak ingin melakukan ini
khebbie
1
@Solo tidak,! null= falseDalam C #; C # hanya memungkinkan bools aktual atau hal-hal yang secara implisit dapat dikonversi menjadi bools dalam ifkondisi. Tidak ada nulls atau pun dari tipe integer yang secara implisit dapat dikonversi menjadi bools.
Roman Starkov

Jawaban:

323

Jawaban di bawah ini ditulis bertahun-tahun yang lalu dan diperbarui seiring waktu. Pada C # 7, Anda dapat menggunakan pencocokan pola:

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

Catatan yang dogmasih dalam cakupan setelah ifpernyataan, tetapi tidak ditetapkan secara pasti.


Tidak, tidak ada. Akan lebih idiomatis untuk menulis ini:

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

Mengingat bahwa "sebagaimana diikuti oleh jika" hampir selalu digunakan dengan cara ini, mungkin lebih masuk akal jika ada operator yang melakukan kedua bagian sekaligus. Ini saat ini tidak dalam C # 6, tetapi mungkin menjadi bagian dari C # 7, jika proposal pencocokan pola diterapkan.

Masalahnya adalah Anda tidak dapat mendeklarasikan variabel di bagian kondisi ifpernyataan 1 . Pendekatan terdekat yang dapat saya pikirkan adalah ini:

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

Itu hanya jahat ... (Saya baru saja mencobanya, dan itu berhasil. Tapi tolong, tolong jangan lakukan ini. Oh, dan Anda dapat mendeklarasikan dogmenggunakan vartentu saja.)

Tentu saja Anda dapat menulis metode ekstensi:

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

Kemudian sebut dengan:

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

Atau, Anda dapat menggabungkan keduanya:

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);
    }
}

Anda juga dapat menggunakan metode ekstensi tanpa ekspresi lambda dengan cara yang lebih bersih daripada for loop:

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

Kemudian:

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

1 Anda dapat menetapkan nilai dalam ifpernyataan, meskipun saya jarang melakukannya. Itu tidak sama dengan mendeklarasikan variabel. Ini tidak terlalu biasa bagi saya untuk melakukannya dalam whilemeskipun ketika membaca aliran data. Sebagai contoh:

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

Hari-hari ini saya biasanya lebih suka menggunakan pembungkus yang memungkinkan saya menggunakan foreach (string line in ...)tetapi saya melihat di atas sebagai pola yang cukup idiomatis. Ini biasanya tidak baik untuk memiliki efek samping dalam kondisi, tetapi alternatif biasanya melibatkan duplikasi kode, dan ketika Anda tahu pola ini sangat mudah untuk mendapatkan hak.

Jon Skeet
sumber
76
+1 untuk memberi jawaban dan juga memohon agar OP tidak menggunakannya. Klasik instan.
ckittel
8
@ Paul: Jika saya mencoba menjualnya kepada siapa pun, saya tidak akan menyarankan mereka untuk tidak menggunakannya. Saya hanya menunjukkan apa yang mungkin .
Jon Skeet
12
@ Paul: Saya pikir itu mungkin menjadi motivasi di belakang EVIL EVIL EVIL, tapi saya tidak positif.
Adam Robinson
18
Saya membuat metode ekstensi yang sama (dengan banyak kelebihan) beberapa waktu lalu dan saya memanggil mereka AsEither(...), saya pikir itu sedikit lebih jelas daripada AsIf(...), jadi saya bisa menulis myAnimal.AsEither(dog => dog.Woof(), cat => cat.Meeow(), unicorn => unicorn.ShitRainbows()).
herzmeister
97
Itu penyalahgunaan C # terbaik yang pernah saya lihat. Jelas Anda jenius jahat.
Eric Lippert
48

Jika asgagal, ia kembali null.

Dog dog = animal as Dog;

if (dog != null)
{
    // do stuff
}
Platinum Azure
sumber
Pertama terima kasih Kedua, saya ingin membuat variabel anjing dalam lingkup ifpernyataan dan bukan dalam lingkup luar.
michael
@Michael Anda tidak bisa melakukan itu dalam pernyataan if. Jika harus memiliki hasil bool bukan tugas. Jon Skeet memberikan beberapa kombinasi generik dan lambda yang bagus yang dapat Anda pertimbangkan juga.
Rodney S. Foley
ifdapat memiliki hasil bool dan tugas. Dog dog; if ((dog = animal as Dog) != null) { // Use Dog }tetapi itu masih memperkenalkan variabel dalam lingkup luar.
Tom Mayfield
12

Anda dapat menetapkan nilai ke variabel, selama variabel sudah ada. Anda juga bisa membuat ruang lingkup variabel untuk memungkinkan nama variabel untuk digunakan lagi nanti dalam metode yang sama, jika itu masalah.

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();
                // ...
            }
        }
    }
}

asumsi

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");
    }
}

mendapat hasil:

Name is now Scopey
Flying

Pola penetapan variabel dalam tes ini juga digunakan ketika membaca blok byte dari stream, misalnya:

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

Namun, pola pelingkupan variabel yang digunakan di atas bukan pola kode yang umum dan jika saya melihatnya digunakan di semua tempat, saya akan mencari cara untuk memperbaikinya.

Pengrajin
sumber
11

Apakah ada beberapa sintaks yang memungkinkan saya untuk menulis sesuatu seperti:

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

?

Kemungkinan akan ada di C # 6.0. Fitur ini disebut "pernyataan deklarasi". Lihat

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

untuk detail.

Sintaks yang diusulkan adalah:

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

Secara umum, fitur yang diusulkan adalah bahwa deklarasi variabel lokal dapat digunakan sebagai ekspresi . Ini ifsintaks adalah hanya konsekuensi bagus dari fitur yang lebih umum.

Eric Lippert
sumber
1
Sekilas ini sepertinya kurang mudah dibaca daripada hanya mendeklarasikan variabel seperti yang Anda lakukan hari ini. Apakah Anda tahu mengapa fitur khusus ini berhasil melewati bilah titik -100?
asawyer
3
@asawyer: Pertama, ini adalah fitur yang sangat sering diminta. Kedua, bahasa lain memiliki ekstensi ini ke "jika"; gcc misalnya memungkinkan yang setara dalam C ++. Ketiga, fitur ini lebih umum daripada hanya "jika", seperti yang saya perhatikan. Keempat, ada kecenderungan dalam C # sejak C # 3.0 untuk membuat semakin banyak hal yang membutuhkan konteks pernyataan alih-alih membutuhkan konteks ekspresi; ini membantu pemrograman gaya fungsional. Lihat catatan desain bahasa untuk detail lebih lanjut.
Eric Lippert
2
@asawyer: Sama-sama! Jangan ragu untuk berpartisipasi dalam diskusi di Roslyn.codeplex.com jika Anda memiliki lebih banyak komentar. Juga, saya akan menambahkan: Kelima, infrastruktur Roslyn yang baru menurunkan biaya marjinal kepada tim implementasi untuk melakukan fitur-fitur eksperimental yang kecil ini, yang berarti bahwa besarnya poin "minus 100" berkurang. Tim ini mengambil kesempatan ini untuk menjelajahi fitur-fitur kecil yang layak yang telah lama diminta tetapi tidak pernah berhasil di atas penghalang -100 poin sebelumnya.
Eric Lippert
1
Pembaca komentar ini yang bingung tentang apa "poin" yang kita bicarakan harus membaca posting blog mantan desainer C # Eric Gunnerson tentang topik ini: blogs.msdn.com/b/ericgu/archive/2004/01/12/57985. aspx . Ini analogi; tidak ada "poin" aktual yang dihitung.
Eric Lippert
@asawyer: Saya pikir fitur ini benar-benar bersinar dalam panggilan ke Try*(misalnya, TryParse). Fitur ini tidak hanya membuat panggilan seperti itu menjadi satu ekspresi (sebagaimana mestinya, IMO), tetapi juga memungkinkan pelingkupan yang lebih bersih dari variabel-variabel tersebut. Saya sangat antusias tentang memiliki outparameter Trymetode scoping untuk yang bersyarat; ini membuatnya lebih sulit untuk memperkenalkan jenis bug tertentu.
Brian
9

Salah satu metode ekstensi yang sering saya gunakan untuk menulis dan menggunakan * adalah

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

Yang dapat digunakan dalam situasi ini sebagai

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

Dan kemudian nameadalah nama anjing (jika itu adalah anjing), jika tidak, null.

* Saya tidak tahu apakah ini pemain. Itu tidak pernah muncul sebagai hambatan dalam profil.

Greg
sumber
2
+1 untuk catatan. Jika itu tidak pernah muncul sebagai hambatan dalam profiling, itu pertanda yang cukup baik bahwa itu cukup berkinerja.
Cody Gray
Mengapa Anda mengambil DefaultValue sebagai argumen dan membiarkan pemanggil memutuskan apa yang saya z bukannya kembali ke default (....)?
Trident D'Gao
5

Melawan biji-bijian di sini, tapi mungkin Anda salah melakukannya sejak awal. Memeriksa jenis objek hampir selalu merupakan bau kode. Tidak semua Hewan, dalam contoh Anda, memiliki Nama? Kemudian panggil saja Animal.name, tanpa memeriksa apakah itu anjing atau bukan.

Atau, balikkan metode sehingga Anda memanggil metode pada Hewan yang melakukan sesuatu yang berbeda tergantung pada jenis beton Hewan. Lihat juga: Polimorfisme.

fwielstra
sumber
4

Pernyataan Lebih Pendek

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

Berikut adalah beberapa kode kotor tambahan (tidak seburuk yang dimiliki Jon, tetapi :-)) tergantung pada modifikasi kelas dasar. Saya pikir itu menangkap maksud sementara mungkin melewatkan intinya:

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();
    }
}
James Ashley
sumber
3

Jika Anda harus melakukan banyak seperti-jika satu demi satu (dan menggunakan polimorfisme bukan pilihan), pertimbangkan untuk menggunakan konstruk SwitchOnType .

Omer Raviv
sumber
3

Masalahnya (dengan sintaks) bukan dengan penugasan, karena operator penugasan dalam C # adalah ekspresi yang valid. Melainkan, dengan deklarasi yang diinginkan karena deklarasi adalah pernyataan.

Jika saya harus menulis kode seperti itu, kadang-kadang saya akan (tergantung pada konteks yang lebih besar) menulis kode seperti ini:

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

Ada kelebihan dengan sintaks di atas (yang dekat dengan sintaks yang diminta) karena:

  1. Menggunakan dog luar yang ifakan menghasilkan error kompilasi karena tidak ditugaskan nilai di tempat lain. (Yaitu, jangan ditugaskan di dogtempat lain.)
  2. Pendekatan ini juga dapat diperluas dengan baik if/else if/...(Hanya ada sebanyak yang asdiperlukan untuk memilih cabang yang sesuai; ini kasus besar di mana saya menulisnya dalam bentuk ini ketika saya harus.)
  3. Hindari duplikasi is/as. (Tetapi juga dilakukan dengan Dog dog = ...bentuk.)
  4. Tidak berbeda dengan "sementara idiomatis". (Hanya saja, jangan terbawa suasana: jaga kondisional dalam bentuk yang konsisten dan sederhana.)

Untuk benar-benar mengisolasi dogdari seluruh dunia, sebuah blok baru dapat digunakan:

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

Selamat coding.


sumber
Poin # 1 yang Anda tawarkan adalah hal pertama yang terlintas di pikiran saya. Deklarasikan variabel tetapi tetapkan hanya dalam if. Variabel tidak dapat dirujuk dari luar jika tanpa kesalahan kompiler - sempurna!
Ian Yates
1

Anda bisa menggunakan sesuatu seperti itu

// Nyatakan variabel bool temp = false;

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

Solusi EVIL lain dengan metode ekstensi :)

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;
    }
}

Saya pribadi lebih suka cara bersih:

Dog dog = animal as Dog;

if (dog != null)
{
    // do stuff
}
Stefan Michev
sumber
0

Pernyataan if tidak akan mengizinkan itu, tetapi sebuah for loop akan melakukannya.

misalnya

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

Dalam hal cara kerjanya tidak segera jelas maka di sini adalah langkah demi langkah penjelasan proses:

  • Anjing variabel dibuat sebagai anjing jenis dan ditugaskan hewan variabel yang dilemparkan ke Anjing.
  • Jika tugas gagal maka anjing nol, yang mencegah isi dari for loop dari berjalan, karena itu segera rusak.
  • Jika tugas berhasil maka for loop dijalankan melalui
    iterasi.
  • Pada akhir iterasi, variabel dog diberi nilai nol, yang keluar dari for for.
WonderWorker
sumber
0
using(Dog dog = animal as Dog)
{
    if(dog != null)
    {
        dog.Name;    
        ... 

    }

}
WonderWorker
sumber
0

IDK jika ini membantu siapa saja tetapi Anda selalu dapat mencoba menggunakan TryParse untuk menetapkan variabel Anda. Berikut ini sebuah contoh:

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;
    }

The Total variabel akan dideklarasikan sebelum pernyataan Anda jika.

Alejandro Garcia
sumber
0

Saya baru saja menggarisbawahi pernyataan if untuk membuat garis kode yang terlihat seperti apa yang Anda minati. Itu hanya membantu memampatkan kode beberapa dan saya menemukan itu lebih mudah dibaca terutama ketika tugas peneluran:

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);
    }
 }
pengguna1689175
sumber
0

Saya tahu saya sangat terlambat terlambat ke pesta, tapi saya pikir saya akan memposting solusi saya sendiri untuk dilema ini karena saya belum melihatnya di sini (atau di mana pun dalam hal ini).

/// <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;
        }
    }
}

Dengan ini, Anda dapat melakukan hal-hal seperti:

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

CATATAN PENTING: Jika Anda ingin menggunakan TryA () menggunakan Interface, Anda HARUS memiliki antarmuka mewarisi IAble.

Nikmati! 🙂

Darian Lehmann-Plantenberg
sumber