当需要设计模块化架构时,对象铸造是现实的必然性吗?

Is object casting an inevitability of reality when there is a need to design modular architecture?

普遍认为对象转换是一种不好的做法,应该避免,例如Why should casting be avoided?问题已经得到了一些有很好论据的答案:

  1. By Jerry Coffin:

    Looking at things more generally, the situation's pretty simple (at least IMO): a cast (obviously enough) means you're converting something from one type to another. When/if you do that, it raises the question "Why?" If you really want something to be a particular type, why didn't you define it to be that type to start with? That's not to say there's never a reason to do such a conversion, but anytime it happens, it should prompt the question of whether you could re-design the code so the correct type was used throughout.

  2. By Eric Lippert:

    Both kinds of casts are red flags. The first kind of cast raises the question "why exactly is it that the developer knows something that the compiler doesn't?" If you are in that situation then the better thing to do is usually to change the program so that the compiler does have a handle on reality. Then you don't need the cast; the analysis is done at compile time.

    The second kind of cast raises the question "why isn't the operation being done in the target data type in the first place?" If you need a result in ints then why are you holding a double in the first place? Shouldn't you be holding an int?

继续我的问题,最近我开始研究由 Mark Seemann 最初开发的著名开源项目 AutoFixture 的源代码非常感谢。

库的主要组件之一是接口 ISpecimenBuilder,它定义了某种抽象方法:

object Create(object request, ISpecimenContext context);

如你所见,请求参数类型是对象,因此它接受完全不同的类型,接口的不同实现根据它们的运行时类型处理不同的请求,检查它是否是他们电缆处理的东西,否则返回某种无响应表示。

界面的设计似乎没有遵循"good practice"应该稀疏地使用对象转换

我在想是否有更好的方法来设计这个合同以击败所有铸造但找不到任何解决方案。
显然,对象参数可以用一些标记接口替换,但它不会为我们解决转换问题,我还认为可以使用here所述的访问者模式的一些变体,但它似乎不是非常可扩展,访问者必须有几十种不同的方法,因为有这么多不同的接口实现能够处理不同类型的请求。

尽管我基本上同意反对在这种特定情况下将转换作为良好设计的一部分的论点,但它似乎不仅是最佳选择,而且是唯一现实的选择。

综上所述,当需要设计模块化和可扩展的架构时,对象铸造和非常普遍的契约是现实的必然性吗?

我不认为我可以针对任何类型的应用程序或框架回答这个问题,但我可以提供一个专门讨论 AutoFixture 的答案,并提供一些关于其他使用场景的推测。

如果我今天必须从头开始编写 AutoFixture,我肯定会做一些不同的事情。特别是,我不会围绕 ISpecimenBuilder 之类的东西设计日常 API。相反,我会围绕仿函数和单子的概念设计数据操作 API,如 outlined here.

此设计完全基于泛型,但它确实需要在编译时已知的静态类型构建块(也在文章中描述)。

这与 QuickCheck 的工作原理密切相关。当您编写基于 QuickCheck 的测试时,您必须为您自己的所有自定义类型提供生成器。 Haskell 不支持 运行 时间值转换,而是完全依赖泛型和一些编译时自动化。诚然,Haskell 的泛型比 C# 的更强大,因此您不一定能将从 Haskell 获得的知识转移到 C#。但是,它确实表明可以完全不依赖 运行 时间转换来编写代码。

但是,AutoFixture 确实支持用户定义的类型,而无需用户编写自定义生成器。它通过 .NET 反射来实现。在 .NET 中,Reflection API 是无类型的;所有生成对象和调用成员的方法都以object作为输入,return object作为输出。

任何基于反射的应用程序、库或框架都必须执行一些 运行 时间转换。我不知道如何解决这个问题。

是否可以在没有反射的情况下编写数据生成器?我没有尝试过以下方法,但也许可以采用一种策略,即直接在 IL 中为数据生成器编写 'the code' 并使用 Reflection emit 动态编译一个 in -包含发电机的内存组件。

这有点像 Hiro container 的工作方式,IIRC。我想人们可以围绕这个概念设计其他类型的通用框架,但我很少看到它在 .NET 中完成。