测试一个对象来查看它是否实现了一个接口有什么问题吗?
在这个答案的评论中 据说“检查对象是否实现了接口,尽管它可能很猖獗,但却是一件坏事”
下面是我相信的是这种做法的一个例子:
public interface IFoo
{
void Bar();
}
public void DoSomething(IEnumerable<object> things)
{
foreach(var o in things)
{
if(o is IFoo)
((IFoo)o).Bar();
}
}
作为一个以前使用过这种模式变体的人,我的好奇心激起了我,我寻找一个很好的例子或解释为什么它是一件坏事,但找不到一个。
虽然我很可能误解了该评论,但有人可以为我提供示例或链接来更好地解释该评论吗?
In the comments of this answer it is stated that "checking whether the object has implemented the interface , rampant as it may be, is a bad thing"
Below is what I believe is an example of this practice:
public interface IFoo
{
void Bar();
}
public void DoSomething(IEnumerable<object> things)
{
foreach(var o in things)
{
if(o is IFoo)
((IFoo)o).Bar();
}
}
With my curiosity piqued as someone who has used variations of this pattern before, I searched for a good example or explanation of why it is a bad thing and was unable to find one.
While it is very possible that I misunderstood the comment, can someone provide me with an example or link to better explain the comment?
如果你对这篇内容有疑问,欢迎到本站社区发帖提问 参与讨论,获取更多帮助,或者扫码二维码加入 Web 技术交流群。
绑定邮箱获取回复消息
由于您还没有绑定你的真实邮箱,如果其他用户或者作者回复了您的评论,将不能在第一时间通知您!
发布评论
评论(8)
这取决于您想要做什么。有时它可能是合适的 - 示例可能包括:
Count
之类的操作,这些操作可以通过以下方式在IList
上更有效地执行:专门成员。在其他情况下,它不太合适,您应该考虑是否可以更改参数类型。这绝对是一种“气味” - 通常您不应该关心交给您的任何内容的实现细节;您应该只使用声明的参数类型提供的 API。这也被称为违反里氏替换原则。
无论教条式的开发人员怎么说,有时您只是想检查对象的执行时间类型。如果不使用
is
/as
/GetType
就很难正确重写object.Equals(object)
,例如: )这并不总是坏事,但它应该总是让您考虑是否有更好的方法。谨慎使用,仅在真正最合适的设计中使用。请注意,我个人宁愿编写您所展示的代码,请注意:
它完成相同的事情,但以更简洁的方式:)
It depends on what you're trying to do. Sometimes it can be appropriate - examples could include:
Count
which can be performed more efficiently on anIList<T>
via the specialized members.In other cases it's less appropriate and you should consider whether you can change the parameter type instead. It's definitely a "smell" - normally you shouldn't concern yourself with the implementation details of whatever has been handed to you; you should just use the API provided by the declared parameter type. This is also known as a violation of the Liskov Substitution Principle.
Whatever the dogmatic developers around may say, there are times when you simply do want to check an object's execution time type. It's hard to override
object.Equals(object)
correctly without usingis
/as
/GetType
, for example :) It's not always a bad thing, but it should always make you consider whether there's a better approach. Use sparingly, only where it's genuinely the most appropriate design.I would personally rather write the code you've shown like this, mind you:
It accomplishes the same thing, but in a neater way :)
我希望该方法看起来像这样,看起来更安全:
要阅读有关违反里氏原则的信息:什么是里氏替换原理?
I would expect the method to look like this, it seems much safer:
To read about the referred violation of the Liskov Principle: What is the Liskov Substitution Principle?
如果您想知道评论者为何发表该评论,最好请他们解释一下。
我不认为您发布的代码是“坏”的。一个更“真正”的糟糕做法是使用接口作为标记。也就是说,您并不打算实际使用接口的方法;而是打算使用该接口的方法。相反,您已经在类上声明了接口,作为以某种方式描述它的方式。 使用属性而不是接口作为类上的标记。
标记接口在很多方面都是危险的。我曾经遇到过一个重要产品根据标记界面做出错误决定的真实情况:http://blogs.msdn.com/b/ericlippert/archive/2004/04/05/108086.aspx
也就是说,C# 编译器本身使用“标记接口”情况。 Mads 在这里讲述了这个故事:http ://blogs.msdn.com/b/madst/archive/2006/10/10/what-is-a-collection_3f00_.aspx
If you want to know why the commenter made that comment, probably best to ask them to explain.
I would not consider the code you posted to be "bad". A more "genuinely" bad practice is to use interfaces as markers. That is, you're not planning on actually using a method of the interface; rather, you have declared the interface on a class as a way of describing it in some way. Use attributes, not interfaces, as markers on classes.
Marker interfaces are hazardous in a number of ways. A real-world situation I once ran into where an important product made a bad decision on the basis of a marker interface is here: http://blogs.msdn.com/b/ericlippert/archive/2004/04/05/108086.aspx
That said, the C# compiler itself uses a "marker interface" in one situation. Mads tells the story here: http://blogs.msdn.com/b/madst/archive/2006/10/10/what-is-a-collection_3f00_.aspx
一个原因是,对该接口的依赖关系如果不深入代码就不会立即可见。
A reason is that there will be a dependency on that interface that is not immediately visible without digging in the code.
声明
在我看来 ,过于教条。正如其他人已经回答的那样,您很可能能够将 IFoo 集合传递给您的方法并获得相同的结果。
但是,接口对于向类添加可选功能很有用。例如,.net 框架提供 IDataErrorInfo 接口* 。实现此功能后,它向消费者表明除了类的标准功能之外,它还可以提供错误信息。
在这种情况下,错误信息是可选的。 WPF 视图模型可能会也可能不会提供错误信息。如果不查询接口,如果没有具有巨大表面积的基类,则无法实现此可选功能。
*我们暂时忽略 IDataErrorInfo 接口糟糕的设计。
The statement
Is overly dogmatic in my opinion. As other people have answered, you may well be able to pass a collection of IFoo to your method and achieve the same result.
However, interfaces can be useful to add optional features to classes. For example the .net framework provides the IDataErrorInfo interface*. When this is implemented it indicates to a consumer that in addition to the class' standard functionality, it can also provide error information.
In this case, the error information is optional. A WPF view model may or may not provide error information. Without querying for interfaces, this optional functionality would not be possible without base classes with huge surface area.
*We'll ignore for the moment the terrible design of the IDataErrorInfo interface.
如果您的方法要求您注入接口的实例,则无论实现如何,您都应该以相同的方式对待它。
在您的示例中,您通常不会有一个通用的对象列表,但是
ISomething
的列表并调用ISomething.Bar()
将由具体实现类型,因此称其为实现。如果该实现不执行任何操作,则无需进行检查。If your method requires that you inject an instance of an interface, you should treat it the same regardless of the implementation.
In your example you generally wouldn't have a generic list of object, but a list of
ISomething
's and calling anISomething.Bar()
would be implemented by the concrete type, therefore calling it's implementaiton. If that implementation is to do nothing, then you don't have to do a check.我不喜欢这种“开关类型”的编码风格,原因有几个。 (与我的行业、游戏开发相关的示例。提前道歉。:))
首先,我认为拥有异构的项目集合是草率的。例如,我可以拥有“无处不在的一切”的集合,但是当迭代该集合以应用子弹效果或火焰伤害或敌人 AI 时,我必须遍历这个列表,其中大部分是我不关心的东西。恕我直言,将子弹、熊熊大火和敌人分开收集会更“干净”。请注意,我没有理由不能在多个集合中包含单个项目;可以在所有三个列表中引用单个燃烧的机器人导弹,以根据其需要运行的三种逻辑类型进行部分“更新”。除了“引用所有内容的一个集合”之外,我认为包含所有内容的集合并不是很有用;除非您查询它可以做什么,否则您无法对列表中的任何内容执行任何操作。
我讨厌做不必要的工作。这确实与上面的内容相关,但是当您创建一个给定的事物时,您知道它的功能是什么(或者可以在那时查询它们),因此您不妨抓住当时的机会将它们放入正确的更具体的集合中。你有 16 毫秒的时间来处理世界上的一切,你想浪费时间处理、查询和选择一般的事物,还是想言归正传,只对你关心的具体事物进行操作?
根据我的经验,将代码库从异构数据集上的通用操作转换为具有同质数据集的代码库不仅会提高性能,还会提高理解力,这是因为更简单的代码可以完成更明显的工作,并且总体上减少了所需的代码量完成任何给定的任务。
所以,是的,说查询接口不好是教条式的,但如果你能弄清楚如何避免需要查询任何东西,它似乎会让事情变得更简单。至于我的“绩效”陈述和“如果你不衡量它,你就不能说什么”的反驳,很明显,不做某事比做某事更快。这对于单个项目、程序员或函数是否重要取决于编辑者,但如果我可以简化代码,并在这样做的同时使其减少工作量以获得相同的结果,我就会这样做无需费心去测量。
I dislike this whole "switch on type" style of coding for a couple of reasons. (Examples drawn in relation to my industry, game development. Apologies in advance. :) )
First and foremost, I think it's sloppy to have a heterogeneous collection of items. E.g. I could have a collection of "everything everywhere," but then when iterating the collection to apply bullet effects or fire damage or enemy AI, I have to walk this list which is mostly stuff I don't care about. It's much "cleaner" IMHO to have separate collections of bullets, raging fires, and enemies. Note that there's no reason why I can't have a single item in multiple collections; a single burning robotic missile could be referenced in all three of those lists to do parts of its "update" as appropriate for the three types of logic it needs to run. Outside of having "one single collection that references everything," I think a collection containing everything everywhere is not terribly useful; you can't do anything with anything in the list unless you query it for what it can do.
I hate doing unnecessary work. This really ties into the above, but when you create a given thing you know what its capabilities are (or can query them at that point), so you might as well take the opportunity at that time to put them in the right more specific collections. You have 16ms to process everything in the world, do you want to waste your time dealing with, querying, and selecting from generic things, or do you want to get down to business and operate only on the specific things you care about?
In my experience, transforming a codebase from generic operation on heterogeneous datasets to one that has homogeneous datasets has resulted in not only performance increases but also comprehension increases that come from simpler code doing more obvious work and in general a reduction in the amount of code required to do any given task.
So yeah, it's dogmatic to say that querying interfaces is bad, but it does seem to make things simpler if you can figure out how to avoid needing to query anything. As for my "performance" statements and the counter that "if you don't measure it, you can't say anything about it," it should be obvious that not doing something is faster than doing it. Whether or not this is important to an individual project, programmer, or function is up to the person with the editor, but if I can simplify code and while doing so make it do less work for the same results, I'm going to do it without bothering to measure.
我根本不认为这是一件“坏事”,至少其本身不是。该代码仅仅是“x all of the y in z”的字面转录,并且在您需要的情况下这样做,这是完全可以接受的。为了简洁起见,您当然可以使用
things.OfType()
。反对它的主要原因是,根据 OOP 神学,接口旨在对对象可以替代的不同类型的“黑匣子”进行建模。在接口的实现上预测算法构成了将行为转移到应该在该接口中的算法。
本质上,界面是一种行为角色。如果您认为 OOP 是一个好主意,那么您应该仅使用接口来建模行为,这样算法就不必这样做。我认为现在所谓的 OOP 实际上并不是一个好主意,所以这就是我的回答可能有用的范围。
I don’t see this as a “bad thing” at all, at least not in itself. The code is merely a literal transcription of “x all of the y in z”, and in a situation where you need to do that, it’s perfectly acceptable. You can of course use
things.OfType<Foo>()
for the sake of concision.The main reason to recommend against it is that, according to OOP theology, interfaces are intended to model the different kinds of “black box” for which an object may substituted. Predicating an algorithm on fulfillment of an interface constitutes moving behaviour to the algorithm that should be in that interface.
Essentially, an interface is a behavioural role. If you think OOP is a good idea, then you should use interfaces only to model behaviours, so that algorithms don’t have to. I don’t think what passes for OOP these days is in fact a good idea, so this is as far as my answer can be useful.