在下面的代码中,我有一个重载方法,一个采用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类型。我唯一有两个解决方案是:
if(test is ClazzB) 返回GetDescription((ClazzB)测试);
或
这两个都需要检查对象以确定其类型
答案 0 :(得分:7)
重载是在编译时确定的。引用的编译时类型为ClazzA
,以便选择重载。你要求的是与多次派遣有关。 C#和许多其他语言(如C ++和Java)仅支持单一调度(通过virtual
方法)。人们已经通过多种方式来解决这个问题。最纯粹的OO方式是访客模式。您修改类以包含方法(Accept
),然后将this
引用传递给访问者(Visit
)上的方法。这是有效的,因为您覆盖每个子类中的Accept
方法,以便this
将成为对象的实际类型。所有访问者需求都是您要支持的每个子类的特定方法(有关详细信息,请参阅wikipedia)。
样本:
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"
答案 1 :(得分: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());
答案 2 :(得分:2)
因为方法重载解析在编译时发生。在处理这种情况时,如果您使用的是C#4,则可以使用dynamic,以便将重载决策推迟到执行时。
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));
答案 3 :(得分:1)
要回答标题中的问题(“...为什么基类具有优先权?”),请查看您的变量test
被声明为什么(回答:您的基类)。选择重载时,所有方法调用都知道您正在将ClazzA
类型的变量传递给它。当然,您已为其指定了ClazzB
类型的对象,但假设您的赋值语句更复杂:
ClazzA test = GiveMeSomeObject();
方法选择必须在编译时进行,以提供类型安全性。
答案 4 :(得分:1)
通常,识别类类型不是外部类的责任。 如果你需要多态行为,只需将GetDescription作为虚函数放入ClassA,然后在ClassB中重写 - 这在概念上是正确的。
答案 5 :(得分:0)
正如@roken所提到的,您的示例实际上会导致B
,因为Descr
属性被覆盖。如果这就是您正在做的全部,请删除ClazzB
重载并使用您已经获得的多态行为。如果你真的需要在方法中做一些不同的事情并且重载是最好的方法,你可以通过dynamic
重载决策来实现:
GetDecription((dynamic)test);
然而,这有一些缺点,例如性能和缺乏GetDescription(test)
有意义的编译时测试。我建议在GetDecription(ClazzA)
中进行运行时检查:
if (someClazz is ClazzB)
{
GetDescription((ClazzB)someClazz);
return;
}
答案 6 :(得分:0)
您可以使用.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");
}
}
答案 7 :(得分:-2)
您只需使用一个GetDescription()
方法即可:
public String GetDescription(ClassA in) {
if (in is ClassB) {
return (in as ClassB).Descr
}
return in.Descr;
}