.Netinheritance和方法重载
这是一个代码示例:
class Program { static void Main(string[] args) { var obj = new DerivedClass(); obj.SomeMethod(5); } } class BaseClass { internal void SomeMethod(int a) { } } class DerivedClass : BaseClass { internal void SomeMethod(long a) { } }
有人可以解释一下为什么派生类调用的方法(而不是基类方法)? 我需要详细解释这种情况。 我将非常感谢任何有用文章的链接。
谢谢。
精确的措辞和位置因规范的不同版本而异,但是例如这里可以读取:
构造了方法调用的候选方法集。 从与先前成员查找(第7.3节)找到的与M关联的方法集开始,该集合被简化为适用于参数列表A的那些方法。集合缩减包括应用以下规则集合中的每个方法TN,其中T是声明方法N的类型:
如果N不适用于A(§7.4.2.1),则从集合中删除N.
如果N适用于A(§7.4.2.1),则从集合中删除在基本类型T中声明的所有方法。
因此,假设我们有类型为DerivedClass
obj
,那么成员方法集包含来自DerivedClass
void SomeMethod(int)
来自BaseClass
void SomeMethod(int)
。
这两种方法都适用,实际上void SomeMethod(int)
是一个更好的重载匹配,但是由于上面引用的最后一句中的规则,一旦发现void SomeMethod(long)
适用,所有来自基类的方法从候选集中删除,意味着不再考虑void SomeMethod(int)
。
好的,这是规范方面的技术原因。 首先是在规范中背后的设计原因是什么?
好吧,想象一下BaseClass
定义是:
public class BaseClass { }
如果代码的其余部分是相同的,那么对obj.SomeMethod(5)
调用应该调用唯一存在的如此命名的方法。
现在考虑是否在编写代码之后 ,将方法void SomeMethod(int)
添加到BaseClass
。 并且确实考虑到这可能与DerivedClass
的不同程序集以及单独的作者。
现在调用SomeMethod()
的含义已经改变。 更糟糕的是,它的变化与否取决于给定机器已应用或未应用的更新。 (更糟糕的是,由于返回类型未在C#重载解析中使用,因此它的更改方式可能会在已编译的代码中产生编译错误:完全中断更改)。
如果存在来自更派生类的重载候选者,则排除基类中定义的方法的规则允许更好地保证在面对未来的更改时调用方法调用方法。 (当然,如果您打算调用基类方法,您可能会感到惊讶,但在编码时您可以捕获该问题并使用强制转换来确保您想要的行为是由此产生的)。
这种情况的结果可能令人惊讶,但是:
class Program { static void Main(string[] args) { var obj = new DerivedClass(); obj.SomeMethod(5); } } class BaseClass { public virtual void SomeMethod(int a) { Console.WriteLine("Base"); } } class DerivedClass : BaseClass { public override void SomeMethod(int a) { Console.WriteLine("Defined in Base, overriden in Derived"); } public void SomeMethod(long a) { Console.WriteLine("Derived"); } }
这会输出Derived
,因为此规则根据声明方法的位置应用,即使存在来自覆盖的实现。
(规则正常工作的另一个原因是,当它被转换为CIL时,调用将包含有关它声明的类的信息。这里的规则是最简单的处理方式。那就是说; 1)类似的逻辑应用于CIL的设计和2)上面使这个CCI的一个function为C#人工作,而不是一个工作对抗)。
var obj = new DerivedClass();
var
关键字只是C#中的语法糖; 这基本上与以下相同:
DerivedClass obj = new DerivedClass();
因此,您正在调用DerrivedClass.SomeMethod
,这只是您遇到的行为。 如果你定义了这样的变量,你会看到一个区别:
BaseClass obj = new DerivedClass();
评论后编辑:是的,我可能没有正确回答确切的问题,所以现在让我试试:
原始代码中的方法调用匹配两个方法的签名(在基类和派生类中),因为在这种情况下参数5
可以是int
或long
。 但是,基本方法没有标记为virtual
(允许覆盖),并且“衍生”方法并非真正派生,因为它没有标记为override
。
但请注意,即使您将其标记为override
,也会出现错误,因为实际上,这两个方法签名并不等效:一个采用int
,而另一个采用long
类型。 这将导致与messsage的编译时错误:“ 找不到合适的方法来覆盖 ”。
如果您阅读下面的原始答案的其余部分,其余部分应该有希望变得清晰。
原始答案:
这里有几点需要注意:
1)你的方法有不同的签名; 一个需要很长时间,另一个需要一个int
2)您尚未将方法标记为virtual
或override
。
包含一些注释的代码的编辑版本可能会更清楚地说明这些内容的工作原理:
internal class Program { private static void Main(string[] args) { var obj = new DerivedClass(); // That is the same as: //DerivedClass obj = new DerivedClass(); // Will call the base method, since that now matches the // signature (takes an int parameter). DerivedClass simply // does not HAVE a method with that signature on it's own: obj.SomeMethod(5); // will output "base with int" // Now call the other method, which IS defined in DerivedClass, // by appending an "l", to mark this as a Long: obj.SomeMethod(5l); // Will output "derived" // This would call the base method directly var obj2 = new BaseClass(); obj2.SomeMethod(5l); Console.ReadKey(); } } internal class BaseClass { internal void SomeMethod(int a) { Console.WriteLine("base with int"); } // Added method for the example: // Note that "virtual" allows it to be overridden internal virtual void SomeMethod(long a) { Console.WriteLine("base with long"); } } internal class DerivedClass : BaseClass { // Note: Overrides the base method now internal override void SomeMethod(long a) { Console.WriteLine("derived"); } }
从C#语言参考 :
7.5.5函数成员调用
本节描述在运行时调用特定函数成员的过程。 假设绑定时进程已经确定了要调用的特定成员,可能是通过将重载解析应用于一组候选函数成员。
为了描述调用过程,函数成员分为两类:
- 静态function成员。
- 实例function成员。 这些是实例方法,实例属性访问器和索引器访问器。 实例函数成员是非虚拟或虚拟的 ,并且始终在特定实例上调用。 该实例由实例表达式计算,并且可以在函数成员中访问它(第7.6.7节)。 函数成员调用的运行时处理包括以下步骤,其中M是函数成员,如果M是实例成员,则E是实例表达式:
- 如果M是静态函数成员:
- 如果M是在value-type:
声明的实例函数成员
- 如果M是在reference-type中声明的实例函数成员:
- E被评估。 如果此评估导致exception,则不执行进一步的步骤。
- 参数列表按照§7.5.1中的描述进行评估。
- 如果E的类型是值类型,则
- 检查E的值是否有效。 如果E的值为null,则抛出System.NullReferenceException,并且不执行进一步的步骤。
- 确定要调用的函数成员实现:
- 如果E的绑定时间类型是接口,则
- 否则,如果M是虚函数成员,则
- 否则,M是非虚函数成员,要调用的函数成员是M本身。
- 调用在上面的步骤中确定的函数成员实现。 E引用的对象成为此引用的对象。
我们有1.6.6.4虚拟,覆盖和抽象方法中的内容
调用虚方法时,进行该调用的实例的运行时类型决定了要调用的实际方法实现。 在非虚方法调用中, 实例的编译时类型是决定因素 。
因此,当您编译代码时,您使用的变量类型决定了调用哪种方法。
public class A { public void WhoAreYou() { Console.WriteLine("A"); } } public class B : A { public void WhoAreYou() { Console.WriteLine("B"); } } internal class Program { private static void Main(string[] args) { (new B() as A).WhoAreYou(); // "A" (new B()).WhoAreYou(); // "B" Console.ReadLine(); }
请注意,编译器将警告您可能存在的问题,因为将调用的方法因您用于定义类实例的类型而异。
我理解的是,由于未应用覆盖/隐藏,因此派生类的方法在main()中被实例化时被调用。
方法重写:指向子类对象的基类引用变量将调用子类中的重写方法。 “覆盖”关键字用于派生类方法签名。
方法隐藏:指向子类对象的基类引用变量将调用基类中的隐藏方法。 “New”关键字用于派生类方法签名。