If 语句中的赋值

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

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

对于变量 Animal animal;

有没有什么语法可以让我这样写:

if (Dog dog = animal as Dog)
{
dog.Name;
...
}
121479 次浏览

如果 as失败,它返回 null

Dog dog = animal as Dog;


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

下面的答案是几年前写的,随着时间的推移不断更新。从 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中,但如果实现了 模式匹配建议书,它可能是 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);
}
}

您还可以使用一个没有 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
}

您可以在 if语句中使用 分配值,尽管我很少这样做。但这与声明变量不同。虽然在读取数据流时,我在 while中进行这种操作并不罕见。例如:

string line;
while ((line = reader.ReadLine()) != null)
{
...
}

这些天,我通常喜欢使用一个包装器,让我使用 foreach (string line in ...),但我认为以上是一个相当惯用的模式。在条件中产生副作用是 通常不好的,但是其他的选择通常涉及到代码复制,当您知道这种模式时,就很容易获得正确的结果。

简短声明

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

只要变量已经存在,就可以将该值赋给该变量。如果存在问题,还可以对变量进行范围界定,以便稍后在同一方法中再次使用该变量名。

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

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

下面是一些额外的脏代码(不像 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();
}
}

我经常编写和使用的扩展方法之一是 *

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

* 我不知道这是否有效,它从未成为侧写的瓶颈。

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

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

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

问题(语法)在于赋值的 没有,因为 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 (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语法只是更一般的特性的一个很好的结果。

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

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

例如:。

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

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

  • 可变的狗被创建为类型狗,并分配给可变的动物 演狗的那个。
  • 如果分配失败,那么狗是空的,这阻止了内容 For 循环不能运行,因为它会立即断开 的。
  • 如果赋值成功,则 for 循环将通过
    迭代。
  • 在迭代结束时,狗变量被赋值为 Null,从 for 循环中断开。
using(Dog dog = animal as Dog)
{
if(dog != null)
{
dog.Name;
...


}


}

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

你可以用这样的东西

//声明变量 Bool temp = false;

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

我知道我迟到了,但是我想我应该把我自己的解决方法贴到这个困境上,因为我还没有在这里看到它(或者在任何地方)。

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

好好享受吧!

使用 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 语句中的 像狗一样的动物产生的结果与:

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

然后 不是无效的部分检查上述语句的结果是否为空。只有当该语句为 true 时,才会创建 Dog Dog 类型的变量,该变量不能为 null。

这个特性来自于 C # 9.0的模式组合器,你可以在这里阅读更多相关内容: Https://learn.microsoft.com/pl-pl/dotnet/csharp/language-reference/proposals/csharp-9.0/patterns3#pattern-combinators

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

enter image description here

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

使用 C # 7 模式匹配,你现在可以做这样的事情:

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

这个问题在10年前就问过了,所以现在几乎所有其他的答案都是过时的/错误的

还有一个解决办法,我再给你们举个例子,你们有一个方法,返回字符串 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
}

另一个迟到的例子:

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