ChatGPT解决这个技术问题 Extra ChatGPT

if 语句中的赋值

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

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

对于变量 Animal animal;

是否有一些语法可以让我编写如下内容:

if (Dog dog = animal as Dog)
{    
    dog.Name;    
    ... 
}
据我所知没有。有什么理由不将 Name 移到 Animal 上?
请注意,类似代码通常是破坏其中一个 SOLID Principles 的结果。 L - Liskov Substitution Principle。并不是说一直做你正在做的事情是错误的,但可能值得考虑。
请注意@ckittel 在做什么,你可能不想这样做
我相信在某些语言(包括 C#)中,null 被强制转换为 false。因此,如果赋值的结果为 null,则语句计算为 null,然后将其强制转换为 false。这就是为什么在某些语言中,只有当该变量不为空时,您才能使用 if(var) {...} 来执行代码。通常,在 if 语句中赋值给变量是很糟糕的,因为它看起来像一个常见错误(输入 = 而不是 ==)。过去可以通过在 if 块中使用赋值来获得一些(小的)性能优势,但是许多现代编译器会为您做这样的优化。
@Solo 不,C# 中的 null != false; C# 只允许实际的布尔值或在 if 条件下隐式转换为布尔值的东西。空值和任何整数类型都不能隐式转换为布尔值。

J
Jon Skeet

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

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

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

不,没有。写这个更惯用:

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

鉴于“as follow by if”几乎总是以这种方式使用,因此有一个操作符一次性执行这两个部分可能更有意义。这目前不在 C# 6 中,但如果实现了 pattern matching proposal,它可能是 C# 7 的一部分。

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

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

这只是讨厌...(我刚刚尝试过,它确实有效。但是请不要这样做。哦,您可以使用 var 声明 dog课程。)

当然你可以写一个扩展方法:

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


+1 给出答案并请求 OP 不使用它。瞬间经典。
@Paul:如果我想把它卖给任何人,我不会强烈建议他们不要使用它。我只是在展示什么是可能的。
@Paul:我认为这可能是 EVIL EVIL EVIL 背后的动机,但我并不积极。
前段时间我做了一个类似的扩展方法(有一堆重载),我称它们为AsEither(...),我认为它比AsIf(...)更清晰一些,所以我可以写myAnimal.AsEither(dog => dog.Woof(), cat => cat.Meeow(), unicorn => unicorn.ShitRainbows())
这是我在一段时间内看到的对 C# 的最佳滥用。显然你是一个邪恶的天才。
P
Platinum Azure

如果 as 失败,则返回 null

Dog dog = animal as Dog;

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

首先,谢谢。其次,我想在 if 语句的范围内而不是在外部范围内创建 dog 变量。
@Michael您不能在 if 语句中这样做。 if 必须有一个布尔结果而不是赋值。 Jon Skeet 提供了一些不错的通用和 lambda 组合,您也可以考虑这些组合。
if 可以有一个布尔结果一个赋值。 Dog dog; if ((dog = animal as Dog) != null) { // Use Dog } 但这仍然会在外部范围内引入变量。
H
Handcraftsman

只要变量已经存在,您就可以将值分配给变量。如果这是一个问题,您还可以限定变量以允许稍后在同一方法中再次使用该变量名称。

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

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


E
Eric Lippert

是否有一些语法可以让我编写如下内容:

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 语法只是更通用功能的一个很好的结果。


乍一看,这似乎比像今天这样声明变量更易读。您是否碰巧知道为什么此特定功能能够成功通过 -100 分栏?
@asawyer:首先,这是一个非常频繁请求的功能。其次,其他语言对“if”有这个扩展;例如 gcc 允许 C++ 中的等价物。第三,正如我所指出的,该功能比“如果”更通用。第四,自 C# 3.0 以来,C# 有一种趋势,即越来越多的东西需要语句上下文而不是表达式上下文;这有助于函数式编程。有关详细信息,请参阅语言设计说明。
@asawyer:不客气!如果您有更多意见,请随时参与 Roslyn.codeplex.com 上的讨论。另外,我还要补充一点:第五,新的 Roslyn 基础设施降低了执行团队执行这些小型实验功能的边际成本,这意味着“减 100”点的数量级降低了。团队正在借此机会探索完美体面的小功能,这些小功能长期以来一直被要求,但以前从未超过 -100 分的障碍。
这些评论的读者如果对我们所说的“要点”感到困惑,应该阅读前 C# 设计师 Eric Gunnerson 关于这个主题的博文:blogs.msdn.com/b/ericgu/archive/2004/01/12/57985.aspx。这是一个类比;没有实际的“积分”被计算在内。
注意:现在是 2020 (C# 9),不可能做到 if ((var myLocal = myObj.myProp) != null ) { … myLocal … }
G
Greg

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

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

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


+1 注释。如果它从来没有成为性能分析的瓶颈,那就是它足够性能的一个很好的迹象。
为什么要将 defaultValue 作为参数并让调用者决定它是什么而不是回退到默认值 (..)?
c
cthulhu

在这里违背粮食,但也许你一开始就做错了。检查对象的类型几乎总是一种代码味道。在您的示例中,不是所有动物都有名称吗?然后只需调用 Animal.name,而不检查它是否是狗。

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


J
Jonathan Sterling

简短的陈述

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

佚名

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

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

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

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

在 if 之外使用 dog 会导致编译错误,因为它没有在其他地方赋值。 (也就是说,不要将 dog 分配到其他地方。)这种方法也可以很好地扩展到 if/else if/... (只有选择合适的分支所需的数量;这是我写的大案例当我必须时以这种形式。)避免重复 is/as。 (但也用 Dog dog = ... form 完成。)与“惯用的 while”没有什么不同。 (只是不要得意忘形:保持条件一致且简单。)

要真正将 dog 与世界其他地方隔离开来,可以使用一个新块:

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

快乐编码。


您提供的第 1 点是我首先想到的。声明变量,但只在 if 中赋值。如果没有编译器错误,则无法从 if 外部引用该变量 - 完美!
J
James Ashley

这里有一些额外的脏代码(虽然不像乔恩那样脏:-))依赖于修改基类。我认为它抓住了意图,但可能没有抓住重点:

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
Community

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


P
P.Pasterny

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

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 语句中的作为 Dog 的动物产生了与以下相同的结果:

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

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

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


V
Valid

另一个迟到的条目:

if (animal is Dog dog) 
{ 
    dog.Name="Fido"; 
}
else if (animal is Cat cat)
{
    cat.Name="Bast";
}

A
Alejandro Garcia

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 语句之前声明。


A
Ahmed Salem

你可以使用类似的东西

//声明变量 bool temp= false;

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

S
Stefan Michev

另一个带有扩展方法的 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
}

W
WonderWorker

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

例如

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

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

变量 dog 被创建为 dog 类型,并分配了转换为 Dog 的变量 animal。

如果赋值失败,则 dog 为空,这会阻止 for 循环的内容运行,因为它会立即中断。

如果赋值成功,则 for 循环将遍历迭代。

在迭代结束时,dog 变量被赋值为 null,这会跳出 for 循环。


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

    }

}

D
Darian Lehmann-Plantenberg

我知道我迟到了派对,但我想我会发布我自己的解决方法来解决这个困境,因为我还没有在这里(或任何地方)看到它。

/// <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。

享受! 🙂


m
mattylantz

一些小实验表明我们可以在 if 语句中使用赋值

public static async Task Main(string[] args)
{
    bool f = false;
    if (f = Tru())
    {
        System.Diagnostics.Debug.WriteLine("True");
    }
    if (f = Tru(false))
    {
        System.Diagnostics.Debug.WriteLine("False");
    }
}

private static bool Tru(bool t = true) => t ? true : false;

https://i.stack.imgur.com/EnDZ1.png

至于任何潜在的副作用或“邪恶”,我想不出任何,尽管我确信有人可以。欢迎评论!


D
Dr-Bracket

使用 C# 7Pattern Matching,您现在可以执行以下操作:

if (returnsString() is string msg) {
  Console.WriteLine(msg);
}

这个问题是 10 多年前提出的,所以几乎所有其他答案都已过时/错误


E
ElConrado

有一种解决方法,我再举一个例子,你有一个返回字符串 ID 的方法,而不是 if 语句:

var userId = GetUserId();

if (!string.IsNullOrEmpty(userId))
{ 
    //logic
}

你可能会期望这样的语法,这是行不通的:

if (string userId = GetUserId() && !string.IsNullOrEmpty(userId))
{
    //logic
}

但是现在,您可以通过以下方式获得相同的结果:

if (GetUserId() is string userId && !string.IsNullOrEmpty(userId))
{ 
    //logic
}

在您的示例中,您当然可以这样做:

if(animal is Dog dog)
{
    //logic
}

但是考虑使用一种方法会很有用

var animal = GetAnimal();

if (animal is Dog)
{
    //logic
}

最后您可以将其重写为:

if(GetAnimal() is Dog dog)
{
    //logic
}