为什么'为'已经使用'进行检查时访问功能所需的关键字是'关键词?

时间:2015-12-16 10:18:20

标签: c# generics interface

最近我开始更多地使用接口和继承,我尝试将这些作为参数传递给函数,以使我的代码更加灵活。 但是当我使用泛型函数时,真正让我恼火的是我总是必须同时使用关键字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;
}

这肯定不会使代码更具可读性,在我看来并不需要。在我检查它是否使用mailObject.GetAttachments()实现接口(或基本类)之后,为什么我不能使用is?或者是否可能,我只是遗漏了什么?请用你的智慧赐教我!

4 个答案:

答案 0 :(得分:4)

答案是因为你不能在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
}  

答案 1 :(得分:3)

  • 使用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运算符的双重投射,您的代码会更有效。

答案 2 :(得分:3)

  

为什么我在检查它是否实现了接口(或基本类)后才使用mailObject.GetAttachments()?

因为mailObject变量的编译时类型仍然只是T

请注意,目前您正在执行两次动态类型检查 - 通常首选的方法是:

var attachment = mailObject as IAttachment;
if (attachment != null)
{
    foreach (var att in attachment.GetAttachments())
    {
        mail.Attachmenets.Add(att);
    }
}

这样做的缺点是,我们现在在范围上有一个额外的变量。

C#7 可能通过模式匹配的新功能来解决此问题,因此使用current proposal您可以写:

if (mailObject is Attachment attachment)
{
    // Use attachment
}

(顺便说一下,即使对于单一语句ifforeach机构,我也强烈建议使用大括号。这样可以更轻松地阅读IMO,不必依赖缩进总是很完美。我已经看到太多SO问题,编码人员信任缩进而没有注意到他们的多个陈述并不真正相关......)

答案 3 :(得分:1)

你当然应该阅读Eric Lippert关于使用is的讨论。见http://ericlippert.com/2015/10/19/inferring-from-is/&amp; 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();

(2)

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         

(2)

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 ?.功能,那就变为:

(3)

object foo = new Foo();

(foo as Foo)?.Hello();

如果.Hello()foo(我使用Foo类型进行了测试),则只会调用预期的作品Bar

IL现在是这样的:

(3)

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   

这似乎是最短的代码,可能效率最高。时机将是下一个要做的事情,作为了解哪个horse is faster比赛的唯一方法。

根据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   

(2)

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         

(3)

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