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情况?
柯克·沃尔

我不知道。有什么理由不将Name提升为Animal?
AlG

22
请注意,类似的代码通常可能是违反SOLID Principles之一的结果。在L -里氏替换原则。并不是说一直做自己的事情是错误的,但也许值得思考。
ckittel

请注意@ckittel在做什么,您可能不想这样做
khebbie 2011年

1
@Solo no,null!= false在C#中; C#仅允许实际的布尔值或在if条件下可隐式转换为布尔值的事物。空值或任何整数类型都不能隐式转换为布尔值。
罗曼·斯塔科夫

Answers:


323

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

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

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


不,没有。虽然这样写更惯用:

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

鉴于“ as if后面的”几乎总是以此方式使用,因此可能需要一个操作员一次性执行两个部分的操作。如果实施了模式匹配建议,则C#6中当前不存在此功能,但C#7中可能包含此功能。

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

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

与for循环相比,您还可以使用没有lambda表达式的扩展方法:

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
+1可以给出答案,也可以请求OP不使用它。即时经典。
ckittel

8
@Paul:如果我试图将其出售给任何人,我不会强烈建议他们不要使用它。我只是在说可能的事
乔恩·斯基特

12
@Paul:我认为这可能是背后的动机EVIL EVIL EVIL,但我并不乐观。
亚当·罗宾逊

18
不久前,我做了一个类似的扩展方法(带有一堆重载),我给他们打电话了AsEither(...),我认为它比它更清晰一些AsIf(...),所以我可以写myAnimal.AsEither(dog => dog.Woof(), cat => cat.Meeow(), unicorn => unicorn.ShitRainbows())
herzmeister

97
这是我一段时间以来看到的最好的C#滥用。显然,你是一个邪恶的天才。
埃里克·利珀特

48

如果as失败,则返回null

Dog dog = animal as Dog;

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

首先,谢谢。其次,我想在if语句范围内而不是外部范围内创建dog变量。
迈克尔

@Michael,您不能在if语句中这样做。if必须具有布尔结果而不是赋值。Jon Skeet还提供了一些不错的泛型和lambda组合,您可能也要考虑它们。
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 2014年

3
@asawyer:首先,这是一个非常常用的功能。第二,其他语言对“ if”的扩展。例如,gcc允许C ++中的等效功能。第三,正如我指出的,该功能比“ if”功能更通用。第四,自C#3.0以来,C#中存在一种趋势,即越来越多的事情需要语句上下文而不是表达式上下文。这有助于进行函数式编程。有关更多详细信息,请参见语言设计说明。
埃里克·利珀特

2
@asawyer:不客气!如果您有更多评论,请随时参与Roslyn.codeplex.com上的讨论。另外,我还要补充:第五,新的罗斯林基础设施降低了执行团队进行这些小型实验功能的边际成本,这意味着“负100”点的数量减少了。团队借此机会探索了完美的体面小功能,这些功能长期以来一直被要求,但以前从未达到-100点障碍。
埃里克·利珀特

1
这些评论的读者如果对我们在谈​​论的“观点”感到困惑,应阅读前C#设计师Eric Gunnerson关于该主题的博客文章:blogs.msdn.com/b/ericgu/archive/2004/01/12/57985。 aspx。这是一个比喻。没有实际的“点”被计算在内。
埃里克·利珀特

@asawyer:我认为此功能确实会在对Try*(例如TryParse)的调用中闪耀。此功能不仅使这样的调用成为单个表达式(应为IMO),而且还允许对此类变量进行更清晰的作用域划分。我热衷于将方法的out参数Try范围限定为其条件;这使得引入某些类型的错误变得更加困难。
布莱恩

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

*我不知道这是否表现出色。它从来没有成为概要分析的瓶颈。


2
为笔记+1。如果它从来没有成为性能分析的瓶颈,那么这是一个很好的信号,表明它具有足够的性能。
科迪·格雷

为什么要使用defaultValue作为参数,并让调用者决定它的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

快乐的编码。


您提供的第一点是我想到的第一件事。声明变量,但仅在if中赋值。如果没有编译器错误,则无法从外部引用变量-完美!
伊恩·耶茨

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为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的接口。

请享用!🙂

By using our site, you acknowledge that you have read and understand our Cookie Policy and Privacy Policy.
Licensed under cc by-sa 3.0 with attribution required.