异构列表是否有特定目的?


13

来自C#和Java背景,我习惯于列表是同质的,这对我来说很有意义。当我开始使用Lisp时,我注意到列表可能是异类的。当我开始使用dynamicC#中的关键字时,我注意到,从C#4.0开始,也可能存在异构列表:

List<dynamic> heterogeneousList

我的问题是什么?异构列表似乎在处理时会产生更多开销,并且如果您需要在一个地方存储不同的类型,则可能需要不同的数据结构。我的天真是在丑陋的面孔上浮出水面,还是真的有一次使用异类列表有用吗?


1
你是说...我注意到列表可能是不同的...?
世界工程师

如何List<dynamic>从单纯做不同的(你的问题)List<object>
彼得·K。

@WorldEngineer是的,我愿意。我已经更新了我的帖子。谢谢!
杰蒂2012年

@PeterK。我想对于日常使用而言,没有任何区别。但是,并非C#中的每种类型都派生自System.Object,因此在某些情况下会有差异。
杰蒂2012年

Answers:


16

Oleg Kiselyov,RalfLämmel和Keean Schupke撰写的论文《强类型异类集合》不仅包含Haskell中异构列表的实现,而且还提供了有关何时,为什么以及如何使用HLists的令人振奋的示例。特别是,他们正在使用它进行类型安全的编译时检查的数据库访问。(想想LINQ,实际上,他们所引用的论文由Erik Meijer等人,导致LINQ Haskell的纸。)

引用HLists的介绍性段落:

这是要求异构集合的典型示例的开放式列表:

  • 应该存储不同类型条目的符号表是异构的。这是一个有限映射,其结果类型取决于参数值。
  • XML元素是异构类型的。实际上,XML元素是嵌套的集合,受正则表达式和1-ambiguity属性约束。
  • SQL查询返回的每一行都是从列名到单元格的异构映射。查询的结果是异构行的同质流。
  • 向功能语言中添加高级对象系统需要一种异构集合,这种集合将可扩展记录与子类型和枚举接口结合在一起。

请注意,就问题而言,您给出的示例实际上不是异类列表,因为该词是常用的。它们是弱类型无类型列表。实际上,它们实际上是同质的列表,因为所有元素都是同一类型:objectdynamic。然后,您被迫执行强制转换或未经检查的instanceof测试或类似的操作,以便实际上能够有效地使用元素,这使它们的类型很弱。


感谢您的链接和您的回复。您指出列表确实不是异构的,但类型很弱。我很期待阅读该论文(可能明天,今天晚上给我
做个

5

长话短说,异构容器以运行时性能为代价。如果您想拥有“物料清单”而不考虑物料的特定类型,那么异质性就是解决之道。Lisps具有典型的动态类型,大多数内容无论如何都是装箱值的缺点列表,因此,预期会降低性能。在Lisp世界中,程序员的生产率被认为比运行时性能更重要。

在动态类型语言中,同构与异构容器相比容器实际上会稍有开销,因为添加的所有元素都需要进行类型检查。

关于选择更好的数据结构的直觉是正确的。一般来说,您可以在代码中放置的合同越多,对代码的工作方式的了解就越多,并且可靠性,可维护性和&c越多。它成为了。但是,有时您确实确实想要一个异构容器,并且如果需要,应该允许您拥有一个。


1
“但是,有时候您确实确实想要一个异构容器,如果需要,应该允许您拥有一个。” -为什么呢?那是我的问题。为什么您只需要将一大堆数据放入随机列表中呢?
杰蒂2012年

@Jetti:假设您有各种类型的用户输入设置的列表。您可以创建一个接口IUserSetting并多次实现它,也可以创建一个泛型UserSetting<T>,但是静态类型化的问题之一是您在确切地知道如何使用接口之前定义了一个接口。使用整数设置进行的操作可能与使用字符串设置进行的操作有很大不同,那么在公共接口中进行什么操作才有意义?直到您确定之前,最好明智地使用动态类型,并在以后使其具体化。
乔恩·普迪

看到那是我遇到问题的地方。对我来说,这似乎是一个糟糕的设计,在您知道它会/将被使用之前做些什么。同样,在这种情况下,您可以使接口带有对象返回值。与异构列表具有相同的功能,但是一旦您确定接口中使用的类型是什么,它就会更清晰,更容易修复。
杰蒂2012年

@Jetti:不过,这本质上是相同的问题-通用基类不应该首先存在,因为,不管它定义了什么操作,都会存在那些类型的操作没有意义的类型。但是,如果C#使使用a object而不是a 更容易dynamic,那么可以肯定地使用前者。
乔恩·普迪

1
@Jetti:这就是多态性。该列表包含许多“异构”对象,即使它们可能是单个超类的子类也是如此。从Java的角度来看,您可以正确获得类(或接口)定义。对于其他语言(LISP,Python等),正确地进行所有声明没有任何好处,因为在实现上没有实际差异。
S.Lott

2

在功能语言(如lisp)中,您可以使用模式匹配来确定列表中特定元素发生了什么。C#中的等效项将是一串if ... elseif语句,这些语句检查元素的类型并基于该元素执行操作。不用说,功能模式匹配比运行时类型检查更有效。

使用多态将更接近于模式匹配。也就是说,让列表的对象与特定接口匹配,然后在该接口上为每个对象调用一个函数。另一个选择是提供一系列重载方法,这些方法将特定对象类型作为参数。以Object为参数的默认方法。

public class ListVisitor
{
  public void DoSomething(IEnumerable<dynamic> list)
  {
    foreach(dynamic obj in list)
    {
       DoSomething(obj);
    }
  }

  public void DoSomething(SomeClass obj)
  {
    //do something with SomeClass
  }

  public void DoSomething(AnotherClass obj)
  {
    //do something with AnotherClass
  }

  public void DoSomething(Object obj)
  {
    //do something with everything els
  }
}

这种方法提供了Lisp模式匹配的近似值。访问者模式(如此处实现的,是异构列表用法的一个很好的例子)。另一个示例是消息调度,其中优先级队列中有某些消息的侦听器,并使用责任链,调度程序传递消息,而与该消息匹配的第一个处理程序将其处理。

另一面是通知每个注册消息的人(例如,通常用于MVVM模式中ViewModel的松耦合的Event Aggregator模式)。我使用以下构造

IDictionary<Type, List<Object>>

添加到字典的唯一方法是函数

Register<T>(Action<T> handler)

(该对象实际上是对传入的处理程序的WeakReference)。所以在这里我必须使用List <Object>,因为在编译时,我不知道封闭类型是什么。但是,在运行时,我可以强制将Type作为字典的键。当我想触发事件时,我打电话给

Send<T>(T message)

再一次,我解析列表。使用List <dynamic>没有任何优势,因为无论如何我都需要进行转换。因此,如您所见,这两种方法都有其优点。如果要使用方法重载动态分派对象,则动态方法就是做到这一点。如果无论如何都强制转换,不妨使用Object。


使用模式匹配时,情况(通常-至少在ML和Haskell中)通过声明匹配的数据类型固定在石头上。包含此类类型的列表也不是异类的。

我不确定ML和Haskell,但是Erlang可以与任何语言进行匹配,这意味着,如果您因为没有其他满意的匹配而到达此处,请执行此操作。
迈克尔·布朗

@MikeBrown-很好,但是没有涵盖为什么为什么要使用异构列表并且不能总是与List <dynamic>一起使用
Jetti 2012年

4
在C#中,重载在编译时解决。因此,您的示例代码将始终被调用DoSomething(Object)(至少objectforeach循环中使用时;dynamic是完全不同的事情)。
Heinzi

@Heinzi,您是对的...我今天很困:P固定
Michael

0

您是正确的,异构性会带来运行时开销,但更重要的是,它削弱了类型检查器提供的编译时保证。即使这样,在某些问题上替代方案的成本甚至更高。

以我的经验,通过文件,网络套接字等处理原始字节时,经常会遇到此类问题。

举一个真实的例子,考虑使用Future进行分布式计算的系统。单个节点上的工作人员可以生成任何可序列化类型的工作,从而产生该类型的未来。在幕后,系统将工作发送给对等方,然后保存一条记录,将该记录与该工作单元与特定未来相关联,一旦该工作的答案返回,则必须填写该记录。

这些记录可以保存在哪里?直观地,您想要的东西类似于Dictionary<WorkId, Future<TValue>>,但是这将您限制为在整个系统中仅管理一种类型的期货。更合适的类型是Dictionary<WorkId, Future<dynamic>>,因为当强制未来时,工人可以转换为合适的类型。

注意:此示例来自Haskell世界,在这里我们没有子类型。如果C#中的此特定示例有更惯用的解决方案,我不会感到惊讶,但是希望它仍然是说明性的。


0

ISTR指出Lisp除了列表之外没有任何数据结构,因此,如果您需要任何类型的聚合数据对象,它将必须是一个异构列表。正如其他人指出的那样,它们对于将数据序列化以进行传输或存储也很有用。一个不错的功能是它们也是开放式的,因此您可以在基于管道和过滤器类比的系统中使用它们,并具有连续的处理步骤来扩充或校正数据,而无需固定的数据对象或工作流拓扑。

By using our site, you acknowledge that you have read and understand our Cookie Policy and Privacy Policy.
Licensed under cc by-sa 3.0 with attribution required.