5

在下面的代码中,我有一个重载方法,一个采用 ClazzA 类型的参数,另一个采用 ClazzB 类型的参数。在所示代码中,调用了第一个 GetDescription 方法(以 ClazzA 作为参数的方法)。我想我明白为什么。
我的问题是..如果底层对象的类型为 classB(无需检查每个对象并将其转换为 clazzB),是否有一种优雅的方法可以首先调用 clazzB 的方法?

public class ClazzA
{
    public virtual string Descr { get { return "A"; } }
}

public class ClazzB : ClazzA
{
    public override string Descr { get { return "B"; } }
}

public static class test
{
    public static void Main()
    {
        ClazzA test = new ClazzB();
        GetDecription(test);
    }

    public static void GetDecription(ClazzA someClazz)
    {
        Debug.WriteLine("I am here");
    }

    public static void GetDecription(ClazzB someClazz)
    {
        Debug.WriteLine("I want to be here");
    }
}

输出:“我在这里”

我真的希望调用第二种方法,因为“测试”是 ClassB 类型。目前我仅有的两个解决方案是:

  1. if (test is ClazzB) return GetDescription( (ClazzB) test );

或者

  1. 在 ClassA 中做几乎相同的事情...检查类型并委托给第二个方法

这两者都需要检查对象以确定其类型

4

8 回答 8

9

重载是在编译时确定的。引用的编译时类型ClazzA是选择重载。您所要求的与多次调度有关。C# 和许多其他语言(如 C++ 和 Java)仅支持单一调度(通过virtual方法)。人们想出了很多方法来解决这个问题。最纯粹的 OO 方式是访问者模式。您修改类以包含一个方法 ( Accept),然后将this引用传递给访问者 ( Visit) 上的方法。这是有效的,因为您覆盖了Accept每个子类中的方法,这样这this将是对象的实际类型。所有访问者需要的是您想要支持的每个子类的特定方法(有关更多详细信息,请参见维基百科)。

一个样品:

public class ClazzA
{
   public virtual string Accept(ClassVisitor visitor)
   {
      return visitor.Visit(this);
   }
}

public class ClazzB : ClazzA
{
   public override string Accept(ClassVisitor visitor)
   {
      return visitor.Visit(this);
   }
}

public abstract class ClassVisitor
{
  public abstract string Visit(ClazzA a);
  public abstract string Visit(ClazzB b);
}

public class GetDescriptionVisitor : ClassVisitor
{
    public override string Visit(ClazzA a)
    {
       return "A";
    }

    public override string Visit(ClazzB b)
    {
       return "B";
    }
}

用法:

ClassVisitor visitor = new GetDescriptionVisitor();
ClazzA b = new ClazzB();
Console.WriteLine(b.Accept(visitor)); // prints "B"
于 2012-10-27T01:50:50.847 回答
3

因为方法重载决议发生在编译时。在处理这种情况方面,如果您使用的是 C# 4,那么您可以使用动态,以便将重载解决方案推迟到执行时。

dynamic instance = new ClazzB();
Console.WriteLine(GetDescription(instance));

或者,您可以使用类似以下的访问者模式,但这种双重调度方法感觉工作量很大。注意必须在每个派生类型中重新实现的重复访问方法!

public interface IVisitable
{
    string Visit(DescriptionVisitor visitor);
}

public class ClazzA : IVisitable
{
    public virtual string Visit(DescriptionVisitor visitor)
    {
        return visitor.Visit(this);
    }
}

public class ClazzB : ClazzA
{
    public override string Visit(DescriptionVisitor visitor)
    {
        return visitor.Visit(this);
    }
}

public class DescriptionVisitor
{
    public string Visit(ClazzA item) { return "Description A"; }
    public string Visit(ClazzB item) { return "Description B"; }
}

那么下面的内容最终还是会调用DescriptionVisitor中的重载,取ClazzB。

var visitor = new DescriptionVisitor();
ClazzA a = new ClazzB();
Console.WriteLine(a.Visit(visitor));
于 2012-10-27T02:17:56.553 回答
2

您尝试做的可能是使用多态性更好地执行,如下所示:

public interface IProvideDescription { 
  string GetDescription();
}

public class A : IProvideDescription {
  public string GetDescription() {
    return "I'm an A";
  }
}

public class B : IProvideDescription {
  public string GetDescription() {
    return "I'm a B";
  }
}

// to execute:

IProvideDescription x = new A();
Console.WriteLine(x.GetDescription());
x = new B();
Console.WriteLine(x.GetDescription());
于 2012-10-26T23:11:00.750 回答
1

要回答标题中的问题(“...为什么优先考虑基类?”),请查看您的变量test声明为什么(答案:您的基类)。选择重载时,方法调用所知道的就是您将一个类型的变量传递给ClazzA它。当然,您已经为它分配了一个类型的对象ClazzB,但假设您的赋值语句更复杂:

ClazzA test = GiveMeSomeObject();

方法选择必须在编译时进行以提供类型安全。

于 2012-10-26T23:18:43.077 回答
1

您可以使用 .Net 4.0 中引入的“动态”关键字来获得所需的行为。它在运行时评估类型并选择正确的重载。

public static class test
{
    public static void Main()
    {
        dynamic test = new ClazzB();
        GetDecription(test);
    }

    public static void GetDecription(ClazzA someClazz)
    {
        Debug.WriteLine("I am here");
    }

    public static void GetDecription(ClazzB someClazz)
    {
        Debug.WriteLine("I want to be here");
    }
}
于 2012-10-27T00:06:36.813 回答
1

通常,识别类类型不是外部类的责任。如果您需要多态行为,只需将 GetDescription 作为虚函数放入 ClassA 中,然后在 ClassB 中覆盖 - 这在概念上是正确的。

于 2012-10-27T07:29:58.623 回答
0

正如@roken 提到的,您的示例实际上会导致BDescr属性被覆盖。如果这就是您所做的一切,请删除ClazzB重载并使用您已经拥有的多态行为。dynamic如果您确实需要在方法中做一些不同的事情并且重载是最好的方法,您可以通过重载决议来做到这一点:

GetDecription((dynamic)test);

但是,这有一些缺点,例如性能和缺乏GetDescription(test)有意义的编译时测试。我建议在以下范围内进行运行时检查GetDecription(ClazzA)

if (someClazz is ClazzB)
{
    GetDescription((ClazzB)someClazz);
    return;
}
于 2012-10-26T23:08:28.767 回答
-2

您只需一种GetDescription()方法即可:

public String GetDescription(ClassA in) {
    if (in is ClassB) {
        return (in as ClassB).Descr 
    }

    return in.Descr;
}
于 2012-10-26T23:05:47.003 回答