基于层次结构中的类型最佳且优雅地执行某些行为

时间:2012-05-28 22:27:54

标签: c# performance type-conversion hierarchy overloading

我正在尝试优化我的代码的某个部分,这恰好是在一个紧密的性能循环中。主要是我正在努力学习将来可以应用的新事物。我的实现非常冗长,所以我将举例说明我想要实现的目标。

我的问题与此有关:C# 'is' operator performance,尤其是所选择的答案。

假设我有一个A类。我还有一个B类,它是从A派生的。我有一个A类列表(包含A和B类型的混合)。在我处理这些项目的方法中,我希望根据对象的实际类型实现某种行为(不确定这是否是正确的说法。无论我说错什么,请纠正我。)

void Process(A item)
{
   if (item is A)
   {
      DoBehavior((A)item); //I know the cast is redundant here, I'm just leaving 
                           //it here for my explanation.
   }
   else if (item is B)
   {
      DoBehavior((B)item);
   }
}

void DoBehaviour(A item)
{
   //perform necessary behaviour for type A
}

void DoBehaviour(B item)
{
   //perform necessary behaviour for type B
}

这是我目前的做法。请注意,我遍历A类型的列表,其中包含A和B' s。此外,如果您觉得我没有提供足够的代码来澄清情况,我很乐意扩展。

在上面发布的问题C# 'is' operator performance中,我了解到我可以改变结构以使用" as"运算符,并完全摆脱显式演员。

B bItem = item as B;

if (bItem  != null)
{
    DoBehavior(bItem);
}

这一切都很好,但是,实际上我不仅有A和B,我还有C,D等等,所有这些都来自基类A.这将导致其中许多if语句,它们必须嵌套以获得最佳性能:

B bItem = item as B;

if (bItem  != null)
{
    DoBehavior(bItem);
}
else
{
   C cItem = item as C;
   if (cItem != null)
   {
      DoBehavior(cItem);
   }
   else
   {
      //and so on.
   }
}

现在这很难看。我喜欢编写整洁,优雅的代码,但我做得非常糟糕(这常常导致我浪费时间试图让事情看起来更好一些)。

我希望这个问题不是广泛的,但首先我想知道在获取类型时是否有更优化和干净的解决方案,以便执行相关行为。如果没有,是否有一种更清洁的方式来使用这些'作为'运营商比这样嵌套吗?

我想一种替代方法是将行为移动到基类A中,然后为每个派生类重写它。然而,在更高的思维意义上,我的这种特殊情况下的行为不是A类(或它的孩子)的行为,相反,它是一些在其上行动/表现的外部类(它会表现出来)每种类型都不同)。如果没有更好的方法,我会强烈考虑实施它,正如我现在解释的那样 - 但我想就此提出一些专家意见。

我试图保持这一点,可能会留下太多细节。如果是这种情况,请告诉我。

4 个答案:

答案 0 :(得分:2)

这不是多态性的全部意义吗?一种根据其类型具有不同行为的方法。而且我很确定这会比“打字机”更快 如果需要,您还可以使用函数重载(用于外部处理),请参阅下面的测试程序:

using System;
using System.Collections.Generic;

public class A
{
    public String Value
    {
        get;
        set;
    }

    public A()
    {
        Value = "A's value";
    }

    public virtual void Process()
    {
        // Do algorithm for type A
        Console.WriteLine("In A.Process()");
    }
}

public class B : A
{
    public int Health
    {
        get;
        set;
    }

    public B()
    {
        Value = "B's value";
        Health = 100;
    }

    public override void Process()
    {
        // Do algorithm for type B
        Console.WriteLine("In B.Process()");
    }
}

public static class Manager
{
    // Does internal processing
    public static void ProcessInternal(List<A> items)
    {
        foreach(dynamic item in items)
        {
            item.Process(); // Call A.Process() or B.Process() depending on type
            ProcessExternal(item);
        }
    }

    public static void ProcessExternal(A a)
    {
        Console.WriteLine(a.Value);
    }

    public static void ProcessExternal(B b)
    {
        Console.WriteLine(b.Health);
    }

    public static void Main(String[] args)
    {
        List<A> objects = new List<A>();
        objects.Add(new A());
        objects.Add(new B());
        ProcessInternal(objects);
    }
}

请注意,这只适用于.Net 4.0!

答案 1 :(得分:2)

我强烈建议您通过编程接口而不是引用具体类来避免使用“if..else if..else if ..”路径。

要实现此目的,首先要使Process()方法不知道其参数的类型。该参数可能最终会成为像IDoSomething这样的界面。

接下来,实施Process(),以便它不会直接调用DoSomething()。您将不得不在较小的代码块中中断DoSomething(),这些代码将被移动到IDoSomething方法的特定实现中。 Process()方法会盲目地调用这些方法 - 换句话说,将IDoSomething合约应用于某些数据。

这可能比较复杂DoSomething()更令人厌烦,但您可以更好地分离关注点,并且会Process()“打开”IDoSomething兼容类型,甚至不再写else

答案 2 :(得分:2)

我发现的最佳解决方案是使用Double-Dispatch / Visitor模式。我描述了一种情况,其中基类A是抽象的,具体的类B和C继承自A.另外,通过在基类A抽象中创建DoBehavior方法,我们强迫自己在任何需要它的地方进行实现。 ,所以如果我们扩展它以添加更多类型,我们不会忘记添加它的DoBehavior方法(似乎不太可能忘记,但这种行为可能对你添加的其他新类型无关紧要,可能会被忽视 - 特别是如果有很多这些行为模式)

interface IVisitor
{
   void DoBehavior(B item);
   void DoBehavior(C item);
}

abstract class A
{
    abstract void DoBehavior(IVisitor visitor);
}

class B : A
{
    override void DoBehavior(IVisitor visitor)
    {
       //can do some internal behavior here    
       visitor.DoBehavior(this); //external processing
    }
}

class C : A
{
    override void DoBehavior(IVisitor visitor)
    {
       //can do some internal behavior here   
       visitor.DoBehavior(this); //external processing
    }
}


class Manager: IVisitor //(or executor or whatever. The external processing class)
{

    public static void ProcessAll(List<A> items)
    {
        foreach(A item in items)
        {
            item.DoBehavior(this);
        }
    }

   void DoBehavior(B item)
   {

   }

   void DoBehavior(C item);
   { 

   }

}

感谢大家的贡献。学到了很多,并从大家那里得到了一些好主意(如果你遇到类似的情况,那么阅读所有答案是值得的。)

答案 3 :(得分:1)

一个简单的解决方案是在基类中添加一个指定类类型的字段。

    class A
    {
        // Alternative
        string typeName =  this.GetType().Name;
        public virtual string TypeName { get { return typeName; } }

        public virtual string GetTypeName() { return "A"; }
    }

    class B : A
    {
        public override string GetTypeName() { return "B"; }
    }

    class C : A
    {
        public override string GetTypeName() { return "C"; }
    }


    class Executer
    {

        void ExecuteCommand(A val)
        {
            Console.WriteLine(val.GetType().Name);

            switch (val.GetTypeName())
            {
                case "A": DoSomethingA(val as A); break;
                case "B": DoSomethingB(val as B); break;
                case "C": DoSomethingC(val as C); break;
            }
        }

        private void DoSomethingC(C c)
        {
            throw new NotImplementedException();
        }

        private void DoSomethingB(B b)
        {
            throw new NotImplementedException();
        }

        private void DoSomethingA(A a)
        {
            throw new NotImplementedException();
        }

    }

你真的不需要使用字符串,但我更喜欢使用整数的选项,原因很简单,你不能在同一个命名空间中声明同名的2个类,因此如果你总是返回名字类,你有一个自动反冲突机制。