关于c#:if语句中的赋值

Assignment in an if statement

我有一个类Animal,它的子类Dog。我经常发现自己在编码以下几行:

1
2
3
4
5
6
if (animal is Dog)
{
    Dog dog = animal as Dog;    
    dog.Name;    
    ...
}

对于变量Animal animal;

是否有一些语法允许我编写如下内容:

1
2
3
4
5
if (Dog dog = animal as Dog)
{    
    dog.Name;    
    ...
}


下面的答案是几年前写的,并随着时间的推移而更新。从C 7开始,您可以使用模式匹配:

1
2
3
4
if (animal is Dog dog)
{
    // Use dog here
}

注意,dogif语句之后仍然在范围内,但没有明确分配。

不,没有。不过写这个更习惯:

1
2
3
4
5
Dog dog = animal as Dog;
if (dog != null)
{
    // Use dog
}

考虑到"as followed by if"几乎总是用这种方式使用的,所以有一个运算符可以一次执行两个部分,这可能更有意义。这目前不在C 6中,但如果实现模式匹配建议,它可能是C 7的一部分。

问题是,不能在if语句1的条件部分声明变量。我能想到的最接近的方法是:

1
2
3
4
5
// EVIL EVIL EVIL. DO NOT USE.
for (Dog dog = animal as Dog; dog != null; dog = null)
{
    ...
}

真是太恶心了…(我刚试过,它确实管用。但请不要这样做。哦,你可以用var来声明dog

当然,您可以编写一个扩展方法:

1
2
3
4
5
6
7
8
public static void AsIf<T>(this object value, Action<T> action) where T : class
{
    T t = value as T;
    if (t != null)
    {
        action(t);
    }
}

然后打电话给:

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

或者,您可以将这两者结合起来:

1
2
3
4
5
6
7
8
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);
    }
}

也可以使用不带lambda表达式的扩展方法,而不是使用for循环:

1
2
3
4
5
6
7
8
public static IEnumerable<T> AsOrEmpty(this object value)
{
    T t = value as T;
    if (t != null)
    {
        yield return t;
    }
}

然后:

1
2
3
4
foreach (Dog dog in animal.AsOrEmpty<Dog>())
{
    // use dog
}

1您可以在if语句中分配值,尽管我很少这样做。但这与声明变量不同。不过,当我读取数据流时,在while中执行此操作并不特别。例如:

1
2
3
4
5
string line;
while ((line = reader.ReadLine()) != null)
{
    ...
}

现在,我通常更喜欢使用一个包装器,它允许我使用foreach (string line in ...),但我认为上面是一个非常惯用的模式。在一个条件中产生副作用通常是不好的,但是替代方法通常涉及代码复制,并且当您知道这个模式时,很容易纠正。


如果as失败,则返回null

1
2
3
4
5
6
Dog dog = animal as Dog;

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


只要变量已经存在,就可以将值赋给变量。如果这是一个问题,您也可以在变量范围内允许该变量名稍后在同一方法中再次使用。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
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();
                // ...
            }
        }
    }
}

假设

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
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");
    }
}

获取输出:

1
2
Name is now Scopey
Flying

当从流中读取字节块时,也使用测试中的变量分配模式,例如:

1
2
3
4
5
int bytesRead = 0;
while ((bytesRead = fs.Read(buffer, 0, buffer.Length)) > 0)
{
    // ...
}

然而,上面使用的变量范围模式并不是一个特别常见的代码模式,如果我看到它被广泛使用,我会寻找一种方法来重构它。


Is there some syntax that allows me to write something like:

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

?

C 6.0中可能有。此功能称为"声明表达式"。见

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

详情。

建议的语法是:

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

更一般地说,建议的特性是局部变量声明可以用作表达式。这种if语法只是更一般的特性的一个很好的结果。


我发现自己经常编写和使用的扩展方法之一是

1
2
3
4
5
6
7
8
public static TResult IfNotNull<T,TResult>(this T obj, Func<T,TResult> func)
{
    if(obj != null)
    {
        return func(obj);
    }
    return default(TResult);
}

在这种情况下可以使用

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

然后,name是狗的名字(如果它是狗),否则无效。

*我不知道这是不是在表演。它从来没有成为分析方面的瓶颈。


在这里违背了原则,但也许你一开始就做错事了。检查对象的类型几乎总是一种代码味道。在你的例子中,不是所有的动物都有名字吗?那就叫animal.name,不看是不是狗。

或者,反转该方法,以便在动物上调用根据动物的具体类型执行不同操作的方法。另请参见:多态性。


简短的陈述

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


下面是一些依赖于修改基类的额外的脏代码(不像jon的那样脏,尽管:-)。我认为它抓住了意图,但可能忽略了要点:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
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();
    }
}

问题(语法)不在赋值中,因为C中的赋值运算符是有效的表达式。相反,它使用所需的声明作为声明。

如果我必须这样写代码,我有时会(取决于更大的上下文)这样写代码:

1
2
3
4
Dog dog;
if ((dog = animal as Dog) != null) {
    // use dog
}

上述语法(接近请求的语法)有一些优点,因为:

  • if之外使用dog将导致编译错误,因为它在其他地方没有赋值。(也就是说,不要在其他地方分配dog。)
  • 这种方法也可以很好地扩展到if/else if/...(选择一个合适的分支所需的as的数量是最多的;这是我必须用这种形式编写它的大情况。)
  • 避免重复is/as。(也可以用Dog dog = ...形式完成。)
  • 与"熟语时"没什么不同。(只是不要忘乎所以:保持条件的形式一致且简单。)
  • 要真正将dog与世界其他地区隔离开来,可以使用一个新的块:

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

    快乐编码。


    如果您必须执行多个操作,例如一个接一个地执行ifs(并且不能选择使用多态性),请考虑使用switchontype构造。


    你可以用那样的东西

    //声明变量布尔温度=假;

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


    另一个有扩展方法的邪恶解决方案:)

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    30
    31
    32
    33
    34
    35
    36
    37
    38
    39
    40
    41
    42
    43
    44
    45
    46
    47
    48
    49
    50
    51
    52
    53
    54
    55
    56
    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;
        }
    }

    我个人更喜欢干净的方式:

    1
    2
    3
    4
    5
    6
    Dog dog = animal as Dog;

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

    if语句不允许这样做,但for循环允许这样做。

    例如

    1
    2
    3
    4
    5
    for (Dog dog = animal as Dog; dog != null; dog = null)
    {
        dog.Name;    
        ...
    }

    如果它的工作方式不是很明显,那么下面是对过程的逐步解释:

    • 变量狗被创建为类型狗并分配变量动物那是给狗扔的。
    • 如果分配失败,则dog为空,这将阻止内容因为for循环立即中断的。
    • 如果分配成功,则for循环将通过迭代。
    • 在迭代结束时,dog变量被赋值为空,它从for循环中断。

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    using(Dog dog = animal as Dog)
    {
        if(dog != null)
        {
            dog.Name;    
            ...

        }

    }

    IDK如果这对任何人都有帮助,但您可以尝试使用一个台盼来分配您的变量。下面是一个例子:

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    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;
        }

    total变量将在if语句之前声明。


    我刚刚内联了if语句来创建一行看起来像您感兴趣的代码。它只是帮助压缩一些代码,我发现它更具可读性,尤其是在嵌套分配时:

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    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);
        }
     }