if语句中的赋值

时间:2011-08-18 19:57:22

标签: c# casting if-statement

我有一个班级Animal及其子类Dog。 我经常发现自己编写了以下几行代码:

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

对于变量Animal animal;

是否有一些语法允许我编写类似的内容:

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

18 个答案:

答案 0 :(得分:291)

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

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

请注意,dog语句后if仍在范围内,但未明确分配。


不,没有。写这篇文章更具惯用性:

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

鉴于“as if by if”几乎总是以这种方式使用,因此有一个操作员可以一次执行两个部分更有意义。如果pattern matching proposal已实施,目前这不在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
});

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

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循环更简洁:

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 ...),但我认为上面是一个非常惯用的模式。 通常在条件中有副作用并不好,但替代方案通常涉及代码重复,当你知道这种模式时,很容易做对。

答案 1 :(得分:48)

如果as失败,则返回null

Dog dog = animal as Dog;

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

答案 2 :(得分: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语法只是更通用功能的一个很好的结果。

答案 3 :(得分:11)

可以将值分配给变量,只要该变量已存在即可。您也可以调整变量范围,以便稍后在同一方法中再次使用该变量名称,如果这是一个问题。

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

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

答案 4 :(得分:9)

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

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是狗的名字(如果是狗),否则为空。

*我不知道这是否有效。它从未成为分析的瓶颈。

答案 5 :(得分:5)

在这里反对谷物,但也许你首先做错了。检查对象的类型几乎总是代码味道。在您的示例中,并非所有动物都有姓名吗?然后只需调用Animal.name,而不检查它是否是狗。

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

答案 6 :(得分:4)

更短的陈述

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

答案 7 :(得分: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();
    }
}

答案 8 :(得分:3)

如果你必须一个接一个地执行多个这样的as-ifs(并且不能使用多态),请考虑使用SwitchOnType构造。

答案 9 :(得分:3)

问题(带语法)带有赋值,因为C#中的赋值运算符是一个有效的表达式。相反,它是带有所需的声明,因为声明是语句。

如果我必须编写类似的代码,我有时会(根据更大的上下文)编写如下代码:

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

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

  1. 在{/ 1}}之外使用dog 将导致编译错误,因为它未在其他地方分配值。 (也就是说,在其他地方分配if。)
  2. 这种方法也可以很好地扩展到dog(只需要选择合适分支所需的if/else if/...; 这是大案例我写的地方我必须以这种形式。)
  3. 避免重复as。 (但也用is/as形式完成。)
  4. 与“惯用时”没有什么不同。 (只是不要忘掉:以一致的形式保持条件而且简单。)
  5. 要真正地将Dog dog = ...与世界其他地方隔离开来,可以使用新的块:

    dog

    快乐的编码。

答案 10 :(得分:2)

使用 C# 9.0 和 .NET 5.0,您可以像这样使用 as 编写它:

Animal animal;
if (animal as Dog is not null and Dog dog)
{
    //You can get here only if animal is of type Dog and you can use dog variable only
    //in this scope
}

这是因为 if 语句中的 animal as Dog 产生与以下相同的结果:

animal is Dog ? (Dog)(animal) : (Dog)null

then is not null 部分检查上述语句的结果是否不为空。仅当此语句为真时,它才会创建 Dog dog 类型的变量,该变量不能为 null。

C# 9.0 中带有 Pattern Combinator 的此功能,您可以在此处阅读更多相关信息: https://docs.microsoft.com/pl-pl/dotnet/csharp/language-reference/proposals/csharp-9.0/patterns3#pattern-combinators

答案 11 :(得分: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
}

答案 12 :(得分:0)

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

e.g。

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

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

  • 将可变狗创建为类型狗并分配变量动物 这是施加给狗。
  • 如果赋值失败,那么dog为null,这会阻止内容 来自运行的for循环,因为它立即被打破 的。
  • 如果赋值成功,则for循环遍历
    迭代。
  • 在迭代结束时,dog变量的值被赋值为 null,它突破了for循环。

答案 13 :(得分:0)

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

    }

}

答案 14 :(得分: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;
    }

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

答案 15 :(得分: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);
    }
 }

答案 16 :(得分:0)

您可以使用类似的东西

//声明变量 bool temp = false;

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

答案 17 :(得分: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的接口。

享受! ?