You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

.NET以基类引用对象时为何不使用实际类型?有何解决办法?

问题示例

代码示例

using System;

class Base {}
class SubClass1 : Base{}
class SubClass2 : Base{}

public class Program
{
    public static void Main()
    {
        Test(new Base());
        Test(new SubClass1());
        Test(new SubClass2());
        
        var o = new Base(); Test(o);
        o = new SubClass1(); Test(o);
        o = new SubClass2(); Test(o);
    }
    
    static void Test(Base o) => Console.WriteLine($"Test Base on {o.GetType()}");
    static void Test(SubClass1 o) => Console.WriteLine($"Test SubClass1 on {o.GetType()}");
    static void Test(SubClass2 o) => Console.WriteLine($"Test SubClass2 on {o.GetType()}");
}

运行输出

Test Base on Base
Test SubClass1 on SubClass1
Test SubClass2 on SubClass2
Test Base on Base
Test Base on SubClass1
Test Base on SubClass2


疑问

为什么.NET在第二组调用中不匹配“最优”方法,而是使用变量的引用类型而非对象的实际类型?仅仅是语言设计如此,还是这种设计能避免语言中的诸多问题?

除了显式检查类型(例如使用模式匹配)之外,还有什么解决办法?


解答

为什么会出现这种行为?

这是因为C#的重载解析是在编译时完成的,而非运行时。

第一组调用里,new SubClass1()这类表达式的类型在编译阶段就能明确是SubClass1,编译器会直接匹配对应的Test(SubClass1)重载。

但第二组调用中,变量o的编译时类型是Base——哪怕运行时它实际指向SubClass1或SubClass2实例,编译器编译Test(o)这行代码时,只能基于o的编译时类型(也就是Base)选择重载,所以会直接绑定到Test(Base)方法,运行时不会重新进行重载匹配。

这种设计是有意为之,核心是为了确定性和性能:

  • 确定性:编译阶段就能确定调用哪个方法,开发者能清晰预判代码行为,避免运行时突然切换方法导致的逻辑意外。
  • 性能:编译时绑定方法,运行时无需额外的类型检查和重载匹配逻辑,执行效率更高。如果改成运行时动态匹配,不仅会增加性能开销,还会让代码行为变得不可预测(比如后续新增重载可能改变运行时行为)。

替代解决办法

除了模式匹配,还有以下几种方案:

  • 虚方法/抽象方法:在Base类中定义虚方法,子类重写该方法,调用时会根据对象实际类型动态执行对应实现:
    class Base 
    {
        public virtual void Test() => Console.WriteLine($"Test Base on {GetType()}");
    }
    class SubClass1 : Base
    {
        public override void Test() => Console.WriteLine($"Test SubClass1 on {GetType()}");
    }
    class SubClass2 : Base
    {
        public override void Test() => Console.WriteLine($"Test SubClass2 on {GetType()}");
    }
    
    // 调用示例
    Base o = new SubClass1();
    o.Test(); // 执行SubClass1的Test方法
    
  • 动态类型(dynamic):将变量声明为dynamic,重载解析会延迟到运行时,基于对象实际类型选择方法:
    dynamic o = new Base(); Test(o);
    o = new SubClass1(); Test(o); // 运行时匹配Test(SubClass1)
    o = new SubClass2(); Test(o); // 运行时匹配Test(SubClass2)
    
    注意:dynamic会绕过编译时类型检查,可能引发运行时错误,且存在一定性能开销,需谨慎使用。
  • 访问者模式:如果类型体系稳定,可以用访问者模式实现双分派,让方法调用匹配对象实际类型:
    interface IVisitor
    {
        void Visit(Base obj);
        void Visit(SubClass1 obj);
        void Visit(SubClass2 obj);
    }
    
    class Base
    {
        public virtual void Accept(IVisitor visitor) => visitor.Visit(this);
    }
    class SubClass1 : Base
    {
        public override void Accept(IVisitor visitor) => visitor.Visit(this);
    }
    class SubClass2 : Base
    {
        public override void Accept(IVisitor visitor) => visitor.Visit(this);
    }
    
    class TestVisitor : IVisitor
    {
        public void Visit(Base obj) => Console.WriteLine($"Test Base on {obj.GetType()}");
        public void Visit(SubClass1 obj) => Console.WriteLine($"Test SubClass1 on {obj.GetType()}");
        public void Visit(SubClass2 obj) => Console.WriteLine($"Test SubClass2 on {obj.GetType()}");
    }
    
    // 调用示例
    var visitor = new TestVisitor();
    Base o = new SubClass1();
    o.Accept(visitor); // 执行Visit(SubClass1)
    

内容的提问来源于stack exchange,提问作者Mr. Boy

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.08.05 00:46:03