为什么当已经使用“is”关键字检查时,需要“as”关键字来访问功能
本文关键字:关键字 需要 as 功能 访问 检查 is 为什么 | 更新日期: 2023-09-27 18:26:56
最近我开始更多地使用接口和继承,我尝试将它们作为参数传递到函数中,以使我的代码更加灵活。但是当我使用泛型函数时,真正让我恼火的是,我总是必须同时使用关键字is
和as
。例如:
interface Imail
{
string GetBody();
string GetSubject();
}
interface IAttachment
{
IEnumerable<Attachment> GetAttachments();
}
public MailMessage GetMail<T>(T mailObject) where T: Imail
{
MailMessage mail = new MailMessage();
mail.Subject = mailObject.GetSubject();
mail.Body = mailObject.GetBody();
if (mailObject is IAttachment)
foreach (var att in (mailObject as IAttachment).GetAttachments())
mail.Attachments.Add(att);
return mail;
}
这当然不会使代码更具可读性,在我看来是不需要的。为什么在检查它是否使用 is
实现接口(或基类(后不能只使用 mailObject.GetAttachments()
?还是有可能,我只是错过了什么?请用你的智慧启发我,所以学姐!
答案是,因为不能改变 C# 中变量的类型,只能引入新变量。您的 mailObject 具有 T 类型,并且无论您如何使用它,它都将继续具有该类型。使用 as
关键字进行类型检查并同时引入新变量:
var newType = obj as ISomething;
if(newType != null)
{
//newType is still a reference to obj, but is now of type ISomething.
}
注意:C#7 可能正在实现模式匹配,此类检查可能如下所示:
switch(mailObject)
{
case IAttachment attachment:
foreach (var att in attachment.GetAttachments())
mail.Attachments.Add(att);
break;
// other cases
}
- 使用
is
只是想测试对象是否实现了接口。 - 当您既要测试对象是否实现接口,又要键入包含接口的引用时,请使用
as
。
此代码:
if (mailObject is IAttachment)
foreach (var att in (mailObject as IAttachment).GetAttachments())
mail.Attachments.Add(att);
。应如下所示:
IAttachment attachment = mailObject as IAttachment;
if(attachment != null)
foreach (var att in attachment.GetAttachments())
mail.Attachments.Add(att);
请记住is
操作员必须在内部执行强制转换,以测试整个引用是否实现了您的接口。也就是说,如果避免对is
和更高as
运算符进行双重转换,则代码将更有效率。
为什么在检查它是否实现了接口(或基类(之后,我不能只使用 mailObject.GetAttachments(( ?
因为mailObject
变量的编译时类型仍然只是T
。
请注意,当前您正在执行两次动态类型检查 - 通常首选的方法是:
var attachment = mailObject as IAttachment;
if (attachment != null)
{
foreach (var att in attachment.GetAttachments())
{
mail.Attachmenets.Add(att);
}
}
当然,这样做的缺点是我们现在在范围内有一个额外的变量。
C# 7 可能会通过围绕模式匹配的新功能来解决此问题,因此使用当前提案,您将能够编写:
if (mailObject is Attachment attachment)
{
// Use attachment
}
(顺便说一句,我强烈建议使用大括号,即使是单语句if
和foreach
体。它使阅读 IMO 变得更加容易,而不必依赖缩进始终是完美的。我见过太多的 SO 问题,其中编码人员信任缩进,却没有注意到他们的多个语句并不真正相关......
你当然应该阅读一下 Eric Lippert 关于使用is
的讨论。请参阅 http://ericlippert.com/2015/10/19/inferring-from-is/和 http://ericlippert.com/2015/10/22/inferring-from-is-part-two/。
现在,我认为比较代码的两个版本并查看与 IL POV 的区别可能会很有趣。
我从这门课开始:
public class Foo
{
public void Hello() { Console.WriteLine("Hello"); }
}
然后我写了这两个程序:
(1(
object foo = new Foo();
if (foo is Foo)
(foo as Foo).Hello();
(二(
object foo = new Foo();
Foo foo2 = foo as Foo;
if (foo2 != null)
foo2.Hello();
IL让我感到惊讶。
(1(
IL_0000: nop
IL_0001: newobj UserQuery+Foo..ctor
IL_0006: stloc.0 // foo
IL_0007: ldloc.0 // foo
IL_0008: isinst UserQuery.Foo
IL_000D: ldnull
IL_000E: cgt.un
IL_0010: stloc.1
IL_0011: ldloc.1
IL_0012: brfalse.s IL_0020
IL_0014: ldloc.0 // foo
IL_0015: isinst UserQuery.Foo
IL_001A: callvirt UserQuery+Foo.Hello
IL_001F: nop
IL_0020: ret
(二(
IL_0000: nop
IL_0001: newobj UserQuery+Foo..ctor
IL_0006: stloc.0 // foo
IL_0007: ldloc.0 // foo
IL_0008: isinst UserQuery.Foo
IL_000D: stloc.1 // foo2
IL_000E: ldloc.1 // foo2
IL_000F: ldnull
IL_0010: cgt.un
IL_0012: stloc.2
IL_0013: ldloc.2
IL_0014: brfalse.s IL_001D
IL_0016: ldloc.1 // foo2
IL_0017: callvirt UserQuery+Foo.Hello
IL_001C: nop
IL_001D: ret
实际上,(2(程序还有更多说明。但有趣的是,is
和as
运算符都调用相同的isinst
指令。然后,is
运算符只需与null
进行比较。
现在,如果我更改程序以使用 C# 6 ?.
功能,它将变为:
(三(
object foo = new Foo();
(foo as Foo)?.Hello();
工作符合预期 - 只有当foo
是Foo
时,才会调用.Hello()
(我用Bar
类型进行了测试(。
IL 现在是这样的:
(三(
IL_0000: nop
IL_0001: newobj UserQuery+Foo..ctor
IL_0006: stloc.0 // foo
IL_0007: ldloc.0 // foo
IL_0008: isinst UserQuery.Foo
IL_000D: dup
IL_000E: brtrue.s IL_0013
IL_0010: pop
IL_0011: br.s IL_0019
IL_0013: call UserQuery+Foo.Hello
IL_0018: nop
IL_0019: ret
这似乎是最短的代码,也可能是最有效的代码。时机将是接下来要做的事情,因为知道哪匹马更快的唯一方法是与它们比赛。
根据 Eric 的评论,我在打开优化的情况下重新编译了代码并得到了这个 IL:
(1(
IL_0000: newobj UserQuery+Foo..ctor
IL_0005: stloc.0 // foo
IL_0006: ldloc.0 // foo
IL_0007: isinst UserQuery.Foo
IL_000C: brfalse.s IL_0019
IL_000E: ldloc.0 // foo
IL_000F: isinst UserQuery.Foo
IL_0014: callvirt UserQuery+Foo.Hello
IL_0019: ret
(二(
IL_0000: newobj UserQuery+Foo..ctor
IL_0005: isinst UserQuery.Foo
IL_000A: stloc.0 // foo2
IL_000B: ldloc.0 // foo2
IL_000C: brfalse.s IL_0014
IL_000E: ldloc.0 // foo2
IL_000F: callvirt UserQuery+Foo.Hello
IL_0014: ret
(三(
IL_0000: newobj UserQuery+Foo..ctor
IL_0005: isinst UserQuery.Foo
IL_000A: dup
IL_000B: brtrue.s IL_000F
IL_000D: pop
IL_000E: ret
IL_000F: call UserQuery+Foo.Hello
IL_0014: ret