web-dev-qa-db-fra.com

Affectation dans une instruction if

J'ai une classe Animal et sa sous-classe Dog. Je me retrouve souvent à coder les lignes suivantes:

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

Pour la variable Animal animal;.

Existe-t-il une syntaxe qui me permet d’écrire quelque chose comme:

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

La réponse ci-dessous a été écrite il y a des années et a été mise à jour au fil du temps. À partir de C # 7, vous pouvez utiliser le filtrage par motif:

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

Notez que dog est toujours dans la portée après l'instruction if, mais n'est pas définitivement attribué.


Non, il n'y en a pas. C'est plus idiomatique d'écrire ceci cependant:

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

Etant donné que "tel que suivi par si" est presque toujours utilisé de cette manière, il serait peut-être plus logique de disposer d'un opérateur qui exécute les deux parties en une aller. Ce n'est pas actuellement dans C # 6, mais peut faire partie de C # 7, si le proposition de correspondance de modèle est implémenté.

Le problème est que vous ne pouvez pas déclarer une variable dans la partie condition d'une instruction if1. L’approche la plus proche à laquelle je peux penser est la suivante:

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

C'est juste méchant ... (je viens de l'essayer, et ça marche. Mais s'il vous plait, s'il vous plaît, ne faites pas ça. Oh, et vous pouvez déclarer dog en utilisant var bien sûr.)

Bien sûr, vous pouvez écrire une méthode d'extension:

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

Puis appelez-le avec:

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

Alternativement, vous pouvez combiner les deux:

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

Vous pouvez également utiliser une méthode d'extension sans expression lambda de manière plus nette que la boucle for:

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

Ensuite:

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

1 Vous pouvez attribuer des valeurs dans les instructions if, bien que je le fasse rarement. Ce n'est pas la même chose que déclarer des variables. Ce n'est pas terriblement inhabituel pour moi de le faire dans un while bien que lors de la lecture de flux de données. Par exemple:

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

De nos jours, je préfère normalement utiliser un wrapper qui me permet d’utiliser foreach (string line in ...), mais j’aperçois ce qui précède comme un joli motif idiomatique. Il n’est généralement pas agréable d’avoir des effets secondaires, mais les alternatives impliquent généralement la duplication de code, et lorsque vous connaissez ce modèle, il est facile d’obtenir des réponses exactes. .

299
Jon Skeet

Si as échoue, il retourne null.

Dog dog = animal as Dog;

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

Vous pouvez affecter la valeur à la variable, tant que la variable existe déjà. Vous pouvez également définir l'étendue de la variable pour permettre à ce nom de variable d'être réutilisé ultérieurement dans la même méthode, si cela pose un problème.

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

en supposant

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

obtient la sortie:

Name is now Scopey
Flying

Le modèle d’assignation de variable dans le test est également utilisé lors de la lecture de blocs d’octets dans des flux, par exemple:

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

Le modèle de portée variable utilisé ci-dessus, cependant, n'est pas un modèle de code particulièrement courant et si je le voyais être utilisé partout, je chercherais un moyen de le reformuler.

11
Handcraftsman

Existe-t-il une syntaxe qui me permet d’écrire quelque chose comme:

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

?

Il y aura probablement en C # 6.0. Cette fonctionnalité est appelée "expressions de déclaration". Voir 

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

pour plus de détails.

La syntaxe proposée est la suivante:

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

Plus généralement, la caractéristique proposée est que une déclaration de variable locale peut être utilisée comme expression. Cette syntaxe if n’est qu’une conséquence intéressante de la fonction la plus générale.

11
Eric Lippert

L’une des méthodes d’extension que j’écris et utilise souvent * est

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

Qui pourrait être utilisé dans cette situation comme 

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

Et alors name est le nom du chien (s'il s'agit d'un chien), sinon nul.

* Je ne sais pas si c'est performant. Cela n’a jamais été considéré comme un goulot d’étranglement pour le profilage.

9
Greg

Aller à contre-courant ici, mais peut-être que vous le faites mal en premier lieu. Vérifier le type d'un objet est presque toujours une odeur de code. Dans votre exemple, tous les animaux n'ont-ils pas un nom? Ensuite, appelez simplement Animal.name, sans vérifier si c'est un chien ou non.

Vous pouvez également inverser la méthode pour appeler une méthode sur Animal qui fait quelque chose différemment selon le type concret de l'Animal. Voir aussi: Polymorphisme.

5
fwielstra

Déclaration plus courte

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

Si vous devez faire plusieurs tels-si un après l'autre (et que l'utilisation du polymorphisme n'est pas une option), envisagez d'utiliser une construction SwitchOnType .

3
Omer Raviv

Le problème (avec la syntaxe) est pas avec l'affectation, car l'opérateur d'affectation en C # est une expression valide. Il s’agit plutôt de avec la déclaration souhaitée car les déclarations sont des instructions.

Si je dois écrire un code comme celui-ci, je vais parfois écrire (selon le contexte plus large) comme ceci:

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

La syntaxe ci-dessus présente des avantages (ce qui est proche de la syntaxe demandée) car:

  1. Utiliser dog outside the if entraînera une erreur de compilation car aucune valeur n’est affectée ailleurs. (C’est-à-dire, ne pas assigner dog ailleurs.)
  2. Cette approche peut également être étendue à if/else if/... (il y a juste autant de as que nécessaire pour sélectionner une branche appropriée; c'est le gros cas où je l'écris sous cette forme quand je dois.)
  3. Evite la duplication de is/as. (Mais aussi avec le formulaire Dog dog = ....)
  4. N'est pas différent de "idiomatique tandis que". (Ne vous laissez pas emporter: maintenez le conditionnel sous une forme cohérente et et simple.)

Pour isoler réellement dog du reste du monde, un nouveau bloc peut être utilisé:

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

Bonne codage.

3
user166390

Voici un code sale supplémentaire (pas aussi sale que celui de Jon, cependant :-)) qui dépend de la modification de la classe de base. Je pense que cela traduit l'intention tout en manquant peut-être le problème:

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
James Ashley

IDK si cela aide quelqu'un, mais vous pouvez toujours essayer d'utiliser TryParse pour affecter votre variable Voici un exemple:

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 variable total serait déclarée avant votre instruction if.

0
Alejandro Garcia

Une autre solution EVIL avec des méthodes d'extension :)

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

Personnellement, je préfère la voie propre:

Dog dog = animal as Dog;

if (dog != null)
{
    // do stuff
}
0
Stefan Michev
using(Dog dog = animal as Dog)
{
    if(dog != null)
    {
        dog.Name;    
        ... 

    }

}
0

Je viens d'insérer l'instruction if pour créer une ligne de code qui ressemble à ce qui vous intéresse. Elle aide simplement à compresser le code et je trouve qu'il est plus lisible, en particulier lors de l'imbrication des affectations:

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
user1689175

Une instruction if ne le permettra pas, mais une boucle for le fera.

par exemple.

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

Au cas où le fonctionnement ne soit pas immédiatement évident, voici une explication pas à pas du processus:

  • Variable dog est créé en tant que type dog et reçoit la variable animal Qui est convertie en Dog.
  • Si l'attribution échoue, dog est null, ce qui empêche le contenu .__ de la boucle for de s'exécuter, car elle est immédiatement éclatée
  • Si l'affectation réussit, la boucle for parcourt le
    itération.
  • À la fin de l'itération, la variable chien se voit attribuer la valeur Null, qui sort de la boucle for.
0

vous pouvez utiliser quelque chose comme ça 

// Déclarer une variable Bool temp = false;

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