Warning: file_get_contents(/data/phpspider/zhask/data//catemap/2/csharp/271.json): failed to open stream: No such file or directory in /data/phpspider/zhask/libs/function.php on line 167

Warning: Invalid argument supplied for foreach() in /data/phpspider/zhask/libs/tag.function.php on line 1116

Notice: Undefined index: in /data/phpspider/zhask/libs/function.php on line 180

Warning: array_chunk() expects parameter 1 to be array, null given in /data/phpspider/zhask/libs/function.php on line 181
C# 运行时类型与编译时类型方法调用_C#_Language Specifications - Fatal编程技术网

C# 运行时类型与编译时类型方法调用

C# 运行时类型与编译时类型方法调用,c#,language-specifications,C#,Language Specifications,C#4.0规范如下: 调用虚拟方法时,该方法的实例的运行时类型 调用发生的位置决定了实际的方法 要调用的实现。在非虚拟方法调用中 实例的编译时类型是决定因素 起初,我认为这与初始化有关。例如,给定两个初始化: BaseClass bcDerived=new-Derived()vs基类bcBase=新基类() 以及助手类中的重载: public virtual void Method(Derived d) { Console.WriteLine("Result = derived cal

C#4.0规范如下:

调用虚拟方法时,该方法的实例的运行时类型 调用发生的位置决定了实际的方法 要调用的实现。在非虚拟方法调用中 实例的编译时类型是决定因素

起初,我认为这与初始化有关。例如,给定两个初始化:

BaseClass bcDerived=new-Derived()vs
基类bcBase=新基类()

以及助手类中的重载:

public virtual void Method(Derived d)
{
     Console.WriteLine("Result = derived called");
}

public virtual void Method(BaseClass d)
{
     Console.WriteLine("Result = base called");
}
在这种情况下,
方法
调用不受
virtual
关键字的影响。无论是否标记了
virtual
,都会调用派生最少的重载。只有在派生类中的
重写期间,方法调用才会更改


那么,“运行时类型”和“编译时类型”是什么意思呢?它们如何影响方法调用?

在本例中,参数的编译时类型将始终用于确定要调用的重载。虚拟分派取决于调用方法的对象的运行时类型

编译时类型是由编译器确定的对象类型,运行时类型是代码执行时的实际类型。要使用您的示例:

BaseClass bcDerived = new Derived()
编译时类型是
基类
,而运行时类型是
派生的

为了理解其含义,我们需要稍微扩展您的课程:

class BaseClass 
{ 
  public virtual void SomeMethod() 
  {
    Console.WriteLine("In base class");
  }
}

class Derived : BaseClass
{ 
  public override void SomeMethod() 
  {
    Console.WriteLine("In derived class");
  }
}
现在调用
bcDerived.SomeMethod()
将取决于
bcDerived
的运行时类型,以确定是调用
BaseClass
实现还是调用
Derived
实现


Eric Lippert写了一个关于.Net中的虚拟分派的非常好的三部分系列文章(其中),我强烈建议阅读它们以更全面地理解这个主题。

这更多的是虚拟方法与非虚拟方法的问题,以及调用是如何发生的。您引用的规范部分涉及对变量的方法调用-调用
bcDerived.SomeMethod()
,而不是调用
foo.SomeMethod(bcDerived)

您引用的规范是指具有非虚拟方法的情况:

public class A
{
    public void Foo() { Console.WriteLine("A.Foo"); }
    public virtual void Bar() { Console.WriteLine("A.Bar"); }
}
public class B : A
{
    public new void Foo() { Console.WriteLine("B.Foo"); }
    public override void Bar() { Console.WriteLine("B.Bar"); }
}
然后调用的方法将由编译器在编译时确定,这样做:

A someInst = new B();
someInst.Foo();
将导致调用
A.Foo()
,无论
someInst
引用的是A的哪个子类,因为这是一个非虚拟方法

但是,如果您有一个虚拟方法,编译器将指定
callvirt
指令,从而将决策移动到运行时。这意味着:

 someInst.Bar();
将调用
B.Bar()
,而不是
A.Bar()

在您的例子中,您没有调用虚拟方法(在规范所指的意义上),而是执行标准方法解析。C#规范的7.5.3详细介绍了过载解决方案。在您的例子中,编译器将检查参数列表(
bcDerived
),并将其定义为类型
BaseClass
。这方面的“最佳匹配”将是
公共虚拟无效方法(基类d)
,因为参数列表直接匹配参数列表,因此在编译时使用

如果查看规范,方法重载解析不会直接使虚拟方法调用生效—它只查看类型之间的隐式转换

Using these two classes as examples:

public class Parent
{
    public void NonVirtual()
    {
        Console.WriteLine("Nonvirtual - Parent");
    }
    public virtual void Virtual()
    {
        Console.WriteLine("Virtual - Parent");
    }
}

public class Child : Parent
{
    public override void Virtual()
    {
        Console.WriteLine("Virtual - Child");
    }

    public void NonVirtual()
    {
        Console.WriteLine("Nonvirtual - Child");
    }
}
虚拟和非虚拟之间的区别可以通过以下代码清楚地看到:

Parent childAsParent = new Child();
childAsParent.Virtual();
childAsParent.NonVirtual();
这张照片是:

Virtual - Child Nonvirtual - Parent 虚拟儿童 非虚拟父母 在虚拟方法的情况下,它在运行时看到
childaspant
的类型是子对象,因此执行子对象对
virtual
的定义。对于非虚方法,它看到变量的编译时类型是
父类
,忽略了实际实例是
子类
的事实,并使用父类的实现


virtual
不用于根据参数的类型确定使用哪种方法重载。在您的示例中,确定要调用的方法的哪个重载总是在编译时完成(当不使用
动态
时),从不在运行时完成,因此它总是选择
方法
的重载,根据变量的编译时类型。

请澄清
方法
在何处声明以及如何使用。@BrianRasmussen-在OP中,这些方法是帮助器类的成员。谢谢。在这种情况下,我不确定接受的答案与您的问题如何匹配,因为它不包含帮助器类。不过,只要你对答案满意,我就很好。@BrianRasmussen-起初,我以为他在
callvirt
说明中回答了这个问题(因为我正在比较苹果和桔子)。我假设我的示例中缺少关键字。经过更多的思考,你是对的……它并没有直接针对这个例子。
callvirt
指令应该仍然存在于我的示例中,因为我指定了virtual。我知道我通过测试过载做了一件有点奇怪的事情。我的目标是完全理解C#spec定义。@P.Brian.Mackey我刚刚改写了我的答案,以便更清楚,并直接谈论您所指的内容。这里的问题是您引用的规范部分与您的问题不匹配;)它指的是对具有虚拟与非虚拟的对象的方法调用,而不是参数和参数列表的重载解析。