关于c#:测试对象以查看它是否实现了接口有什么问题?

What is wrong with testing an object to see if it implements an interface?

在这个答案的注释中指出,"检查对象是否实现了接口,尽管它可能很猖獗,这是一件坏事。"

以下是我认为这一做法的一个例子:

1
2
3
4
5
6
7
8
9
10
11
12
13
public interface IFoo
{
   void Bar();
}

public void DoSomething(IEnumerable<object> things)
{
   foreach(var o in things)
   {
       if(o is IFoo)
           ((IFoo)o).Bar();
   }
}

我的好奇心像以前使用过这种模式变化的人一样被激起,我寻找了一个很好的例子或解释为什么它是一件坏事,却找不到。

虽然很可能我误解了评论,但有人能给我举个例子或链接来更好地解释评论吗?


这取决于你想做什么。有时可能是适当的-例如:

  • linq-to-objects,用于优化Count之类的操作,这些操作可以通过专门的成员在IList上更有效地执行。
  • linq-to-xml,用于提供一个非常友好的API,它接受各种类型,并在适当的时候迭代值。
  • 如果希望在Windows窗体中查找特定控件下某一类型的所有控件,则需要检查每个控件是否为容器,以确定是否重复。

在其他情况下,它不太合适,您应该考虑是否可以更改参数类型。这绝对是一种"味道"——通常,您不应该关心已经交付给您的任何东西的实现细节;您应该只使用声明的参数类型提供的API。这也被称为违反了里斯科夫替代原理。

不管周围那些武断的开发人员怎么说,有时候你只是想检查一个对象的执行时间类型。不使用is/as/GetType,很难正确地覆盖object.Equals(object),例如:这并不总是一件坏事,但它应该总是让你考虑是否有更好的方法。谨慎使用,只有在它真正是最合适的设计的地方。

我个人更愿意编写您这样显示的代码,注意:

1
2
3
4
5
6
7
public void DoSomething(IEnumerable<object> things)
{
    foreach(var foo in things.OfType<IFoo>())
    {
        foo.Bar();
    }
}

它完成了同样的事情,但以一种整洁的方式:)


我希望这个方法看起来像这样,看起来更安全:

1
2
3
4
5
6
7
public void DoSomething(IEnumerable<IFoo> things)
{
   foreach(var o in things)
   {
           o.Bar();
   }
}

阅读关于所提到的违反里斯科夫原理的文章:什么是里斯科夫替代原理?


如果你想知道评论人为什么要发表评论,最好让他们解释一下。

我不会认为你发布的代码是"坏的"。更"真实"的错误做法是使用接口作为标记。也就是说,您并不打算实际使用接口的方法;相反,您已经将类上的接口声明为以某种方式描述它的一种方法。使用属性而不是接口作为类上的标记。

标记接口在很多方面都是有害的。我曾经遇到的一个现实情况是: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_uux


一个原因是,如果不挖掘代码,就无法立即看到对该接口的依赖。


声明

checking whether the object has implemented the interface , rampant
as it may be, is a bad thing

在我看来太武断了。正如其他人所回答的,您很可能能够将一组ifoo传递给您的方法,并获得相同的结果。

然而,接口对于向类中添加可选功能是有用的。例如,.NET框架提供IDataErrorInfo接口*。当实现此功能时,它向使用者表明,除了类的标准功能之外,它还可以提供错误信息。

在这种情况下,错误信息是可选的。WPF视图模型可能提供错误信息,也可能不提供错误信息。如果不查询接口,如果没有具有巨大表面积的基类,则不可能实现此可选功能。

*我们暂时忽略IDataErrorInfo接口的糟糕设计。


我不喜欢这种"打开类型"的编码方式,原因有两个。(我的行业、游戏开发方面的例子。提前道歉。:))

首先也是最重要的一点,我认为拥有一个异构的项目集合是草率的。例如,我可以收集"任何地方的一切",但是当重复收集以应用子弹效果、火力伤害或敌人的人工智能时,我必须浏览这个列表,这是我最不关心的东西。有单独的子弹、熊熊烈火和敌人的集合是"更清洁"的。请注意,我没有理由不能在多个集合中拥有一个单一的项目;一个单一燃烧的机器人导弹可以在所有三个列表中被引用,以根据它需要运行的三种逻辑类型来做它的"更新"部分。除了拥有"引用所有内容的单个集合"之外,我认为包含所有地方的所有内容的集合并不是非常有用;除非您查询列表中的任何内容,否则无法对其执行任何操作。

我讨厌做不必要的工作。这确实与上面的内容有关,但是当您创建一个给定的对象时,您知道它的功能是什么(或者可以在此时查询它们),所以您也可以利用当时的机会将它们放入正确的更具体的集合中。你有16毫秒的时间来处理世界上所有的事情,你想浪费时间处理、查询和从一般的事情中选择,还是你想开始做生意,只对你关心的具体事情进行操作?

在我的经验中,将代码库从异构数据集的一般操作转换为具有同构数据集的代码库,不仅提高了性能,而且还提高了理解力,因为更简单的代码可以做更明显的工作,而且通常减少了执行任何给定任务所需的代码量。

所以,说查询接口不好是一种教条式的说法,但如果你能弄清楚如何避免查询任何东西,这确实让事情变得简单了。至于我的"表现"声明和计数器,"如果你不衡量它,你就不能说任何关于它的事情,"应该很明显,不做什么比做它更快。不管这对单个项目、程序员或函数是否重要,都取决于拥有编辑器的人,但是如果我可以简化代码,并且这样做可以使代码在相同结果下工作更少,那么我将在不进行度量的情况下完成它。


如果方法要求插入接口的实例,则无论实现如何,都应该对其进行相同的处理。在您的示例中,一般不会有对象的通用列表,但是一个ISomething和调用ISomething.Bar()的列表将由具体类型实现,因此称为实现。如果该实现什么都不做,那么您不必进行检查。


我一点也不认为这是一件"坏事情",至少它本身没有。这段代码只是"z中所有y元素的x"的文字转录,在需要这样做的情况下,这是完全可以接受的。为了简洁起见,您当然可以使用things.OfType()

反对它的主要原因是,根据OOP神学,接口旨在模拟对象可以替代的不同类型的"黑盒"。预测一个实现接口的算法构成了对该接口中的算法的移动行为。

本质上,界面是行为角色。如果您认为OOP是一个好主意,那么您应该只使用接口来建模行为,这样算法就不必这样做。我不认为现在的OOP是个好主意,所以我的答案是有用的。