Assegnazione in un'istruzione if


142

Ho una classe Animale la sua sottoclasse Dog. Mi trovo spesso a codificare le seguenti righe:

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

Per la variabile Animal animal;.

C'è qualche sintassi che mi permette di scrivere qualcosa del tipo:

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

1
Cosa significherebbe? Quale sarebbe la boolcondizione?
Kirk Woll,

Nessuno di cui io sia a conoscenza. Qualche motivo per non spostare Nome su Animale?
AlG

22
Solo una nota, il codice come spesso può essere il risultato della violazione di uno dei Principi SOLIDI . Il principio di sostituzione L - Liskov . Non dire che è sbagliato fare quello che fai tutto il tempo, ma potrebbe valere la pena pensarci.
ckittel,

per favore prendi nota di cosa sta facendo @ckittel, probabilmente non vorrai farlo
khebbie

1
@Solo no,! null= falseIn C #; C # consente solo bool effettivi o cose implicitamente convertibili in bool in ifcondizioni. Né i valori nulli né alcuno dei tipi interi sono implicitamente convertibili in bool.
Roman Starkov,

Risposte:


323

La risposta di seguito è stata scritta anni fa e aggiornata nel tempo. A partire da C # 7, è possibile utilizzare la corrispondenza dei motivi:

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

Si noti che dogè ancora nell'ambito dopo l' ifistruzione, ma non è definitivamente assegnato.


No, non c'è. È più idiomatico scrivere questo però:

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

Dato che "come seguito da if" è quasi sempre usato in questo modo, potrebbe avere più senso che ci sia un operatore che esegue entrambe le parti in una volta sola. Questo non è attualmente in C # 6, ma può far parte di C # 7, se la proposta di corrispondenza del modello è implementata.

Il problema è che non è possibile dichiarare una variabile nella parte condizione di ifun'istruzione 1 . L'approccio più vicino che mi viene in mente è questo:

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

È semplicemente brutto ... (L'ho appena provato e funziona. Ma per favore, per favore, non farlo. Oh, e puoi dichiararlo dogusando varovviamente.)

Ovviamente potresti scrivere un metodo di estensione:

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

Quindi chiamalo con:

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

In alternativa, è possibile combinare i due:

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

Puoi anche usare un metodo di estensione senza un'espressione lambda in modo più pulito rispetto al ciclo for:

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

Poi:

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

1 È possibile assegnare valori nelle ifistruzioni, anche se raramente lo faccio. Tuttavia, non è lo stesso di dichiarare le variabili. Non è terribilmente insolito per me farlo in un whileattimo durante la lettura di flussi di dati. Per esempio:

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

In questi giorni di solito preferisco utilizzare un wrapper che mi consente di utilizzare, foreach (string line in ...)ma considero quanto sopra un modello piuttosto idiomatico. Di solito non è bello avere effetti collaterali all'interno di una condizione, ma le alternative di solito comportano la duplicazione del codice e quando si conosce questo schema è facile ottenere il risultato giusto.


76
+1 per dare una risposta e anche supplicare che l'OP non la usi. Classico istantaneo.
ckittel,

8
@Paul: se stessi provando a venderlo a qualcuno, non consiglierei vivamente di non usarlo. Sto solo mostrando ciò che è possibile .
Jon Skeet,

12
@Paul: penso che potrebbe essere stata la motivazione dietro EVIL EVIL EVIL, ma non sono positivo.
Adam Robinson,

18
Ho fatto un metodo di estensione simile (con un sacco di sovraccarichi) qualche tempo fa e li ho chiamati AsEither(...), penso che sia un po 'più chiaro di AsIf(...), quindi posso scrivere myAnimal.AsEither(dog => dog.Woof(), cat => cat.Meeow(), unicorn => unicorn.ShitRainbows()).
Herzmeister,

97
Questo è il miglior abuso di C # che ho visto da un po '. Chiaramente sei un genio del male.
Eric Lippert,

48

Se asfallisce, ritorna null.

Dog dog = animal as Dog;

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

Innanzitutto grazie. In secondo luogo, voglio creare la variabile cane nell'ambito di applicazione ifdell'istruzione e non nell'ambito esterno.
michael,

@Michael non puoi farlo in un'istruzione if. L'if deve avere un risultato bool non un incarico. Jon Skeet fornisce alcune combinazioni generiche e lambda che potresti prendere in considerazione.
Rodney S. Foley,

ifpuò avere un risultato bool e un incarico. Dog dog; if ((dog = animal as Dog) != null) { // Use Dog }ma ciò introduce ancora la variabile nell'ambito esterno.
Tom Mayfield,

12

È possibile assegnare il valore alla variabile, purché la variabile esista già. È inoltre possibile ambito la variabile per consentire a quel nome di variabile di essere riutilizzato successivamente nello stesso metodo, se questo è un problema.

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

supponendo

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

ottiene output:

Name is now Scopey
Flying

Il modello di assegnazione delle variabili nel test viene utilizzato anche durante la lettura di blocchi di byte dai flussi, ad esempio:

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

Il modello di scoping variabile usato sopra, tuttavia, non è un modello di codice particolarmente comune e se lo vedessi essere utilizzato dappertutto, sarei alla ricerca di un modo per riformattarlo.


11

C'è qualche sintassi che mi permette di scrivere qualcosa del tipo:

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

?

Probabilmente ci sarà in C # 6.0. Questa funzione si chiama "espressioni di dichiarazione". Vedere

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

per dettagli.

La sintassi proposta è:

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

Più in generale, la caratteristica proposta è che una dichiarazione di variabile locale può essere utilizzata come espressione . Questa ifsintassi è solo una bella conseguenza della funzionalità più generale.


1
A prima vista sembra meno leggibile che dichiarare la variabile come faresti oggi. Ti capita di sapere perché questa particolare funzione è riuscita a passare la barra dei -100 punti?
asawyer,

3
@asawyer: in primo luogo, questa è una funzione molto richiesta. In secondo luogo, altre lingue hanno questa estensione a "if"; gcc per esempio consente l'equivalente in C ++. In terzo luogo, la funzionalità è più generale del semplice "if", come ho notato. In quarto luogo, c'è una tendenza in C # dal C # 3.0 per rendere sempre più cose che richiedono un contesto di istruzione invece che richiedono un contesto di espressione; questo aiuta con la programmazione in stile funzionale. Vedi le note di progettazione della lingua per maggiori dettagli.
Eric Lippert,

2
@asawyer: Prego! Sentiti libero di partecipare alla discussione su Roslyn.codeplex.com se hai altri commenti. Inoltre, aggiungerei: in quinto luogo, la nuova infrastruttura di Roslyn riduce i costi marginali per il team di implementazione nel fare questo tipo di piccole funzionalità sperimentali, il che significa che l'entità dei punti "meno 100" è ridotta. Il team sta cogliendo l'occasione per esplorare piccole funzionalità perfettamente decenti che sono state richieste da tempo ma che non hanno mai superato la barriera di -100 punti in precedenza.
Eric Lippert,

1
I lettori di questi commenti che sono confusi su quali "punti" stiamo parlando dovrebbero leggere il post sul blog dell'ex designer C # Eric Gunnerson su questo argomento: blogs.msdn.com/b/ericgu/archive/2004/01/12/57985. aspx . Questa è un'analogia; non ci sono "punti" effettivi contati.
Eric Lippert,

@asawyer: penso che questa funzione brilli davvero nelle chiamate a Try*(ad es TryParse.). Questa funzione non solo trasforma tali chiamate in una singola espressione (come dovrebbero essere, IMO), ma consente anche una definizione più chiara di tali variabili. Sono entusiasta di avere il outparametro di un Trymetodo orientato al suo condizionale; questo rende più difficile introdurre alcuni tipi di bug.
Brian,

9

Uno dei metodi di estensione che mi ritrovo a scrivere e usare spesso * è

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

Quale potrebbe essere usato in questa situazione come

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

E quindi nameè il nome del cane (se è un cane), altrimenti null.

* Non ho idea se questo è performante. Non si è mai presentato come un collo di bottiglia nella profilazione.


2
+1 per la nota. Se non è mai emerso come un collo di bottiglia nella profilazione, è un buon segno che è sufficientemente performante.
Cody Grey

Perché dovresti prendere defaultValue come argomento e lasciare che il chiamante decida di cosa si tratta invece di tornare al valore predefinito (....)?
Trident D'Gao

5

Andando contro il grano qui, ma forse stai sbagliando in primo luogo. Controllare il tipo di un oggetto è quasi sempre un odore di codice. Non tutti gli animali, nel tuo esempio, hanno un nome? Quindi chiama semplicemente Animal.name, senza verificare se si tratta di un cane o meno.

In alternativa, inverti il ​​metodo in modo da chiamare un metodo su Animale che faccia qualcosa di diverso a seconda del tipo concreto di Animale. Vedi anche: polimorfismo.


4

Dichiarazione più breve

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

3

Ecco del codice sporco aggiuntivo (non sporco come quello di Jon, però :-)) che dipende dalla modifica della classe base. Penso che catturi l'intento mentre forse manca il punto:

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

Se devi fare multipli come se uno dopo l'altro (e l'uso del polimorfismo non è un'opzione), considera l'utilizzo di un costrutto SwitchOnType .


3

Il problema (con la sintassi) non riguarda l'assegnazione, poiché l'operatore di assegnazione in C # è un'espressione valida. Piuttosto, è con la dichiarazione desiderata poiché le dichiarazioni sono dichiarazioni.

Se devo scrivere codice in questo modo, a volte (a seconda del contesto più ampio) scriverò il codice in questo modo:

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

Ci sono meriti con la sintassi sopra (che è vicina alla sintassi richiesta) perché:

  1. Utilizzando dog al di fuori del ifsi tradurrà in un errore di compilazione in quanto non viene assegnato un valore altrove. (Cioè, non assegnare dogaltrove.)
  2. Questo approccio può anche essere ampliato piacevolmente a if/else if/...(ce ne sono solo quelli asnecessari per selezionare un ramo appropriato; questo è il grande caso in cui lo scrivo in questo modulo quando devo.)
  3. Evita la duplicazione di is/as. (Ma anche fatto con la Dog dog = ...forma.)
  4. Non è diverso da "idiomatico while". (Non lasciarti trasportare: mantieni il condizionale in una forma coerente e semplice.)

Per isolare veramente dogdal resto del mondo è possibile utilizzare un nuovo blocco:

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

Buona codifica.


Il punto n. 1 che offri è la prima cosa che mi è venuta in mente. Dichiarare la variabile ma assegnare solo nell'if. Quindi non è possibile fare riferimento alla variabile dall'esterno se senza un errore del compilatore - perfetto!
Ian Yates,

1

puoi usare qualcosa del genere

// Dichiara variabile bool temp = false;

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

0

Un'altra soluzione EVIL con metodi di estensione :)

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

Personalmente preferisco il modo pulito:

Dog dog = animal as Dog;

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

0

Un'istruzione if non lo consentirà, ma un ciclo for lo farà.

per esempio

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

Nel caso in cui il modo in cui funziona non è immediatamente ovvio, ecco una spiegazione dettagliata del processo:

  • Il cane variabile viene creato come tipo cane e gli viene assegnato l'animale variabile che viene lanciato su Cane.
  • Se l'assegnazione non riesce, il cane è nullo, il che impedisce l'esecuzione del contenuto del ciclo for, poiché viene immediatamente interrotto.
  • Se l'assegnazione ha esito positivo, il ciclo for viene eseguito attraverso l'
    iterazione.
  • Alla fine dell'iterazione, alla variabile dog viene assegnato un valore null, che esce dal ciclo for.

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

    }

}

0

IDK se questo aiuta qualcuno ma puoi sempre provare a usare un TryParse per assegnare la tua variabile. Ecco un esempio:

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

La variabile totale verrebbe dichiarata prima dell'istruzione if.


0

Ho appena enunciato l'istruzione if per creare una riga di codice che assomiglia a quello che ti interessa. Aiuta solo a comprimere il codice e l'ho trovato più leggibile soprattutto quando si annidano i compiti:

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

So di essere super duper in ritardo alla festa, ma ho pensato di postare la mia soluzione a questo dilemma poiché non l'ho ancora visto qui (o in qualsiasi altro luogo).

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

Con questo, puoi fare cose come:

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

NOTA IMPORTANTE: se si desidera utilizzare TryAs () utilizzando un'interfaccia, È NECESSARIO disporre dell'interfaccia che eredita IAble.

Godere! 🙂

Utilizzando il nostro sito, riconosci di aver letto e compreso le nostre Informativa sui cookie e Informativa sulla privacy.
Licensed under cc by-sa 3.0 with attribution required.