1

我正在尝试优化我的代码的某个部分,这恰好处于一个紧密的性能循环中。主要是我想学习新的东西,我可以在未来应用。我的实现非常冗长,所以我将给出一个我想要实现的一般示例。

我的问题与此有关: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 和 B 的类型 A 的列表。另外,如果您觉得我没有提供足够的代码来澄清这种情况,我会很乐意扩展。

在我上面发布的问题: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.
   }
}

现在这很丑陋。我喜欢编写整洁、优雅的代码,但我在这方面做得特别差(这常常导致我浪费时间试图让事情看起来更好一点)。

我希望这个问题不要太宽泛,但首先我想知道在获取类型时是否有更优化和更干净的解决方案,以便执行相关行为。如果没有,有没有比这样嵌套更清洁的方式来使用这些“as”运算符?

我想一种替代方法是将行为移到基类 A 中,然后为每个派生类覆盖它。然而,在更高的思维意义上,我这个特殊情况下的行为不是 A 类(或者它的孩子)的行为,而是一些外部类在它上面的行为/行为(对于每种类型,它的行为会有所不同)。如果没有更好的方法来做到这一点,我将强烈考虑实施它,正如我现在解释的那样 - 但我希望对此有一些专家意见。

我试图保持简短,并且可能遗漏了太多细节。让我知道是否是这种情况。

4

4 回答 4

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 !

于 2012-05-28T22:51:55.740 回答
2

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

为此,首先使Process()方法不知道其参数的类型。可能该参数最终将成为一个接口,例如IDoSomething.

接下来,实现Process()它不会DoSomething()直接调用。您将不得不分解DoSomething()更小的代码块,这些代码块将被移动到IDoSomething方法的特定实现中。该Process()方法将盲目地调用这些方法——换句话说,将IDoSomething契约应用于某些数据。

越复杂,这可能会令人厌烦DoSomething(),但是您将拥有更好的关注点分离,并且将“打开”Process()任何IDoSomething兼容类型,甚至无需再编写一个else.

于 2012-05-28T22:54:12.243 回答
2

我发现的情况的最佳解决方案是使用双调度/访客模式。我描述了基类 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);
   { 

   }

}

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

于 2012-05-29T10:11:40.647 回答
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 个具有相同名称的类,因此如果您总是返回名称类,您有自动反冲突机制。

于 2012-05-28T22:38:17.653 回答