重载方法..为什么基类具有优先权?

在下面的代码中,我有一个重载方法,一个采用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"); } } 

输出:“我在这里”

我真的希望调用第二种方法,因为’test’是ClassB类型。 我唯一有两个解决方案是:

  1. if(test是ClazzB)返回GetDescription((ClazzB)测试);

要么

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

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

过载在编译时确定。 引用的编译时类型是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" 

您尝试做的事情可能更好地使用多态,如下所示:

 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()); 

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

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

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

 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"; } } 

然后,以下内容最终仍会调用带有ClazzB的DescriptionVisitor中的重载。

 var visitor = new DescriptionVisitor(); ClazzA a = new ClazzB(); Console.WriteLine(a.Visit(visitor)); 

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

 ClazzA test = GiveMeSomeObject(); 

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

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

正如@roken所提到的,你的例子实际上会导致B因为Descr属性被覆盖了。 如果这就是您正在做的全部,请删除ClazzB重载并使用您已经获得的多态行为。 如果你真的需要在方法中做一些不同的事情并且重载是最好的方法,你可以通过dynamic重载解析来做到这一点:

 GetDecription((dynamic)test); 

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

 if (someClazz is ClazzB) { GetDescription((ClazzB)someClazz); return; } 

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

 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"); } } 

只需一个GetDescription()方法即可完成:

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