Zuordnung in einer if-Anweisung

142

Ich habe eine Klasse Animalund ihre Unterklasse Dog. Ich finde mich oft dabei, die folgenden Zeilen zu codieren:

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

Für die Variable Animal animal;.

Gibt es eine Syntax, mit der ich etwas schreiben kann wie:

if (Dog dog = animal as Dog)
{    
    dog.Name;    
    ... 
}
Michael
quelle
1
Was würde das überhaupt bedeuten? Wie wäre der boolZustand?
Kirk Woll
Keine, von denen ich weiß. Gibt es einen Grund, den Namen nicht auf Animal zu verschieben?
AlG
22
Nur eine Anmerkung, Code wie kann oft das Ergebnis eines Verstoßes gegen eines der SOLID-Prinzipien sein . Das L-Liskov-Substitutionsprinzip . Nicht zu sagen, dass es falsch ist, die ganze Zeit das zu tun, was Sie tun, aber es könnte sich lohnen, darüber nachzudenken.
ckittel
Bitte beachten Sie, was @ckittel tut, Sie wollen dies wahrscheinlich nicht tun
khebbie
1
@Solo nein , null! = falseIn C #; C # erlaubt nur tatsächliche Bools oder Dinge, die implizit unter bestimmten ifBedingungen in Bools konvertierbar sind. Weder Nullen noch einer der Integer-Typen können implizit in Bools konvertiert werden.
Roman Starkov

Antworten:

323

Die folgende Antwort wurde vor Jahren geschrieben und im Laufe der Zeit aktualisiert. Ab C # 7 können Sie den Mustervergleich verwenden:

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

Beachten Sie, dass dies dognach der ifAnweisung noch im Geltungsbereich liegt , aber nicht definitiv zugewiesen ist.


Nein, gibt es nicht. Es ist jedoch idiomatischer, dies zu schreiben:

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

Angesichts der Tatsache, dass "wie folgt von if" fast immer auf diese Weise verwendet wird, ist es möglicherweise sinnvoller, dass es einen Bediener gibt, der beide Teile auf einmal ausführt. Dies ist derzeit nicht in C # 6 enthalten, kann jedoch Teil von C # 7 sein, wenn der Mustervergleichsvorschlag implementiert ist.

Das Problem ist, dass Sie im Bedingungsteil einer Anweisung 1 keine Variable deklarieren können . Der nächste Ansatz, den ich mir vorstellen kann, ist folgender:if

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

Das ist nur böse ... (Ich habe es gerade versucht und es funktioniert. Aber bitte, bitte tu das nicht. Oh, und du kannst natürlich die dogVerwendung deklarieren var.)

Natürlich können Sie eine Erweiterungsmethode schreiben:

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

Dann nenne es mit:

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

Alternativ können Sie beide kombinieren:

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

Sie können auch eine Erweiterungsmethode ohne Lambda-Ausdruck sauberer als die for-Schleife verwenden:

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

Dann:

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

1 Sie können Werte in Anweisungen zuweisenif , obwohl ich dies selten tue. Das ist jedoch nicht dasselbe wie das Deklarieren von Variablen. Es ist nicht sonderlich ungewöhnlich für mich, dies whilebeim Lesen von Datenströmen zu tun . Beispielsweise:

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

Heutzutage bevorzuge ich normalerweise die Verwendung eines Wrappers, mit dem ich ihn verwenden kann, foreach (string line in ...)aber ich betrachte das Obige als ein ziemlich idiomatisches Muster. Es ist normalerweise nicht schön, Nebenwirkungen innerhalb einer Bedingung zu haben, aber die Alternativen beinhalten normalerweise eine Codeduplizierung, und wenn Sie dieses Muster kennen, ist es einfach, es richtig zu machen.

Jon Skeet
quelle
76
+1 für die Beantwortung und die Bitte, dass das OP sie nicht verwendet. Sofortiger Klassiker.
ckittel
8
@ Paul: Wenn ich versuchen würde , es an jemanden zu verkaufen , würde ich ihnen nicht dringend raten, es nicht zu verwenden. Ich zeige nur, was möglich ist .
Jon Skeet
12
@ Paul: Ich denke, das war vielleicht die Motivation dahinter EVIL EVIL EVIL, aber ich bin nicht positiv.
Adam Robinson
18
Ich habe vor einiger Zeit eine ähnliche Erweiterungsmethode (mit einer Reihe von Überladungen) erstellt und sie angerufen AsEither(...). Ich denke, sie ist etwas klarer als AsIf(...), damit ich schreiben kann myAnimal.AsEither(dog => dog.Woof(), cat => cat.Meeow(), unicorn => unicorn.ShitRainbows()).
Herzmeister
97
Das ist der beste Missbrauch von C #, den ich seit einiger Zeit gesehen habe. Sie sind eindeutig ein böses Genie.
Eric Lippert
48

Wenn dies asfehlschlägt, wird es zurückgegeben null.

Dog dog = animal as Dog;

if (dog != null)
{
    // do stuff
}
Platinum Azure
quelle
Zunächst danke. Zweitens möchte ich die Hundevariable im Bereich der ifAnweisung und nicht im äußeren Bereich erstellen .
Michael
@Michael das kannst du nicht in einer if-Anweisung machen. Das if muss ein Bool-Ergebnis haben, keine Zuordnung. Jon Skeet bietet einige schöne generische und Lambda-Kombinationen, die Sie ebenfalls berücksichtigen sollten.
Rodney S. Foley
ifkann ein booles Ergebnis und eine Zuordnung haben. Dog dog; if ((dog = animal as Dog) != null) { // Use Dog }Damit wird die Variable jedoch immer noch in den äußeren Bereich eingeführt.
Tom Mayfield
12

Sie können der Variablen den Wert zuweisen, solange die Variable bereits vorhanden ist. Sie können die Variable auch so erweitern, dass dieser Variablenname später in derselben Methode erneut verwendet werden kann, wenn dies ein Problem darstellt.

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

unter der Annahme

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

erhält Ausgabe:

Name is now Scopey
Flying

Das Muster der Variablenzuweisung im Test wird auch beim Lesen von Byteblöcken aus Streams verwendet, zum Beispiel:

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

Das oben verwendete Muster des variablen Gültigkeitsbereichs ist jedoch kein besonders verbreitetes Codemuster, und wenn ich sehen würde, dass es überall verwendet wird, würde ich nach einer Möglichkeit suchen, es umzugestalten.

Handwerker
quelle
11

Gibt es eine Syntax, mit der ich etwas schreiben kann wie:

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

?

Es wird wahrscheinlich in C # 6.0 sein. Diese Funktion wird als "Deklarationsausdrücke" bezeichnet. Sehen

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

für Details.

Die vorgeschlagene Syntax lautet:

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

Allgemeiner ist das vorgeschlagene Merkmal, dass eine lokale Variablendeklaration als Ausdruck verwendet werden kann . Diese ifSyntax ist nur eine nette Folge der allgemeineren Funktion.

Eric Lippert
quelle
1
Auf den ersten Blick scheint dies weniger lesbar zu sein, als nur die Variable zu deklarieren, wie Sie es heute tun würden. Würdest du zufällig wissen, warum diese spezielle Funktion es geschafft hat, die -100-Punkte-Leiste zu überschreiten?
Asawyer
3
@asawyer: Erstens ist dies eine sehr häufig nachgefragte Funktion. Zweitens haben andere Sprachen diese Erweiterung auf "if"; gcc erlaubt zum Beispiel das Äquivalent in C ++. Drittens ist die Funktion allgemeiner als nur "wenn", wie ich bemerkt habe. Viertens gibt es in C # seit C # 3.0 einen Trend, immer mehr Dinge zu machen, die einen Anweisungskontext erfordern, stattdessen einen Ausdruckskontext; Dies hilft bei der funktionalen Programmierung. Weitere Informationen finden Sie in den Hinweisen zum Sprachdesign.
Eric Lippert
2
@asawyer: Gern geschehen! Nehmen Sie an der Diskussion auf Roslyn.codeplex.com teil, wenn Sie weitere Kommentare haben. Außerdem möchte ich hinzufügen: Fünftens senkt die neue Roslyn-Infrastruktur die Grenzkosten für das Implementierungsteam, wenn diese Art von kleinen experimentellen Funktionen ausgeführt wird, was bedeutet, dass die Größe der "minus 100" -Punkte verringert wird. Das Team nutzt diese Gelegenheit, um perfekt anständige kleine Funktionen zu erkunden, die seit langem angefragt wurden, es aber zuvor noch nie über die -100-Punkte-Grenze geschafft haben.
Eric Lippert
1
Leser dieser Kommentare, die sich nicht sicher sind, über welche "Punkte" wir sprechen, sollten den Blog-Beitrag des ehemaligen C # -Designers Eric Gunnerson zu diesem Thema lesen : blogs.msdn.com/b/ericgu/archive/2004/01/12/57985. aspx . Dies ist eine Analogie; Es werden keine tatsächlichen "Punkte" hochgezählt.
Eric Lippert
@asawyer: Ich denke, diese Funktion glänzt wirklich bei Anrufen an Try*(z TryParse. B. ). Diese Funktion macht solche Aufrufe nicht nur zu einem einzigen Ausdruck (wie sie sein sollten, IMO), sondern ermöglicht auch ein saubereres Scoping solcher Variablen. Ich bin begeistert davon, dass der outParameter einer TryMethode auf ihre Bedingungen festgelegt wird. Dies macht es schwieriger, bestimmte Arten von Fehlern einzuführen.
Brian
9

Eine der Erweiterungsmethoden, die ich häufig schreibe und verwende *, ist

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

Welches könnte in dieser Situation als verwendet werden

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

Und dann nameist der Name des Hundes (wenn es ein Hund ist), sonst null.

* Ich habe keine Ahnung, ob dies performant ist. Es ist nie zu einem Engpass bei der Profilerstellung gekommen.

Greg
quelle
2
+1 für die Notiz. Wenn es nie zu einem Engpass bei der Profilerstellung gekommen ist, ist dies ein ziemlich gutes Zeichen dafür, dass es ausreichend leistungsfähig ist.
Cody Gray
Warum sollten Sie den defaultValue als Argument verwenden und den Aufrufer entscheiden lassen, was er ist, anstatt auf den Standardwert (....) zurückzugreifen?
Trident D'Gao
5

Hier gegen den Strich gehen, aber vielleicht machst du es überhaupt falsch. Das Überprüfen des Objekttyps ist fast immer ein Codegeruch. Haben in Ihrem Beispiel nicht alle Tiere einen Namen? Rufen Sie dann einfach Animal.name auf, ohne zu prüfen, ob es sich um einen Hund handelt oder nicht.

Alternativ können Sie die Methode invertieren, sodass Sie eine Methode für Animal aufrufen, die je nach dem konkreten Typ des Tieres etwas anderes tut. Siehe auch: Polymorphismus.

fwielstra
quelle
4

Kürzere Aussage

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

Hier ist ein zusätzlicher schmutziger Code (allerdings nicht so schmutzig wie der von Jon :-)), der von der Änderung der Basisklasse abhängt. Ich denke, es fängt die Absicht ein, während es vielleicht den Punkt verfehlt:

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
quelle
3

Das Problem (mit der Syntax) liegt nicht bei der Zuweisung, da der Zuweisungsoperator in C # ein gültiger Ausdruck ist. Es ist vielmehr mit der gewünschten Deklaration, da Deklarationen Aussagen sind.

Wenn ich solchen Code schreiben muss, schreibe ich den Code manchmal (abhängig vom größeren Kontext) wie folgt:

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

Es gibt Vorteile mit der obigen Syntax (die der angeforderten Syntax nahe kommt), weil:

  1. Unter Verwendung dog außerhalb der ifwird in einem Compiler - Fehler zur Folge haben, da sie keinen Wert an anderer Stelle zugeordnet ist. (Das heißt, nichtdog anderswo zuweisen .)
  2. Dieser Ansatz kann auch gut erweitert werden auf if/else if/...(Es gibt nur so viele aswie erforderlich, um einen geeigneten Zweig auszuwählen; dies ist der große Fall, in dem ich ihn in dieser Form schreibe, wenn ich muss.)
  3. Vermeidet Doppelarbeit von is/as. (Aber auch mit Dog dog = ...Form gemacht.)
  4. Ist nicht anders als "idiomatisch während". (Lassen Sie sich einfach nicht mitreißen: Halten Sie die Bedingung in einer konsistenten Form und einfach.)

Um sich wirklich dogvom Rest der Welt zu isolieren , kann ein neuer Block verwendet werden:

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

Viel Spaß beim Codieren.


quelle
Punkt 1, den Sie anbieten, ist das erste, was mir in den Sinn gekommen ist. Deklarieren Sie die Variable, weisen Sie sie jedoch nur im if zu. Die Variable kann dann ohne Compilerfehler nicht von außerhalb des if referenziert werden - perfekt!
Ian Yates
1

Sie können so etwas verwenden

// Variable deklarieren bool temp = false;

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

Eine weitere böse Lösung mit Erweiterungsmethoden :)

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

Ich persönlich bevorzuge den sauberen Weg:

Dog dog = animal as Dog;

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

Eine if-Anweisung lässt das nicht zu, eine for-Schleife jedoch.

z.B

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

Falls die Funktionsweise nicht sofort ersichtlich ist, finden Sie hier eine schrittweise Erklärung des Prozesses:

  • Der variable Hund wird als Typ Hund erstellt und das variable Tier zugewiesen, das dem Hund zugewiesen wird.
  • Wenn die Zuweisung fehlschlägt, ist dog null, wodurch verhindert wird, dass der Inhalt der for-Schleife ausgeführt wird, da er sofort abgebrochen wird.
  • Wenn die Zuweisung erfolgreich ist, durchläuft die for-Schleife die
    Iteration.
  • Am Ende der Iteration wird der Dog-Variablen der Wert null zugewiesen, der aus der for-Schleife ausbricht.
WonderWorker
quelle
0
using(Dog dog = animal as Dog)
{
    if(dog != null)
    {
        dog.Name;    
        ... 

    }

}
WonderWorker
quelle
0

IDK, wenn dies jemandem hilft, aber Sie können jederzeit versuchen, Ihre Variable mit TryParse zuzuweisen. Hier ist ein Beispiel:

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

Die Gesamt Variable würde , bevor Ihre if - Anweisung deklariert werden.

Alejandro Garcia
quelle
0

Ich habe gerade die if-Anweisung eingefügt, um eine Codezeile zu erstellen, die so aussieht, wie Sie interessiert sind. Sie hilft nur dabei, den Code zu komprimieren, und ich fand ihn besser lesbar, insbesondere beim Verschachteln von Zuweisungen:

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);
    }
 }
user1689175
quelle
0

Ich weiß, dass ich zu spät zur Party komme, aber ich dachte, ich würde meine eigene Problemumgehung für dieses Dilemma veröffentlichen, da ich es hier (oder sonstwo) noch nicht gesehen habe.

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

Damit können Sie Dinge tun wie:

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

WICHTIGER HINWEIS: Wenn Sie TryAs () über eine Schnittstelle verwenden möchten, MUSS diese Schnittstelle IAble erben.

Genießen! 🙂

Darian Lehmann-Plantenberg
quelle