类型测试什么时候可以?


53

假设一种语言具有某种固有的类型安全性(例如,不是JavaScript):

给定一个接受a的方法SuperType,我们知道在大多数情况下,我们可能会倾向于执行类型测试以选择一个动作:

public void DoSomethingTo(SuperType o) {
  if (o isa SubTypeA) {
    o.doSomethingA()
  } else {
    o.doSomethingB();
  }
}

通常,如果不是总是的话,我们应该在上创建一个可覆盖的方法SuperType并执行以下操作:

public void DoSomethingTo(SuperType o) {
  o.doSomething();
}

...,其中每个子类型都有自己的doSomething()实现。然后,我们应用程序的其余部分可以适当地忽略给定的SuperType是a SubTypeA还是a SubTypeB

精彩。

但是,我们仍然可以is a使用大多数(即使不是全部)类型安全语言进行类似操作。这表明可能需要显式类型测试。

那么,在什么情况下(如果有的话)我们应该还是必须执行显式类型测试?

原谅我心不在or或缺乏创造力。我知道我以前做过;但是,老实说,很久以前,我不记得自己做的是否好!在最近的记忆中,我认为我没有遇到过需要测试我的牛仔JavaScript之外的类型的需求



4
值得指出的是,Java和C#在其第一个版本中都没有泛型。您必须在Object之间进行转换才能使用容器。
2014年

6
“类型检查”几乎总是指检查代码是否遵守该语言的静态键入规则。您的意思通常称为类型测试


3
这就是为什么我喜欢编写C ++并保持RTTI关闭的原因。当一个人实际上不能在运行时测试对象类型时,它会迫使开发人员就此处提出的问题坚持良好的OO设计。

Answers:


48

对于“何时进行类型测试还行”的规范答案是“从不” 没有办法证明或反驳;它是关于什么构成“良好设计”或“良好面向对象设计”的信念体系的一部分。这也是hokum。

可以肯定的是,如果您拥有一组集成的类以及需要这种直接类型测试的一个或两个以上功能,那么您可能会做错了。您真正需要的是在SuperType其子类型中实现不同的方法 。这是面向对象编程的一部分,并且存在类和继承的全部原因。

在这种情况下,显式的类型测试是错误的,不是因为类型测试本质上是错误的,而是因为该语言已经具有完成类型歧视的干净,可扩展,惯用的方式,而您没有使用它。相反,您陷入了原始,脆弱,不可扩展的习惯用法。

解决方案:使用成语。如您所建议,为每个类添加一个方法,然后让标准继承和方法选择算法确定哪种情况适用。或者,如果您不能更改基本类型,请子类化并在其中添加方法。

对于传统的看法,这还有很多答案。显式类型测试有意义的某些情况:

  1. 这是一次性的。如果您要进行很多类型区分,则可以扩展类型或子类。但是你没有。您只有一两个地方需要显式测试,因此不值得花时间去遍历类层次结构以将函数添加为方法。否则,为这种简单,有限的使用而增加基类的一般性,测试,设计审查,文档或其他属性,这是不值得的实践工作。在那种情况下,添加执行直接测试的功能是合理的。

  2. 您无法调整课程。您可以考虑子类化,但是您不能。例如,指定了Java中的许多类final。您尝试抛出a public class ExtendedSubTypeA extends SubTypeA {...} ,编译器毫无疑问地告诉您您正在做的事情是不可能的。抱歉,面向对象的模型的优雅和精致!有人认为您不能扩展他们的类型!不幸的是,许多标准库都是final,并且制作类final是常见的设计指南。最终运行的功能就是您可以使用的功能。

    顺便说一句,这不仅限于静态类型的语言。动态语言Python具有许多基类,这些基类在C语言中实现了真正的修改。像Java一样,它包括大多数标准类型。

  3. 您的代码是外部的。您正在使用来自一系列数据库服务器,中间件引擎和无法控制或调整的其他代码库的类和对象进行开发。您的代码只是在其他地方生成的对象的低消耗者。即使您可以创建子类SuperType,也将无法获得您用来在子类中生成对象的库。他们将向您提供他们知道的类型的实例,而不是您的变体。并非总是如此...有时它们是为灵活性而构建的,它们会动态实例化您提供给它们的类的实例。或者,它们提供了一种机制来注册您希望其工厂构造的子类。XML解析器似乎特别擅长提供此类入口点。见例如Python中的lxml。但是大多数代码库都不 提供此类扩展。他们将把您带回与他们一起建立并了解的课程。通常仅将它们的结果代理到您的自定义结果中是没有意义的,因此您可以使用纯面向对象的类型选择器。如果要进行类型区分,则必须相对粗略地进行。然后,您的类型测试代码看起来非常合适。

  4. 可怜的人的仿制药/多次派遣。您想接受各种不同类型的代码,并认为拥有一系列非常特定于类型的方法并不理想。 public void add(Object x)似乎是合乎逻辑,但不是一个数组 addByteaddShortaddIntaddLongaddFloataddDoubleaddBooleanaddChar,和addString变种(仅举几例)。具有的功能或方法采用高级超级类型,然后逐个类型地确定要做什么—它们不会在年度Booch-Liskov设计研讨会上赢得您的纯度奖,而是放弃匈牙利语命名 将为您提供更简单的API。从某种意义上说,您is-ais-instance-of 测试是在本机不支持的语言环境中模拟通用或多调度。

    泛型鸭子类型的内置语言支持 通过使“优雅而恰当地执行某事”的可能性降低,从而减少了类型检查的需求。在类似Julia和Go这样的语言中看到的多个调度 /接口选择类似地用内置的机制代替直接类型测试,以基于类型的“做什么”的选择。但并非所有语言都支持这些语言。Java例如通常是单调度程序,它的习惯用法对鸭子类型不是超级友好的。

    但是,即使具有所有这些类型区分功能(继承,泛型,鸭类输入和多次分派),有时使用单个合并例程也很方便,该例程使您根据对象的类型来做某事清晰而直接。在元编程中, 我发现它是不可避免的。退回直接类型查询是构成“实用主义行动”还是“肮脏编码”将取决于您的设计理念和信念。


如果某个操作不能在特定类型上执行,而是可以在两个可以隐式转换为不同类型的类型上执行,则仅当两个转换都将产生相同的结果时,重载才是合适的。否则,最终会出现诸如.NET中的愚蠢行为:(1.0).Equals(1.0f)产生true [参数会提升为double],但是(1.0f).Equals(1.0)产生false [参数会提升为object];在Java中,Math.round(123456789*1.0)产生123456789,但Math.round(123456789*1.0)产生123456792 [参数提升为,float而不是double]。
2014年

这是反对自动类型转换/升级的经典论点。不良的和矛盾的结果,至少在边缘情况下如此。我同意,但不确定您打算如何将其与我的回答联系起来。
乔纳森·尤妮丝

我在回应您的观点4,这似乎是在倡导重载,而不是使用名称不同的方法。
2014年

2
@supercat怪我的视力不好,但是两个表情Math.round看起来和我一样。有什么不同?
莉莉·钟

2
@IstvanChung:糟糕……后者应该是Math.round(123456789)[指示如果有人重写Math.round(thing.getPosition() * COUNTS_PER_MIL)以返回未缩放的位置值,而未意识到会getPosition返回int或,会发生什么long。]
supercat

25

我曾经需要的主要情况是比较两个对象,例如在一个equals(other)方法中,根据的确切类型,可能需要使用不同的算法other。即使那样,它还是相当罕见的。

我遇到过的另一种情况(很少是)是在反序列化或解析之后,有时您需要将其安全地转换为更特定的类型。

另外,有时您只需要使用hack即可解决您无法控制的第三方代码。这是您不希望经常使用的东西之一,但是很高兴当您真正需要它时就可以使用它。


1
反序列化案例让我一时感到高兴,因为我错误地记得在那里使用过。但是,现在我无法想象我会怎么做!我知道我为此做了一些奇怪的类型查询。但我不确定这是否构成类型测试。也许序列化是一个更接近的并行:必须查询对象的具体类型。
svidgen 2014年

1
通常可以使用多态性进行序列化。反序列化时,通常会执行,之类的操作BaseClass base = deserialize(input),因为尚不知道类型,然后if (base instanceof Derived) derived = (Derived)base将其存储为确切的派生类型。
Karl Bielefeldt 2014年

1
平等是有道理的。以我的经验,这种方法通常具有这样的形式:“如果这两个对象具有相同的具体类型,则返回它们的所有字段是否相等;否则,返回false(或不可比)”。
乔恩·普迪

2
特别是,您发现自己属于测试类型这一事实很好地表明了多态相等性比较是毒蛇的巢穴。
史蒂夫·杰索普

12

标准(但希望很少见)情况如下所示:如果在以下情况下

public void DoSomethingTo(SuperType o) {
  if (o isa SubTypeA) {
    DoSomethingA((SubTypeA) o )
  } else {
    DoSomethingB((SubTypeB) o );
  }
}

的功能DoSomethingADoSomethingB不能容易地被实现为继承树的成员函数SuperType/ SubTypeA/ SubTypeB。例如,如果

  • 子类型是您无法更改的库的一部分,或者
  • 如果将代码添加DoSomethingXXX到该库将意味着引入禁止的依赖关系。

请注意,在很多情况下您都可以解决此问题(例如,通过为SubTypeA和创建包装器或适配器SubTypeB,或尝试DoSomething根据的基本操作完全重新实现SuperType),但有时这些解决方案不值得您麻烦或自找比进行显式类型测试更复杂,扩展性更差。

我昨天的工作中有一个例子:我遇到一种情况,我要并行处理对象列表(类型为SuperType,具有完全不同的两个子类型,这极不可能出现更多)的处理。非并行版本包含两个循环:一个循环用于子类型A的对象,调用DoSomethingA,第二个循环用于子类型B的对象,调用DoSomethingB

“ DoSomethingA”和“ DoSomethingB”方法都是时间密集型计算,使用的上下文信息在子类型A和B的范围内不可用(因此将它们实现为子类型的成员函数没有意义)。从新的“并行循环”的角度来看,通过统一处理它们使事情变得容易得多,因此我实现了与DoSomethingTo上面类似的功能。但是,查看“ DoSomethingA”和“ DoSomethingB”的实现可发现它们在内部的工作方式非常不同。因此,尝试通过SuperType使用许多抽象方法进行扩展来实现通用的“ DoSomething” 并不能真正起作用,或者意味着要完全过度设计。


2
您是否有可能添加一个小的具体示例来说服我的大脑模糊,这种情况并非人为?
svidgen 2014年

明确地说,我并不是说它人为的。我怀疑我今天真是太头脑干了。
svidgen 2014年

@svidgen:这绝非易事,实际上我今天遇到了这种情况(尽管我无法在此处发布此示例,因为它包含业务内部信息)。我同意其他答案,即使用“ is”运算符应该是一个例外,并且仅在极少数情况下可以使用。
布朗

我认为您的编辑(我忽略了)就是一个很好的例子。...是否有实例即使在您进行控制SuperType且它是子类的情况下也可以吗?
svidgen 2014年

6
这是一个具体的示例:来自Web服务的JSON响应中的顶级容器可以是字典或数组。通常,您有一些工具可以将JSON转换为真实的对象(例如,NSJSONSerialization在Obj-C中),但您不想简单地相信响应包含所需的类型,因此在使用它之前,请先对其进行检查(例如if ([theResponse isKindOfClass:[NSArray class]])...) 。
Caleb 2014年

5

正如鲍伯叔叔所说的那样:

When your compiler forgets about the type.

在他的Clean Coder情节之一中,他给出了一个用于返回Employees 的函数调用的示例。Manager是的子类型Employee。假设我们有一个应用程序服务,该服务接受一个Managerid并将其召唤到办公室:)该函数getEmployeeById()返回一个super-type Employee,但是我想检查在此用例中是否返回了管理器。

例如:

var manager = employeeRepository.getEmployeeById(empId);
if (!(manager is Manager))
   throw new Exception("Invalid Id specified.");
manager.summon();

在这里,我正在检查查询返回的雇员是否实际上是经理(即,我希望它是经理,否则很快就会失败)。

不是最好的例子,但毕竟是鲍伯叔叔。

更新资料

我对示例进行了尽可能多的记忆更新。


1
在此示例中,为什么不只抛出异常Manager的实现summon()
svidgen

@svidgen也许CEO可以召唤Managers。
user253751 2014年

@svidgen,那么现在还不清楚employeeRepository.getEmployeeById(empId)是否将返回经理
Ian

@Ian我认为这不是问题。如果调用代码要求的话Employee,它应该只在意它的行为像一样Employee。如果不同的子类Employee具有不同的权限,职责等,那么使类型测试比真实权限系统更好的选择是什么?
svidgen

3

什么时候进行类型检查可以?

决不。

  1. 通过在其他函数中具有与该类型关联的行为,您将违反开放式封闭原则,因为您可以通过更改is子句或(在某些语言中,或取决于方案),因为您不能在不修改执行is检查功能的内部的情况下扩展类型。
  2. 更重要的是,is检查是您违反Liskov替代原则的有力信号。任何适用的方法SuperType都应该完全不知道可能存在的子类型。
  3. 您正在将某些行为与类型的名称隐式关联。这使您的代码更难维护,因为这些隐式契约分布在整个代码中,并且不能保证像实际的类成员一样被普遍且一致地实施。

综上所述,is支票可以比其他替代品很多。将所有常用功能都放到基类中是费力的,通常会导致更严重的问题。使用一个带有标志或枚举的类来表示实例是什么“类型”……比可怕的糟糕,因为您现在正在将类型系统规避扩展到所有使用者。

简而言之,您应该始终将类型检查视为强烈的代码味道。但是,与所有准则一样,有时您会被迫在违反准则最少的情况下进行选择。


3
有一个小小的警告:用不支持它们的语言实现代数数据类型。但是,继承和类型检查的使用纯粹是其中的一个拙劣的实现细节。目的不是引入子类型,而是对值进行分类。我之所以提出这一点,仅是因为ADT很有用,而且从来都不是很强的限定词,但是我完全同意。instanceof泄漏实现细节并破坏抽象。
2014年

17
在这种情况下,“ Never”这个词我不是很喜欢,尤其是当它与您在下面的内容相矛盾时。
布朗

10
如果说“从不”实际上是“有时”,那么您是对的。
Caleb 2014年

2
OK表示允许,可接受等,但不一定是理想或最佳。与“ 不正确”对比:如果某事不正确,那么您根本不应该这样做。正如您所指出的那样,需要检查某种类型可能表明代码中存在更深层的问题,但是有时它是最方便,最不坏的选择,在这种情况下,可以在您的设备上使用这些工具显然可以处置。(如果很容易在所有情况下都避免它们,那么它们可能一开始就不会出现。)这个问题归结为确定那些情况,并且永远没有帮助。
Caleb 2014年

2
@supercat:方法应要求满足其要求的最小特定类型。 IEnumerable<T>不保证存在“最后一个”元素。如果您的方法需要这样的元素,则它应该需要一种可以保证存在的类型。然后,该类型的子类型可以提供“ Last”方法的有效实现。
cHao 2014年

2

如果您的代码库很大(超过10万行代码)并且即将交付或正在分支中工作,而以后必须将其合并,那么更改很多应付成本或风险很大。

然后,您有时可以选择使用系统的大型折射镜,或进行一些简单的局部“类型测试”。这会产生技术债务,应尽快偿还,但通常不会偿还。

(不可能提出一个示例,因为任何足够小的代码都可以用作示例的代码,也要足够小以使更好的设计清晰可见。)

换句话说,当目标是支付您的工资而不是为了设计的简洁性而获得“赞成票”时。


另一个常见的情况是UI代码,例如,当您为某些类型的员工显示不同的UI,但是显然您不希望UI概念转义到所有“域”类中。

您可以使用“类型测试”来决定显示哪个版本的UI,或者使用一些精美的查找表来将“域类”转换为“ UI类”。查找表只是一种将“类型测试”隐藏在一个地方的方法。

(数据库更新代码可能具有与UI代码相同的问题,但是您往往只有一组数据库更新代码,但是可以有许多不同的屏幕,这些屏幕必须适应所显示的对象的类型。)


访客模式通常是解决UI情况的好方法。
伊恩·戈德比

@IanGoldby,有时可能会同意,但是您仍在进行“类型测试”,只是有点隐藏。
2014年

隐藏与您调用常规虚拟方法时隐藏的含义相同吗?还是您还有其他意思?我使用的Visitor模式没有依赖于类型的条件语句。这一切都是由语言完成的。
伊恩·戈德比

@IanGoldby,我的意思是隐藏,因为它会使WPF或WinForms代码更难以理解。我希望对于某些基于Web的UI来说,它会很好地工作。
2014年

2

LINQ的实现使用大量类型检查来进行可能的性能优化,然后使用IEnumerable进行后备。

最明显的示例可能是ElementAt方法(.NET 4.5源的小节摘录):

public static TSource ElementAt<TSource>(this IEnumerable<TSource> source, int index) { 
    IList<TSource> list = source as IList<TSource>;

    if (list != null) return list[index];
    // ... and then an enumerator is created and MoveNext is called index times

但是Enumerable类中有很多地方都使用了类似的模式。

因此,对于常用子类型的性能进行优化也许是一种有效的用法。我不确定如何设计得更好。


可以通过提供一种方便的方法来更好地设计它,接口可以通过该方法提供默认的实现,然后IEnumerable<T>包括许多方法(如中的方法)List<T>以及一个Features属性,该属性指示可以预期哪些方法可以很好地,缓慢地或根本不起作用,以及消费者可以安全地对集合进行各种假设(例如,是否保证其大小和/或其现有 内容永不更改[一种类型可能会支持,Add同时仍保证现有内容将是不变的)。
2014年

除了在一种类型可能需要在不同时间保存两个完全不同的事物之一并且需求显然是互斥的(因此使用单独的字段将是多余的)的情况下,我通常认为需要try-casting是一个标志应该是基本接口一部分的成员却不是。这并不是说使用接口的代码本应包含一些成员,但不应该使用try-casting来解决基本接口的遗漏,但是编写基本接口的代码应使客户端的try-cast需求最小化。
2014年

1

在游戏开发中,尤其是在碰撞检测中,经常会出现一个例子,如果不使用某种形式的类型测试,就很难处理。

假定所有游戏对象都来自一个公共基类GameObject。每个对象具有刚性体碰撞形状CollisionShape,其可以提供一个通用的接口(说查询位置,方向等),但实际的碰撞形状都将是具体的子类,例如SphereBoxConvexHull等存储专用于该类型的几何对象的信息(有关实际示例,请参见此处

现在,要测试碰撞,我需要为每对碰撞形状类型编写一个函数:

detectCollision(Sphere, Sphere)
detectCollision(Sphere, Box)
detectCollision(Sphere, ConvexHull)
detectCollision(Box, ConvexHull)
...

包含执行这两种几何类型的相交所需的特定数学。

在游戏循环的每个“滴答声”中,我需要检查对象对是否存在碰撞。但是我只能访问GameObject和及其对应CollisionShape的。显然,我需要知道具体的类型才能知道要调用哪个碰撞检测函数。甚至没有双重调度(从逻辑上来说,与检查类型没有区别)在这里可以提供帮助*。

在这种情况下,实际上,我所见过的物理引擎(Bullet和Havok)依赖于一种或另一种形式的类型测试。

我并不是说这一定是一个很好的解决方案,只是它可能是少数可能解决该问题的最佳方法

*技术上讲,它可能的,将需要N(N + 1)/ 2的组合(其中N是你有形状类型的数量),只会混淆你真的做什么可怕的,复杂的方式来使用双传递,其中同时发现两种形状的类型,所以我认为这不是一个现实的解决方案。


1

有时您不想向所有类添加通用方法,因为执行特定任务实际上不是他们的责任。

例如,您想要绘制一些实体,但不希望直接向其添加绘制代码(这很有意义)。在不支持多调度的语言中,您可能会得到以下代码:

void DrawEntity(Entity entity) {
    if (entity instanceof Circle) {
        DrawCircle((Circle) entity));
    else if (entity instanceof Rectangle) {
        DrawRectangle((Rectangle) entity));
    } ...
}

当此代码出现在多个位置,并且在添加新的Entity类型时需要在任何地方进行修改时,这将成为问题。如果是这种情况,则可以通过使用Visitor模式来避免,但是有时最好只是保持简单而不过度设计。这些就是类型测试可以的情况。


0

我唯一使用的时间是结合反射。但是即使那样,动态检查还是大部分,而不是硬编码为特定类(或仅硬编码为特殊类,例如Stringor List)。

通过动态检查,我的意思是:

boolean checkType(Type type, Object object) {
    if (object.isOfType(type)) {

    }
}

而不是硬编码

boolean checkIsManaer(Object object) {
    if (object instanceof Manager) {

    }
}

0

类型测试和类型转换是两个非常相关的概念。关系非常密切,我觉得说你应该有信心绝不做型式试验,除非你的目的是类型转换基于结果的对象。

当你想到理想的面向对象设计,型式试验(铸造)应该不会发生。但是希望现在您已经发现面向对象的编程不是理想的。有时,尤其是在使用较低级代码的情况下,代码无法保持理想状态。Java中的ArrayList就是这种情况。因为他们在运行时不知道数组中存储了什么类,所以他们创建Object[]数组并将其静态转换为正确的类型。

有人指出,类型测试(和类型转换)的普遍需求来自于该Equals方法,在大多数语言中,该方法应该是简单的Object。该实现应进行一些详细的检查,以确保两个对象是否为同一类型,这需要能够测试它们是什么类型。

类型测试也经常反映出来。通常,您将拥有返回的方法Object[]或其他通用数组,并且Foo出于任何原因都想提取所有对象。这是类型测试和强制转换的完全合法使用。

通常,如果类型测试不必要地将您的代码与特定实现的编写方式耦合在一起,则它很糟糕。这很容易导致需要针对每种类型或类型组合进行特定的测试,例如是否要查找线,矩形和圆的交点,并且交点函数对每种组合都有不同的算法。您的目标是将特定于一种对象的所有详细信息放在与该对象相同的位置,因为这样可以更轻松地维护和扩展您的代码。


1
ArrayLists不知道在运行时存储该类是因为Java没有泛型,而当它们最终引入时,Oracle选择了与无泛型代码的向后兼容性。equals有同样的问题,无论如何这是一个有问题的设计决定;平等比较并非对每种类型都有意义。
Doval

1
从技术上讲,Java集合不会将其内容转换为任何内容。编译器会在每次访问的位置注入类型转换:例如String x = (String) myListOfStrings.get(0)

我上次查看Java源代码(可能是1.6或1.5)时,在ArrayList源代码中进行了显式转换。出于充分的原因,它会生成(抑制的)编译器警告,但是仍然允许。我想你可以说,由于泛型的实现方式,它始终是一个Object直到被访问为止。Java中的泛型仅提供由编译器规则保证安全的隐式转换。
meustrus

0

在必须做出涉及两种类型的决策并将该决策封装在该类型层次结构之外的对象中的情况下,这是可以接受的。例如,假设您正在计划等待处理的对象列表中接下来要处理的对象:

abstract class Vehicle
{
    abstract void Process();
}

class Car : Vehicle { ... }
class Boat : Vehicle { ... }
class Truck : Vehicle { ... }

现在让我们说我们的业务逻辑实际上是“所有汽车都优先于船和卡车”。Priority在类中添加属性不允许您清晰地表达此业务逻辑,因为您最终会得到以下结果:

abstract class Vehicle
{
    abstract void Process();
    abstract int Priority { get }
}

class Car : Vehicle { public Priority { get { return 1; } } ... }
class Boat : Vehicle { public Priority { get { return 2; } } ... }
class Truck : Vehicle { public Priority { get { return 2; } } ... }

问题在于,现在要了解优先级顺序,您必须查看所有子类,或者换句话说,您已将耦合添加到子类。

当然,您应该将优先级设置为常量,然后将它们自己放入一个类,这有助于将业务逻辑安排在一起:

static class Priorities
{
    public const int CAR_PRIORITY = 1;
    public const int BOAT_PRIORITY = 2;
    public const int TRUCK_PRIORITY = 2;
}

但是,实际上,调度算法可能会在将来发生变化,并且最终可能不仅仅取决于类型。例如,它可能会说“重量超过5000公斤的卡车比所有其他车辆都具有特殊的优先权”。这就是调度算法属于其自己的类的原因,并且检查类型以确定哪个应该首先使用是一个好主意

class VehicleScheduler : IScheduleVehicles
{
    public Vehicle WhichVehicleGoesFirst(Vehicle vehicle1, Vehicle vehicle2)
    {
        if(vehicle1 is Car) return vehicle1;
        if(vehicle2 is Car) return vehicle2;
        return vehicle1;
    }
}

这是实现业务逻辑的最直接方法,并且对于将来的更改仍然是最灵活的。


我不同意您的特定示例,但确实同意拥有私有引用的原则,该引用可能包含具有不同含义的不同类型。我建议作为一个更好的示例的是可以容纳null,a String或a 的字段String[]。如果99%的对象仅需要一个字符串,则将每个字符串封装在单独构造的中String[]可能会增加可观的存储开销。使用直接引用a处理单字符串的情况String将需要更多代码,但会节省存储空间,并且可能使处理速度更快。
2014年

0

类型测试是一种工具,请明智地使用它,并且可以成为强大的盟友。如果使用不当,您的代码将开始发臭。

在我们的软件中,我们通过网络收到了响应请求的消息。所有反序列化消息都共享一个公共基类Message

这些类本身非常简单,只是将有效载荷作为类型化的C#属性以及用于编组和解组它们的例程(实际上,我使用t4模板从消息格式的XML描述生成了大多数类)

代码类似于:

Message response = await PerformSomeRequest(requestParameter);

// Server (out of our control) would send one message as response, but 
// the actual message type is not known ahead of time (it depended on 
// the exact request and the state of the server etc.)
if (response is ErrorMessage)
{ 
    // Extract error message and pass along (for example using exceptions)
}
else if (response is StuffHappenedMessage)
{
    // Extract results
}
else if (response is AnotherThingHappenedMessage)
{
    // Extract another type of result
}
// Half a dozen other possible checks for messages

当然,有人可能会争辩说,可以更好地设计消息体系结构,但是它是很久以前设计的,而不是为C#设计的,所以它就是它。在这里,类型测试以一种不太破旧的方式为我们解决了一个真正的问题。

值得一提的是C#7.0正在获得模式匹配(在许多方面,这都是对类固醇的类型测试),并不能完全坏...


0

采取通用的JSON解析器。解析成功的结果是数组,字典,字符串,数字,布尔值或空值。可以是任何一个。数组的元素或字典中的值可以再次是任何这些类型。由于数据是从程序外部提供的,因此您必须接受任何结果(也就是说,您必须接受它而不会崩溃;您可以拒绝不是您期望的结果)。


好吧,如果您的结构中包含“推理字段”的类型信息,则某些JSON反序列化器将尝试实例化对象树。但是,是的。我认为这就是Karl B的答案所指向的方向
。– svidgen
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.