C# 类型转换(向上转型,虚函数)

本文介绍了C#中的类型转换,重点讲解了向上转型的内存模型和合理性,强调了向下转型的错误以及条件。讨论了虚函数的概念,展示了如何实现多态,并解释了虚函数表的工作原理。最后,提到了as和is操作符在继承链中的应用。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

1, C# 构造函数

  • 当重写构造函数,则默认无参构造函数会失效,如下代码:
    class Program
    {
        private static void Main(string[] args)
        {
            A a = new A();//会报错!!!
        }
    }

    class A
    {
        public A(string name)
        {
            _name= name;
        }
        
        private string _name;
    }

  • 如需要无参的构造函数存在,需重载一个无参数构造函数,如下代码:
    class Program
    {
        private static void Main(string[] args)
        {
            A a1 = new A();
            A a2 = new A("hello");
        }
    }

    class A
    {
        public A()
        {

        }

        public A(string name)
        {
            _name= name;
        }

        private string _name;
    }

  • A1继承中构造过程,如下代码:
    1,将name通过base传给基类A的构造函数,进行基类A创建
    2,调用A1的构造函数,进行派生类A1创建

当不写base时,派生类会找基类的无参构造函数,找不到会报错

    class A
    {
        public A(string name)
        {
            _name = name;
        }

        private string _name;
    }

    class A1 : A
    {
        public A1(string name, int score)
            : base(name)
        {
            _score = score;
        }

        private int _score;
    }

2, C# 类型转换

  • 从内存模型角度,派生类引用转换成基类引用是合理的,因为派生类由基类成员和自己的成员组成,在内存中是可以指向其内存中的基类成员信息(向上转型),如下代码:
           static void Main(string[] args)
        {
            
            A a = new A("A");//a指向内存中的(实例a)

            A1 a1 = new A1("A1",12);//a1指向内存中的(实例a1)

            a = a1;//a放弃指向(实例a),转而指向(实例a1)中的基类部分

            a.Afunc();//a可以指向(实例a1)中的基类部分中的成员函数

            Console.WriteLine(a._name); //a可以指向(实例a1)中的基类部分中的成员变量

            Console.ReadKey();
        }
    }
    class A
    {
        public A(string name)
        {
            _name = name;
        }

        public void Afunc()
        { 

        }

        public string _name;
    }
    class A1 : A
    {
        public A1(string name, int score)
            : base(name)
        {
            _score = score;
        }

        public void A1func()
        {

        }

        public int _score;
    }
  1. a = a1之后,a可以指向的信息如下图:
    在这里插入图片描述
  2. 代码运行结果如下:Afunc没写内容所以内输出,a._name得到的(实例a1)中的_name值A1,如下图:
    在这里插入图片描述

  • 但是反过来,基类引用转换成派生类引用不合理,因为基类内存是无法额外产生组成派生类成员内存,这是错误的。(向下转型),如下图:
  1. (编译期报错)
    在这里插入图片描述
  2. (运行期报错)
    在这里插入图片描述

上面代码中,原本指向(实例a1)a1引用现在需要指向(实例a),但是a1引用A1派生类型,它是需指向派生类的成员比如:_scoreA1func,但是现在指向的(实例a)中无法额外生成派生类的成员,所以转型错误

  • 针对上一点,如果这个基类引用本身就是派生类转换过来的,它是可以继续回头再指向原来的派生类信息,如下代码:
    在这里插入图片描述

3, 虚函数

示例代码:

       class Program
    {
        private static void Main(string[] args)
        {
            A a = new A();
            a.Show();

            a = new A1();
            a.Show();

            a = new A2();
            a.Show();

            Console.ReadKey();
        }
    }

    class A
    {
        public virtual void Show()
        {
            Console.WriteLine("this is showfunc of classA");
        }

        public int _a;
    }

    class A1 : A
    {
        public override void Show()
        {
            Console.WriteLine("this is showfunc of classA1");
        }
        public int _a1;
    }

    class A2 : A
    {
        public override void Show()
        {
            Console.WriteLine("this is showfunc of classA2");
        }

        public int _a2;
    }
}

运行结果如下 :
在这里插入图片描述
如上代码基类引用可以指向其派生类的成员函数了,形成多态。

不知道c#的虚函数多态机制是如何实现的,C++是利用虚函数表,所以下面就以c++的虚函数表机制解释下多态的形成

按照上文,我们知道基类的引用是无法找派生类的成员的,这里之所以可以找到派生类成员,是因为虚函数表的存在

示例代码如下:

class Program
    {
        private static void Main(string[] args)
        {
            A a = new A();
            a.Show();
            a.Call();

            a = new A1();
            a.Show();
            a.Call();

            a = new A2();
            a.Show();
            a.Call();

            Console.ReadKey();
        }
    }

    class A
    {
        public virtual void Call()
        {
            Console.WriteLine("this is Callfunc of classA");
        }

        public virtual void Show()
        {
            Console.WriteLine("this is showfunc of classA");
        }

        public int _a;
    }

    class A1 : A
    {
        public override void Show()
        {
            Console.WriteLine("this is showfunc of classA1");
        }

        public int _a1;
    }

    class A2 : A
    {
        public override void Call()
        {
            Console.WriteLine("this is Callfunc of classA2");
        }

        public override void Show()
        {
            Console.WriteLine("this is showfunc of classA2");
        }

        public int _a2;
    }

运行结果如下:
在这里插入图片描述

  1. 当基类中存有虚函数时,在基类和派生类中对应实例内存中会多一个隐藏信息:虚函数表指针它指向虚函数表
  2. 虚函数表其实就存放虚函数地址的数组
  3. 当调用虚函数时,首先去虚函数表地址里找到对应的虚函数列表,在虚函数表里再找到需要执行的虚函数

上述代码对应的内存模型概览如下:
在这里插入图片描述

a = new A1();
a.Show();
a.Call();

a.Show();调用过程如下:
1,获悉A1实例内存中虚函数地址2096
2,前往2096处的表
3,获悉表中第2个函数的地址6820
4,前往地址6820,并执行这里的函数(传入this指针)

a.Call();调用过程如下:
1,获悉A1实例内存中虚函数地址2096
2,前往2096处的表
3,获悉表中第1个函数的地址4064
4,前往地址4064,并执行这里的函数(传入this指针)

不过虚函数表示面对程序员的隐藏的,代码书写只需定义虚函数行为,编译器去实现


4, as 和 is

  • is
    在这里插入图片描述
  • as
    如下代码:test1转换成指向(a2实例)中基类部分的引用,test2编译器报错,as 和 is 用于继承链中的转换和判断
    在这里插入图片描述
### C# 中隐藏元素、方法或功能的实现 在 C# 编程语言中,可以通过 `new` 关键字来隐藏基类中的成员(包括方法、属性或其他成员)。这种机制允许派生类定义与其基类具有相同名称的成员,从而覆盖基类成员的行为。需要注意的是,这种方式不同于重写(override),它不会改变继承行为,而是简单地屏蔽了基类成员。 #### 使用 `new` 关键字隐藏基类方法 当在一个派生类中声明了一个与基类同名的方法时,可以使用 `new` 关键字显式表明该方法用于隐藏基类中的对应方法。如果未使用 `new` 关键字,则编译器会发出警告提示可能存在的意外行为[^1]。 以下是通过 `new` 隐藏基类方法的一个具体例子: ```csharp class MyBaseClass { public virtual void MyMethod() { Console.WriteLine("这是MyBaseClass的MyMethod"); } } class MyDerivedClass : MyBaseClass { public new void MyMethod() { // 使用 'new' 来隐藏基类方法 Console.WriteLine("这里是MyDerivedClass的MyMethod"); } } ``` 在这个案例中,`MyDerivedClass.MyMethod()` 完全替换了来自 `MyBaseClass` 的版本。然而,这并不意味着原始方法被删除;只是它的可见性受到限制,在特定上下文中无法直接访问[^1]。 #### 显示调用被隐藏的方法 尽管派生类中的新方法掩盖了基类中的旧方法,仍然有可能通过强制转换回基类类型的方式重新获取到原版函数的功能: ```csharp static void Main(string[] args) { MyDerivedClass derived = new MyDerivedClass(); derived.MyMethod(); // 输出: 这里是MyDerivedClass的MyMethod MyBaseClass baseRef = (MyBaseClass)derived; baseRef.MyMethod(); // 输出: 这是MyBaseClass的MyMethod } ``` 这里展示了即使子类已经定义了自己的 `MyMethod`, 我们依然能够借助向上转型恢复对父类版本的调用[^3]。 #### 注意事项 - **区别于 Override**: 覆盖(`override`)修改现有虚方法的具体实现, 并且遵循多态原则自动选择最合适的执行路径; 而隐藏(`new`)则仅仅是在当前作用域内遮蔽掉原有定义. - **运行期绑定 vs 编译期绑定**: 当涉及到虚拟方法(virtual method),最终决定哪个实例会被调用取决于对象的实际类型而非引用变量所属类别. 对非virtual methods来说则是依据静态类型判断. --- ### 总结 为了有效地控制程序逻辑并减少潜在错误风险,在设计阶段就应该仔细考虑是否真的有必要去替换某个已有的组件或者特性。通常情况下推荐优先采用 override 替代 new ,除非确实有特殊需求才选用后者[^2].
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值