我有一个班级Animal
及其子类Dog
。
我经常发现自己编写了以下几行代码:
if (animal is Dog)
{
Dog dog = animal as Dog;
dog.Name;
...
}
对于变量Animal animal;
。
是否有一些语法允许我编写类似的内容:
if (Dog dog = animal as Dog)
{
dog.Name;
...
}
答案 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
}
以上语法(接近请求的语法)有优点,因为:
dog
将导致编译错误,因为它未在其他地方分配值。 (也就是说,不在其他地方分配if
。)dog
(只需要选择合适分支所需的if/else if/...
; 这是大案例我写的地方我必须以这种形式。)as
。 (但也用is/as
形式完成。)要真正地将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;
...
}
如果它的工作方式不是很明显,那么这里是对该过程的逐步解释:
答案 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的接口。
享受! ?