C# 构造函数如何调用虚方法

 更新时间:2020年07月03日 09:50:21   作者:NiKaFace  
这篇文章主要介绍了C# 构造函数如何调用虚方法,文中讲解非常详细,示例代码帮助大家更好的理解和学习,感兴趣的朋友可以了解下

谜题

在C#中,用virtual关键字修饰的方法(属性、事件)称为虚方法(属性、事件),表示该方法可以由派生类重写(override)。虚方法是.NET中的重要概念,可以说在某种程度上,虚方法使得多态成为可能。

然而虚方法的使用却存在着很大学问,如果滥用的话势必对程序产生很大的负面影响。比如下面这个例子:

public class Puzzle
{
  public Puzzle()
  {
    Name = "Virtual member call in constructor";
    Solve();
  }

  public virtual string Name { get; set; }

  public virtual void Solve()
  {
  }
}

如果您的Visual Studio没有安装ReSharper,那么上面的代码不会有任何异常。但如果安装了,在构造函数内部给Name赋值和调用Solve时就会在下面产生一个波浪线,即警告:virtual member call in constructor。

这是什么原因呢?我们在构造函数中调用虚方法,碍着ReSharper什么事儿了?

其实这个警告就是提醒我们不要在非封闭类型的构造函数内调用虚方法或虚属性。但为什么这样做不合适呢?在解惑之前,我们先来了解两个概念。

类型的初始化顺序

我们先来看这样一段代码:

class Base
{
  public Base()
  {
    Console.WriteLine("Base constructor");
  }
}
class Derived : Base
{
  public Derived()
  {
    Console.WriteLine("Derived constructor");
  }
}
static class Program
{
  static void Main()
  {
    new Derived();
    Console.Read();
  }
}

猜一猜它的输出结果是什么?

你也许已经猜到了,它的结果是:

Base constructor
Derived constructor

我们在初始化一个对象时,总是会先执行基类的构造函数,然后再执行子类的构造函数。

虚方法调用

我们再来看一段代码:

class Base
{
  public void M()
  {
    Console.WriteLine("Base.M");
  }

  public virtual void V()
  {
    Console.WriteLine("Base.V");
  }
}
class Derived : Base
{
  public new void M()
  {
    Console.WriteLine("Derived.M");
  }

  public override void V()
  {
    Console.WriteLine("Derived.V");
  }
}
static class Program
{
  static void Main()
  {
    var d = new Derived();
    Base b = d;
    b.M();
    b.V();
    d.M();
    d.V();
    Console.Read();
  }
}

再来猜一猜输出结果吧。

貌似应该是:

Base.M
Base.V
Derived.M
Derived.V

但运行一下会发现,真正的结果是这样的:

Base.M
Derived.V
Derived.M
Derived.V

这是为什么呢?

原来对于非虚方法调用,编译器会进行一些额外的“动作”。比如找出所调用对象的实际类型,以访问正确的方法表(调用b.V()的时候就会找到变量b的实际类型Derived,从而输出Derived.V)。

解惑

现在回到我们最初的谜题,virtual member call in constructor。结合以上两个知识点,会有哪些发现?

我们稍微改造一下虚方法调用的那个例子。

class Foo
{
  public Foo(string s)
  {
    Console.WriteLine(s);
  }
  public void Bar() { }
}

class Base
{
  public Base()
  {
    V(); // Virtual member call in constructor
  }
  public virtual void V()
  {
    Console.WriteLine("Base.V");
  }
}
class Derived : Base
{
  private Foo foo;
  public Derived()
  {
    foo = new Foo("foo in Derived");
  }

  public override void V()
  {
    Console.WriteLine("Derived.V");
    foo.Bar(); // will throw NullReferenceException
  }
}

在Base的构造函数中调用虚方法V()时,ReSharper会给出virtual member call in constructor的警告。这是因为V可以在Base的任意子类中被改写(override),而这种改写,很有可能使得它依赖于自己的构造函数,如上例所示。而由于之前提到的类型初始化顺序,在执行Base b = new Derived();这样的代码时,Base的构造函数要早于Derived的构造函数执行,因此在执行到foo.Bar()时foo还是个空引用。

明白了吗?我们来简单总结一下。Virtual member call in constructor的警告是因为,对于Base b = new Derived();这样的代码:

  1. 基类构造函数的执行要早于子类构造函数
  2. 基类构造函数中对于虚方法的调用,实际调用的是子类中重写的虚方法

因此,ReSharper会警告我们,这么做存在隐患。

我们能完全避免这么做吗?很遗憾,答案是不能。比如如果项目中使用了NHibernate,框架本身要求ORM实体类中,所有与数据库列具有对应关系的属性都必须为虚属性。这是因为NHibernate为了实现延迟加载,会为每个实体类生成proxy,这些proxy需要重写实体类中属性的getter/setter。而有些时候,为了业务需要,我们不得不在实体类的构造函数中对这些属性进行某些操作(比如初始化)。

我认为这么做是技术选型所致的必然结果,是完全可以接受的。但我们要注意,在代码中保证那些可能会被继承的实体,在子类中重写那些虚属性时,不要依赖于子类自身的构造函数(这几乎是可以保证的,因为与数据库列映射的属性,只能是最简单的getter/setter)。

以上就是C# 构造函数如何调用虚方法的详细内容,更多关于C# 构造函数内调用虚方法的资料请关注脚本之家其它相关文章!

相关文章

  • C#/VB.NET实现HTML转为XML的示例代码

    C#/VB.NET实现HTML转为XML的示例代码

    可扩展标记语言(XML)文件是一种标准的文本文件,它使用特定的标记来描述文档的结构以及其他特性。本文将利用C#实现HTML转为XML,需要的可以参考一下
    2022-06-06
  • C#串口接收程序的实现

    C#串口接收程序的实现

    本文主要介绍了C#串口接收程序的实现,文中通过示例代码介绍的非常详细,对大家的学习或者工作具有一定的参考学习价值,需要的朋友们下面随着小编来一起学习学习吧
    2022-05-05
  • C#使用SqlDataAdapter对象获取数据的方法

    C#使用SqlDataAdapter对象获取数据的方法

    这篇文章主要介绍了C#使用SqlDataAdapter对象获取数据的方法,结合实例形式较为详细的分析了SqlDataAdapter对象获取数据具体步骤与相关使用技巧,需要的朋友可以参考下
    2016-02-02
  • C#实现汉字转换为拼音缩写的代码

    C#实现汉字转换为拼音缩写的代码

    这篇文章主要为大家详细介绍了C#实现汉字转换为拼音缩写的代码,感兴趣的小伙伴们可以参考一下
    2016-07-07
  • Unity创建平铺网格地图的方法

    Unity创建平铺网格地图的方法

    这篇文章主要为大家详细介绍了Unity创建平铺网格地图的方法,文中示例代码介绍的非常详细,具有一定的参考价值,感兴趣的小伙伴们可以参考一下
    2022-07-07
  • C# 制作PictureBox圆形头像框并从数据库中读取头像

    C# 制作PictureBox圆形头像框并从数据库中读取头像

    C#提供的PictureBox控键默认情况下是方形的非常大的影响美观,怎么解决这一问题呢?下面小编给大家带来了C# 制作PictureBox圆形头像框并从数据库中读取头像的操作代码,感兴趣的朋友一起学习下吧
    2021-08-08
  • C#实现将PDF转为Excel的方法详解

    C#实现将PDF转为Excel的方法详解

    通常,PDF格式的文档能支持的编辑功能不如office文档多,针对PDF文档里面有表格数据的,如果想要编辑表格里面的数据,可以将该PDF文档转为Excel格式。本文将介绍如何利用C#实现PDF转Excel,需要的可以参考一下
    2022-04-04
  • 如何获取C#中方法的执行时间以及其代码注入详解

    如何获取C#中方法的执行时间以及其代码注入详解

    这篇文章主要给大家介绍了关于如何获取C#中方法的执行时间以及其代码注入的相关资料,文中通过示例代码介绍的非常详细,对大家的学习或者工作具有一定的参考学习价值,需要的朋友们下面来一起看看吧
    2018-11-11
  • C#实现并查集的使用示例

    C#实现并查集的使用示例

    并查集是一种用于处理一些不相交集合的合并及查询问题的数据结构,具有高效、简洁、易用的特点,本文主要介绍了C#实现并查集的使用示例,感兴趣的可以了解一下
    2023-11-11
  • C#中string.format用法详解

    C#中string.format用法详解

    这篇文章主要介绍了C#中string.format用法,以实例形式较为详细的讲述了string.format格式化的各种用法,非常具有实用价值,需要的朋友可以参考下
    2014-11-11

最新评论