为什么当已经使用“is”关键字检查时,需要“as”关键字来访问功能

本文关键字:关键字 需要 as 功能 访问 检查 is 为什么 | 更新日期: 2023-09-27 18:26:56

最近我开始更多地使用接口和继承,我尝试将它们作为参数传递到函数中,以使我的代码更加灵活。但是当我使用泛型函数时,真正让我恼火的是,我总是必须同时使用关键字isas。例如:

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()?还是有可能,我只是错过了什么?请用你的智慧启发我,所以学姐!

为什么当已经使用“is”关键字检查时,需要“as”关键字来访问功能

答案是,因为不能改变 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
}

(顺便说一句,我强烈建议使用大括号,即使是单语句ifforeach体。它使阅读 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(程序还有更多说明。但有趣的是,isas运算符都调用相同的isinst指令。然后,is运算符只需与null进行比较。

现在,如果我更改程序以使用 C# 6 ?.功能,它将变为:

(三(

object foo = new Foo();
(foo as Foo)?.Hello();

工作符合预期 - 只有当fooFoo时,才会调用.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