ifステートメントでの割り当て


142

クラスAnimalとそのサブクラスがありDogます。次の行をコーディングしていることがよくあります。

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

変数Animal animal;

次のようなものを書くことができる構文はありますか?

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

1
それはどういう意味ですか?bool状態はどうなりますか?
カーク・ウォル、

私が知っていることはありません。名前を動物に移動しない理由はありますか?
AlG、2011

22
ただのメモですが、のようなコードは、多くの場合、SOLID原則の 1つを破った結果である可能性があります。L -リスコフの置換原則。いつもやっていることをするのは悪いことではありませんが、考えてみる価値があるかもしれません。
ckittel 2011

@ckittelが何をしているのかに注意してください、おそらくこれをしたくないでしょう
ケビー

1
@Solo no、null!= falsein C#; C#では、実際のブール値または暗黙的にブール値に変換可能なもののみを許可しますif条件でます。nullも整数型も暗黙的にブール値に変換できません。
Roman Starkov、2011

回答:


323

以下の答えは何年も前に書かれ、時間の経過とともに更新されました。C#7以降では、パターンマッチングを使用できます。

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

dog後のスコープにまだあるif声明、間違いなく割り当てられていません。


いいえ、ありません。これを書くのはもっと慣用的です:

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

ほとんどされた「場合によって以下のように」ということを考えると、常にこの方法を使用し一度に両方の部品を行うオペレータがあるために、それはより多くの意味を作るかもしれません。これは現在C#6にはありませんが、パターンマッチングの提案が実装されている場合、C#7の一部になる可能性があります。

問題は、変数の条件部分で変数を宣言できないことです。ifステートメントの 1。私が考えることができる最も近いアプローチはこれです:

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

それだけです 厄介です...(私は試してみましたが、うまくいきました。しかし、そうしないでください。ああ、もちろんdog使用varを宣言できます。)

もちろん、拡張メソッドを書くこともできます:

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

次に、それを次のように呼び出します:

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

または、2つを組み合わせることができます。

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

forループよりもクリーンな方法で、ラムダ式なしの拡張メソッドを使用することもできます。

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

次に:

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

1私はめったにそうしませんが、ステートメントで値を割り当てることができますif。ただし、変数の宣言とは異なります。そうではありませんひどく、私はそれを行うには珍しいwhileデータのストリームを読み込むときけれども。例えば:

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

最近私は通常、使用できるラッパーを使用することを好みますforeach (string line in ...)が、上記をかなり慣用的なパターンと見なしています。それはだ、通常の条件の中に副作用があると便利ではないが、選択肢は通常、コードの重複を伴い、そしてあなたがこのパターンを知っているとき、それが権利を取得するのは簡単です。


76
回答を与え、OPがそれを使用しないことを懇願するための+1。インスタントクラシック。
ckittel 2011

8
@Paul:誰かに売ろうとしても、使用しないよう強く勧めません。可能なことを示しているだけです。
Jon Skeet

12
@Paul:それが背後EVIL EVIL EVILにある動機だったと思うが、私は前向きではない。
アダムロビンソン

18
しばらく前に同様の拡張メソッド(大量のオーバーロードを含む)を作成し、それらを呼び出しましたAsEither(...)。それよりも少しわかりやすいのでAsIf(...)、を記述できますmyAnimal.AsEither(dog => dog.Woof(), cat => cat.Meeow(), unicorn => unicorn.ShitRainbows())
herzmeister 2011

97
それは私がしばらく見た中でC#の最高の乱用です。明らかにあなたは邪悪な天才です。
Eric Lippert

48

場合はas失敗し、それが返されますnull

Dog dog = animal as Dog;

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

まずはありがとうございます。次に、if外部スコープではなく、ステートメントのスコープ内にdog変数を作成します。
マイケル、2011

@Michaelは、ifステートメントではそれを実行できません。ifは、代入ではなくブール結果を持っている必要があります。Jon Skeetは、考慮すべきいくつかの優れたジェネリックとラムダの組み合わせを提供します。
Rodney S. Foley

ifブール値の結果割り当てを持つことができます。Dog dog; if ((dog = animal as Dog) != null) { // Use Dog }しかし、それでも外部スコープに変数が導入されます。
トムメイフィールド

12

変数がすでに存在している限り、変数に値を割り当てることができます。変数のスコープを設定して、問題がある場合は、その変数名を同じメソッドで後で再び使用できるようにすることもできます。

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

想定

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

出力を取得します。

Name is now Scopey
Flying

テストでの変数割り当てのパターンは、ストリームからバイトブロックを読み取るときにも使用されます。次に例を示します。

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

ただし、上記で使用した変数スコープのパターンは、特に一般的なコードパターンではなく、至る所で使用されているのを見た場合、それをリファクタリングする方法を探しています。


11

次のようなものを書くことができる構文はありますか?

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

C#6.0に含まれる可能性があります。この機能は「宣言式」と呼ばれます。見る

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

詳細については。

提案される構文は次のとおりです。

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

より一般的には、提案されている機能は、ローカル変数宣言を式として使用できることです。このif構文は、より一般的な機能の優れた結果にすぎません。


1
一見すると、これは現在のように変数を宣言するよりも読みにくいように見えます。この特定の機能が-100ポイントのバーを通過できた理由を偶然知っていますか?
asawyer

3
@asawyer:まず、これは非常に頻繁に要求される機能です。第二に、他の言語にはこの「if」の拡張子があります。たとえば、gccはC ++で同等のものを許可します。第三に、この機能は、私が述べたように、単なる「if」よりも一般的です。第4に、C#3.0以降、C#には、ステートメントコンテキストを必要とするものの代わりに式コンテキストを必要とするものがますます増える傾向があります。これは関数型プログラミングに役立ちます。詳細については、言語設計ノートを参照してください。
Eric Lippert 2014

2
@asawyer:どういたしまして!コメントがありましたら、Roslyn.codeplex.comでのディスカッションに参加してください。また、追加します。第5に、新しいRoslynインフラストラクチャは、この種の小さな実験的機能を実行するための実装チームの限界コストを削減します。つまり、「マイナス100」ポイントの大きさが減少します。チームはこの機会を利用して、長い間リクエストされてきたが、これまで-100ポイントの障壁を超えたことがない、まともな小さな機能を探索しています。
Eric Lippert 2014

1
私たちが話している「ポイント」について混乱しているこれらのコメントの読者は、このトピックに関する元のC#デザイナーのEric Gunnersonのブログ投稿を読んでください:blogs.msdn.com/b/ericgu/archive/2004/01/12/57985。 aspx。これは類推です。カウントされる実際の「ポイント」はありません。
エリックリッペルト2014

@asawyer:この機能は、Try*(たとえばTryParse)の呼び出しで本当に優れていると思います。この機能は、このような呼び出しを単一の式(必要に応じてIMO)にするだけでなく、そのような変数のスコープを明確にすることもできます。メソッドのoutパラメーターTryをその条件付きにスコープ指定することに熱心です。これにより、特定の種類のバグを導入することが難しくなります。
Brian

9

私が自分で頻繁に作成して使用している拡張メソッドの1つは、*

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

これはこのような状況で使用できます

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

そしてname、犬の名前(犬の場合)、それ以外の場合はnullです。

*これが効果的かどうかはわかりません。これは、プロファイリングのボトルネックになることはありません。


2
ノートの+1。それがプロファイリングのボトルネックになったことが一度もない場合、それは十分にパフォーマンスがあるというかなり良い兆候です。
コーディグレイ

なぜdefaultValueを引数として受け取り、デフォルト(....)にフォールバックするのではなく、呼び出し元にI zを決定させるのでしょうか?
Trident D'Gao

5

ここでは穀物に逆らうことになるが、そもそも間違っているのかもしれない。オブジェクトのタイプをチェックすることは、ほとんどの場合コードのにおいです。あなたの例では、すべての動物に名前があるわけではありませんか?次に、犬かどうかを確認せずに、Animal.nameを呼び出します。

または、メソッドを反転させて、Animalの具体的なタイプに応じて異なる動作をするメソッドをAnimalで呼び出すようにします。参照:ポリモーフィズム。


4

短いステートメント

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

3

基本クラスの変更に依存する追加のダーティコード(Jonほどダーティではありませんが:-)があります。私はそれがおそらくポイントを逃している間意図を捕らえると思います:

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

C#の代入演算子は有効な式であるため、(構文の)問題は代入にはありません。むしろ、それは望ましい宣言を持っています宣言はステートメントです。

そのようなコードを書かなければならない場合、私は時々(より大きなコンテキストに応じて)次のようなコードを書くでしょう:

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

上記の構文(要求された構文に近い)にはメリットがあります。

  1. dog 外部で使用するifと、他の場所で値が割り当てられないため、コンパイルエラーが発生します。(つまり、他の場所に割り当てないdogでください)。
  2. このアプローチは、適切に拡張することもできますif/else if/...as適切なブランチを選択するために必要な数だけあります。これは、必要なときにこのフォームでそれを記述する大きなケースです)。
  3. の重複を避けis/asます。(ただし、Dog dog = ...フォームも使用できます。)
  4. 「慣用的な間」と同じです。(ただ夢中にならないでください:条件を一貫性のある形式でシンプルにしてください。)

dog他の世界から完全に分離するには、新しいブロックを使用できます。

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

ハッピーコーディング。


あなたが提供するポイント#1は、私の頭に浮かんだ最初のものです。変数を宣言しますが、ifでのみ割り当てます。コンパイラエラーがない場合、変数はifの外部から参照できません-完璧です!
Ian Yates 2013年

1

あなたはそのようなものを使うことができます

//変数bool temp = falseを宣言します;

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

0

拡張メソッドを持つ別のEVILソリューション:)

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

私は個人的にクリーンな方法を好みます:

Dog dog = animal as Dog;

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

0

ifステートメントはそれを許可しませんが、forループは許可します。

例えば

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

それが機能する方法がすぐに明らかでない場合、ここにプロセスの段階的な説明があります:

  • 変数dogはタイプdogとして作成され、Dogにキャストされる変数animalが割り当てられます。
  • 割り当てが失敗した場合、dogはnullになり、forループの内容がすぐに抜けるため、実行が妨げられます。
  • 割り当てが成功すると、forループが
    反復処理を実行します。
  • 反復の最後に、dog変数にnullの値が割り当てられ、forループから抜け出します。


0

IDKがこれが誰にとっても役立つ場合は、常にTryParseを使用して変数を割り当てることができます。次に例を示します。

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

変数は、あなたのif文の前に宣言されます。


0

私はifステートメントをインライン化して、興味のあるようなコード行を作成しました。これは、コードを圧縮するのに役立ち、特に割り当てをネストするときに読みやすくなることがわかりました。

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

私はパーティーに遅れをとっているのはわかっていますが、私はこのジレンマ(まだここで(またはそのことについてはどこでも)で)まだ見ていなかったので、自分の回避策をこのジレンマに投稿すると思いました。

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

これにより、次のようなことができます。

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

重要な注意:インターフェイスを使用してTryAs()を使用する場合は、そのインターフェイスにIAbleを継承させる必要があります。

楽しい!🙂

弊社のサイトを使用することにより、あなたは弊社のクッキーポリシーおよびプライバシーポリシーを読み、理解したものとみなされます。
Licensed under cc by-sa 3.0 with attribution required.