【问题标题】:Assignment in an if statementif 语句中的赋值
【发布时间】:2011-10-30 02:41:18
【问题描述】:

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

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

对于变量Animal animal;

是否有一些语法可以让我写出类似的东西:

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

【问题讨论】:

  • 这意味着什么? bool 条件是什么?
  • 我不知道。有什么理由不将 Name 移到 Animal 上?
  • 请注意,类似代码通常是破坏SOLID Principles 之一的结果。 L - Liskov Substitution Principle。并不是说一直做你正在做的事情是错误的,但可能值得考虑。
  • 请注意@ckittel 在做什么,你可能不想这样做
  • @Solo no, null != false 在 C# 中; C# 只允许在if 条件下实际的布尔值或隐式转换为布尔值的东西。空值和任何整数类型都不能隐式转换为布尔值。

标签: c# casting if-statement


【解决方案1】:

如果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 } 但这仍然会在外部范围内引入变量。
【解决方案2】:

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

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

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


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

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

鉴于几乎总是以这种方式使用“后跟 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)
{
    ...
}

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

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

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 =&gt; dog.Woof(), cat =&gt; cat.Meeow(), unicorn =&gt; unicorn.ShitRainbows()) .
  • 这是我在一段时间内看到的对 C# 的最佳滥用。显然你是个邪恶的天才。
【解决方案3】:

简短的陈述

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

【讨论】:

    【解决方案4】:

    只要变量已经存在,您可以为变量赋值。如果有问题,您还可以限定变量以允许稍后在同一方法中再次使用该变量名。

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

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

    【讨论】:

      【解决方案5】:

      这里有一些额外的脏代码(虽然不像 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();
          }
      }
      

      【讨论】:

        【解决方案6】:

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

        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 作为参数,让调用者决定它是什么而不是回退到默认值 (....)?
        【解决方案7】:

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

        【讨论】:

          【解决方案8】:

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

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

          【讨论】:

            【解决方案9】:

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

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

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

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

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

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

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

            编码愉快。

            【讨论】:

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

            是否有一些语法可以让我写出类似的东西:

            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:不客气!如果您有更多 cmets,请随时参与 Roslyn.codeplex.com 上的讨论。另外,我还要补充一点:第五,新的 Roslyn 基础设施降低了执行团队执行这些小型实验功能的边际成本,这意味着“负 100”点的数量级降低了。团队正在借此机会探索完美体面的小功能,这些小功能长期以来一直被要求,但以前从未超过 -100 分的障碍。
            • 这些 cmets 的读者如果对我们所说的“点”感到困惑,应该阅读前 C# 设计师 Eric Gunnerson 关于此主题的博文:blogs.msdn.com/b/ericgu/archive/2004/01/12/57985.aspx。这是一个类比;没有实际的“积分”被计算在内。
            • @asawyer:我认为这个功能在对Try*(例如TryParse)的调用中非常出色。此功能不仅使此类调用成为单个表达式(应该如此,IMO),而且还允许对此类变量进行更清晰的范围界定。我热衷于将Try 方法的out 参数限定在其条件范围内;这使得引入某些类型的错误变得更加困难。
            【解决方案11】:

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

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

              例如

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

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

              • 变量 dog 被创建为 dog 类型并分配了变量 animal 被转换为 Dog。
              • 如果分配失败,则 dog 为空,这会阻止内容 for 循环的运行,因为它立即被打破 的。
              • 如果赋值成功,则 for 循环将通过
                迭代。
              • 在迭代结束时,dog 变量被赋值为 null,跳出 for 循环。

              【讨论】:

                【解决方案13】:
                using(Dog dog = animal as Dog)
                {
                    if(dog != null)
                    {
                        dog.Name;    
                        ... 
                
                    }
                
                }
                

                【讨论】:

                  【解决方案14】:

                  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】:

                    你可以使用类似的东西

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

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

                    【讨论】:

                      【解决方案16】:

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

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

                      享受吧! ?

                      【讨论】:

                        【解决方案17】:

                        使用 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 Combinators 的这个特性,你可以在这里阅读更多关于它的信息: https://docs.microsoft.com/pl-pl/dotnet/csharp/language-reference/proposals/csharp-9.0/patterns3#pattern-combinators

                        【讨论】:

                          【解决方案18】:

                          一些小实验表明我们可以在 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;
                          

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

                          【讨论】:

                            【解决方案19】:

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

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

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

                            【讨论】:

                              猜你喜欢
                              • 1970-01-01
                              • 1970-01-01
                              • 2015-07-15
                              • 2011-10-31
                              • 2021-08-13
                              • 2020-08-07
                              • 2020-01-20
                              • 2020-06-27
                              • 2017-10-01
                              相关资源
                              最近更新 更多