Affectation dans une instruction if


142

J'ai une classe Animalet 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;.

Y a-t-il une syntaxe qui me permet d'écrire quelque chose comme:

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

1
Qu'est-ce que cela signifierait même? Quelle serait la boolcondition?
Kirk Woll

Aucun à ma connaissance. Une raison de ne pas déplacer le nom vers Animal?
AlG

22
Juste une note, le code comme peut souvent être le résultat de la violation de l'un des principes SOLID . Le principe de substitution de L - Liskov . Ne pas dire que c'est mal de faire ce que vous faites tout le temps, mais cela vaut peut-être la peine d'y penser.
ckittel

Veuillez prendre note de ce que fait @ckittel, vous ne voulez probablement pas faire ça
khebbie

1
@Solo no,! null= falseEn C #; C # n'autorise que les booléens réels ou les objets implicitement convertibles en booléens dans des ifconditions. Ni les valeurs nulles ni aucun des types entiers ne sont implicitement convertibles en booléens.
Roman Starkov

Réponses:


323

La réponse ci-dessous a été écrite il y a des années et mise à jour au fil du temps. À partir de C # 7, vous pouvez utiliser la correspondance de modèles:

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

Notez que cela dogest toujours dans la portée après l' ifinstruction, mais n'est pas définitivement attribué.


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

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

Etant donné que "comme suivi de si" est presque toujours utilisé de cette façon, il pourrait être plus logique qu'il y ait un opérateur qui exécute les deux parties en une seule fois. Ce n'est pas actuellement en C # 6, mais peut faire partie de C # 7, si la proposition de correspondance de modèle est implémentée.

Le problème est que vous ne pouvez pas déclarer une variable dans la partie condition d'une ifinstruction 1 . L'approche la plus proche à laquelle je puisse 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 plaît, ne faites pas ça. Oh, et vous pouvez déclarer dogutiliser varbien 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);
    }
}

Alors appelez-le avec:

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

Vous pouvez également 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 d'une manière plus propre 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 ifinstructions, bien que je le fasse rarement. Ce n'est pas la même chose que de déclarer des variables. Il n'est pas terriblement inhabituel pour moi de le faire dans un whilemoment lors de la lecture de flux de données. Par exemple:

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

Ces jours-ci, je préfère normalement utiliser un wrapper qui me permet d'utiliser, foreach (string line in ...)mais je considère ce qui précède comme un modèle assez idiomatique. Il n'est généralement pas agréable d'avoir des effets secondaires dans une condition, mais les alternatives impliquent généralement la duplication de code, et lorsque vous connaissez ce modèle, il est facile de bien faire.


76
+1 pour donner une réponse et implorer également que l'OP ne l'utilise pas. Classique instantané.
ckittel

8
@Paul: Si j'essayais de le vendre à quiconque, je ne leur conseillerais pas fortement de ne pas l'utiliser. Je montre simplement ce qui est possible .
Jon Skeet

12
@Paul: Je pense que c'est peut-être la motivation derrière EVIL EVIL EVIL, mais je ne suis pas sûr.
Adam Robinson

18
J'ai fait une méthode d'extension similaire (avec un tas de surcharges) il y a quelque temps et je les ai appelées AsEither(...), je pense que c'est un peu plus clair que AsIf(...), donc je peux écrire myAnimal.AsEither(dog => dog.Woof(), cat => cat.Meeow(), unicorn => unicorn.ShitRainbows()).
herzmeister

97
C'est le meilleur abus de C # que j'ai vu depuis un moment. De toute évidence, vous êtes un mauvais génie.
Eric Lippert

48

En cas d' aséchec, il revient null.

Dog dog = animal as Dog;

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

Premièrement, merci. Deuxièmement, je veux créer la variable dog dans la portée de l' ifinstruction et non dans la portée externe.
michael

@Michael vous ne pouvez pas faire cela dans une instruction if. Le if doit avoir un résultat booléen et non une affectation. Jon Skeet fournit de belles combinaisons génériques et lambda que vous pouvez également prendre en compte.
Rodney

ifpeut avoir un résultat booléen et une affectation. Dog dog; if ((dog = animal as Dog) != null) { // Use Dog }mais cela introduit toujours la variable dans la portée externe.
Tom Mayfield

12

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'affectation de variable dans le test est également utilisé lors de la lecture de blocs d'octets à partir de 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 utilisé partout, je chercherais un moyen de le refactoriser.


11

Y a-t-il une syntaxe qui me permet d'écrire quelque chose comme:

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

?

Il y en aura probablement dans 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:

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

Plus généralement, la fonctionnalité proposée est qu'une déclaration de variable locale peut être utilisée comme expression . Cette ifsyntaxe n'est qu'une belle conséquence de la fonctionnalité plus générale.


1
En un coup d'œil, cela semble moins lisible que de déclarer la variable comme vous le feriez aujourd'hui. Sauriez-vous pourquoi cette fonctionnalité particulière a réussi à passer la barre des -100 points?
asawyer le

3
@asawyer: Tout d'abord, c'est une fonctionnalité très fréquemment demandée. Deuxièmement, d'autres langues ont cette extension à "si"; gcc par exemple autorise l'équivalent en C ++. Troisièmement, la fonctionnalité est plus générale que simplement "si", comme je l'ai noté. Quatrièmement, il y a une tendance en C # depuis C # 3.0 pour faire de plus en plus de choses qui nécessitent un contexte d'instruction à la place nécessitent un contexte d'expression; cela facilite la programmation de style fonctionnel. Voir les notes de conception du langage pour plus de détails.
Eric Lippert

2
@asawyer: De rien! N'hésitez pas à participer à la discussion sur Roslyn.codeplex.com si vous avez d'autres commentaires. Aussi, j'ajouterais: Cinquièmement, la nouvelle infrastructure de Roslyn réduit les coûts marginaux pour l'équipe de mise en œuvre de faire ce genre de petites fonctionnalités expérimentales, ce qui signifie que la magnitude des points «moins 100» est diminuée. L'équipe profite de cette occasion pour explorer de petites fonctionnalités parfaitement décentes qui ont été demandées depuis longtemps mais qui n'ont jamais dépassé la barrière des -100 points auparavant.
Eric Lippert

1
Les lecteurs de ces commentaires qui ne savent pas quels "points" dont nous parlons devraient lire le billet de blog de l'ancien concepteur C # Eric Gunnerson sur ce sujet: blogs.msdn.com/b/ericgu/archive/2004/01/12/57985. aspx . Ceci est une analogie; il n'y a pas de "points" réels comptés.
Eric Lippert

@asawyer: Je pense que cette fonctionnalité brille vraiment dans les appels à Try*(par exemple, TryParse). Cette fonctionnalité fait non seulement de tels appels dans une seule expression (comme ils devraient l'être, IMO), mais permet également une portée plus claire de ces variables. Je suis enthousiaste à l'idée que le outparamètre d'une Tryméthode soit étendu à son conditionnel; cela rend plus difficile l'introduction de certains types de bogues.
Brian

9

L'une des méthodes d'extension que j'écris et que j'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 puis nameest le nom du chien (s'il s'agit d'un chien), sinon nul.

* Je n'ai aucune idée si c'est performant. Cela n'a jamais été un goulot d'étranglement dans le profilage.


2
+1 pour la note. S'il n'est jamais apparu comme un goulot d'étranglement dans le profilage, c'est un assez bon signe qu'il est suffisamment performant.
Cody Gray

Pourquoi prendriez-vous la valeur defaultValue comme argument et laisser l'appelant décider ce que je z au lieu de revenir à la valeur par défaut (....)?
Trident D'Gao

5

Aller à contre-courant ici, mais peut-être que vous le faites mal au départ. Vérifier le type d'un objet est presque toujours une odeur de code. Tous les animaux, dans votre exemple, n'ont-ils pas un nom? Ensuite, appelez simplement Animal.name, sans vérifier s'il s'agit d'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.


4

Déclaration plus courte

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

3

Voici un code sale supplémentaire (pas aussi sale que celui de Jon, cependant :-)) dépendant de la modification de la classe de base. Je pense que cela capture l'intention tout en ratant peut-être le point:

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

Si vous devez faire plusieurs comme-si un après un (et utiliser le polymorphisme n'est pas une option), envisagez d'utiliser une construction SwitchOnType .


3

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

Si je dois écrire du code comme ça, j'écrirai parfois (selon le contexte plus large) le code comme ceci:

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

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

  1. L' utilisation en dog dehors du ifentraînera une erreur de compilation car il n'est pas attribué une valeur ailleurs. (C'est, ne pas affecter dogailleurs.)
  2. Cette approche peut également être étendue à if/else if/...(Il y en a seulement autant asque nécessaire pour sélectionner une branche appropriée; c'est le grand cas où je l'écris sous cette forme quand je le dois.)
  3. Évite la duplication de is/as. (Mais aussi fait avec la Dog dog = ...forme.)
  4. N'est pas différent de "tandis que idiomatique". (Ne vous laissez pas emporter: gardez le conditionnel sous une forme cohérente et simple.)

Pour vraiment isoler dogdu 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

Bon codage.


Le point n ° 1 que vous proposez est la première chose qui m'est venue à l'esprit. Déclarez la variable mais ne l'affectez que dans le if. La variable ne peut alors pas être référencée de l'extérieur du if sans erreur du compilateur - parfait!
Ian Yates

1

tu peux utiliser quelque chose comme ça

// Déclare la variable bool temp = false;

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

0

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 manière propre:

Dog dog = animal as Dog;

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

0

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

Dans le cas où la façon dont cela fonctionne n'est pas immédiatement évidente, voici une explication étape par étape du processus:

  • Le chien variable est créé en tant que chien type et est affecté à l'animal variable qui est lancé sur Chien.
  • Si l'affectation échoue, dog est nul, ce qui empêche le contenu de la boucle for de s'exécuter, car il est immédiatement interrompu.
  • Si l'affectation réussit, la boucle for exécute l'
    itération.
  • À la fin de l'itération, la variable dog se voit attribuer une valeur nulle, qui sort de la boucle for.


0

IDK si cela aide quelqu'un mais vous pouvez toujours essayer d'utiliser un 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 totale serait déclarée avant votre instruction if.


0

Je viens d'inclure l'instruction if pour créer une ligne de code qui ressemble à ce qui vous intéresse. Cela aide simplement à compresser le code une partie et je l'ai trouvé 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

Je sais que je suis super duper en retard à la fête, mais j'ai pensé que je publierais ma propre solution de contournement à ce dilemme puisque je ne l'ai pas encore vue ici (ou ailleurs d'ailleurs).

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

Avec cela, vous pouvez faire des choses telles que:

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

REMARQUE IMPORTANTE: Si vous souhaitez utiliser TryAs () en utilisant une interface, vous DEVEZ avoir cette interface héritant de IAble.

Prendre plaisir! 🙂

En utilisant notre site, vous reconnaissez avoir lu et compris notre politique liée aux cookies et notre politique de confidentialité.
Licensed under cc by-sa 3.0 with attribution required.