我应该将继承类的类型存储为其属性,而不是使用`as`或`is`?

时间:2012-03-26 15:59:37

标签: c#

说,如果我想集中测试一些继承类的类型,并且需要良好的性能,我应该使用enum存储所有可能的类型,并使用枚举相等来测试类型而不是使用{{ 1}}。

e.g。

is

switch (myObject.Type)
      {
      case myType.type1:
                 myObject as myInheritedObject1;
                 ...
                 break;
      case myType.type2:
                 myObject as myInheritedObject2;
                 ...
                 break;
      case myType.type3:
                 myObject as myInheritedObject3;
                 ...
                 break;
         ...
     }

7 个答案:

答案 0 :(得分:10)

  

说,如果我想集中测试一些继承类的类型,并且需要良好的性能,我应该使用枚举存储所有可能的类型,并使用枚举相等来测试类型而不是使用is

这种机制通常被称为“歧视型”,仅供参考。

答案是:这取决于您的性能要求。歧视类型有许多缺点:

  • 他们使用更多的记忆;如果你有一百万个有区别类型的实例,并且鉴别器占用四个字节,那就是四个完全不必要的虚拟内存。如果对象长时间存在并且堆需要压缩,那么内存将被垃圾收集器复制。
  • 信息完全是多余的;对象已经知道它的类型是什么。这不仅浪费空间,而且是潜在的错误机会;任何时候您有冗余信息,您都有可能因为错误而导致信息不一致
  • 此模式脆弱;编写一个switch非常容易忘记一个案例,并在运行时可怕地死去,或者做一些疯狂的事情。
  • 这意味着使用此代码库的未来程序员将面临巨大的维护负担;他们必须确保他们可以在不破坏任何现有代码的情况下引入新的枚举值,或者从不在层次结构中引入新类型。
  • 该模式也很脆弱,因为它对深层次层次结构起作用;当你必须记住ColorFoo和FrobbyFoo以及BlobbyFoo是各种Foo时,编写多态代码会变得更加困难。如果你添加一种新的Foo怎么办?

它们的优势在于它们实际上比进行类型测试稍快。

因此,解决此问题的方法与解决任何其他性能问题的方法相同:

  • 与客户相关的所有绩效方面(包括时间,内存等)设置以客户为中心的有效绩效目标。
  • 撰写细致,真实,有意义的基准测试,在客户真实体验的现实条件下测试程序。
  • 如果您使用is等标准惯用语的效果不符合您的要求,则评估是否对有区别的类型进行更改会导致程序符合您的要求。如果确实如此,那么考虑这样做的成本并做出相应的决定。如果没有那么你有一个更大的问题需要解决,所以先解决这个问题。

在Roslyn团队中,我们以任何可能的方式都有极大限制性的性能要求,因此我们非常仔细地考虑了这个问题。我们决定采用在整个Roslyn中使用歧视类型的一般策略。这意味着我们必须非常小心关于我们设计交换机的方式,类型层次结构等等。我们还拥有一支专业的绩效团队,每天都会根据客户要求确认我们的绩效。

我们的要求与绝大多数业务线开发人员的性能要求完全不同;您不应该使用Roslyn代码作为确保良好性能的模型。相反,您应该使用我们的方法作为确保良好性能的模型:设置目标,经常测量,并且只有在实际上解决现有问题时才进行体系结构更改。 / p>

答案 1 :(得分:2)

理想情况下,您不必使用 - 而是使用多态来在派生类上执行所需的功能。

如果你必须确定我坚持as / is的类型 - 这不太可能是你的性能瓶颈。

答案 2 :(得分:2)

这感觉就像糟糕的OO设计。如果您提供有关您要完成的内容的更多信息,我确信我们可以提供更多帮助(您可以使用模板模式,或使用接口约束的通用方法等),但这种类型的编码结构将很快成为维护的噩梦。此外,以这种方式使用enum并不能真正解释继承层次结构。您可以在继承链中有一个is基类型以及多个子类的类,但它只能有一个enum值。

答案 3 :(得分:2)

如果你想密集测试一些继承类的类型,我会使用枚举,或者Eric Lippert称之为“Discriminated类型”。 Eric确定了这种技术的5个缺点,但我不同意大多数缺点。下面我将解决每一个问题:

  1. 他们使用更多内存。如果基类定义了子类中定义的抽象属性而不占用内存,则实例不需要占用更多内存。以下是一个例子:

    abstract class MyBase
    {
        // Define each child class as a new MyType enum
        public abstract MyType Type { get; }
    }
    class MyChild1 : MyBase
    {
        public override MyType Type
        {
            get { return MyType.Child1; }
        }
    }
    

    编辑:添加其他示例。以上是不使用额外内存的示例。下面将使用额外的内存,因为“Discriminated type”被保存为基类上的字段,而不是在子类的代码中设置。如果将Type属性设置为abstract并在子类的代码中定义,则不使用额外的内存。

    class MyBase
    {
        protected MyBase(MyType type) { this.type = type; }
        MyType type;
        public MyType Type { get { return type; } }
    }
    class MyChild1 : MyBase
    {
        public MyChild1() : base(MyType.Child1) { }
    }
    
  2. 信息完全是多余的。这是事实,但是使用枚举和抽象属性会向开发人员调用子类是“被识别的类型”并且他们需要解决其他代码。

  3. 这种模式很脆弱。是的,模式很脆弱,但使用“AS”和“IS”也是如此,如果你需要基于实例类型的逻辑,那么我更喜欢“识别类型”模式。
  4. 这意味着未来的程序员将面临巨大的维护负担。在我看来,“Discriminated Type”模式比“AS”和“IS”模式更少负担,并且更自我记录,代码更易于阅读和理解。
  5. 该模式也很脆弱,因为它适用于深层次的层次结构。我相信这是一件好事,因为如果你需要使用这种类型的模式,最好不要使用深层次的层次结构。
  6. 简而言之,如果确实需要这种类型的模式,并且您可以选择使用“识别类型”模式和“AS”/“IS”模式,我会选择前者,因为它更快,更容易阅读,更多自我记录。

答案 4 :(得分:1)

正如其他人所提到的那样,非常关注设计问题 - 你需要思考你的理由和“责任”...... 对于一个非常“多样化”的继承层次结构(尤其是深层嵌套层次结构),
您可能会考虑访问者模式,这在这种情况下通常是一个很好的解决方案并且可以最大限度地减少麻烦,而且您不必检查,自动编码“重新路由”事物(即'设计'在这里是合适的:)。
  但这只是一种问题的解决方案,有许多取决于你想要什么,需要。

希望它有所帮助(如果您需要任何示例,请告诉我。)

答案 5 :(得分:0)

我认为is应该没有性能问题,但根据您要解决的问题,您应该考虑使用接口来解耦您的类/模块。

答案 6 :(得分:0)

您的代码仍在执行冗余检查。如果要最小化它们,请使用(T) x强制转换语法而不是as。但是,此仍然需要在投射时执行一次检查,这是无法避免的。

也就是说,对于深度嵌套的层次结构(使用is需要遍历类型层次结构),使用枚举可以is更有效。对于平面层次结构,它不太可能更有效,因此您可以直接使用转换。

注意:由于as中隐式执行的检查,不合并isas - 这是一个反模式。而是使用以下模式:

myInheritedObject1 x = myObject as myInheritedObject1;
if (x != null) {
    // …
}

与使用is / as相比,这避免了一次冗余类型检查。

但是,最好的方法是不要进行投射并尽可能使用虚拟方法。