最近我开始更多地使用接口和继承,我尝试将这些作为参数传递给函数,以使我的代码更加灵活。
但是当我使用泛型函数时,真正让我恼火的是我总是必须同时使用关键字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;
}
这肯定不会使代码更具可读性,在我看来并不需要。在我检查它是否使用mailObject.GetAttachments()
实现接口(或基本类)之后,为什么我不能使用is
?或者是否可能,我只是遗漏了什么?请用你的智慧赐教我!
答案 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
}
(顺便说一下,即使对于单一语句if
和foreach
机构,我也强烈建议使用大括号。这样可以更轻松地阅读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)程序有更多指令。但有趣的是is
和as
运算符都调用相同的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