在泛型 class 中使用 Activator.CreateInstance 并结合方法上的 "new" 修饰符

Using Activator.CreateInstance in generic class combined with "new" modifier on method

我有一个继承自 BaseClass 的 class (DerivedClass)。 在 DerivedClass 中,我在方法 (SayHello()) 上使用 "new" 修饰符,因为我想更改签名 - 我想添加一个 return 值。
我也有一个通用的 class。提供给泛型 class 的类型应该是 "BaseClass" 类型(在我的例子中是 BaseClass 或 DerivedClass)。

如果我使用 Activator.CreateInstance() 获取我的泛型类型的新实例,然后调用我的方法,则始终会调用 BaseClass 上的方法。为什么在将 DerivedClass 设置为泛型类型时不调用 SayHello 方法?

我做了一个简单的控制台应用程序来说明:

namespace TestApp
{
    using System;

    class Program
    {
        static void Main(string[] args)
        {
            var gc = new GenericClass<DerivedClass>();
            gc.Run();
        }
    }

    public class BaseClass
    {
        public void SayHello()
        {
            Console.WriteLine("Hello!");
        }
    }

    public class DerivedClass : BaseClass
    {
        new public int SayHello()
        {
            Console.WriteLine("Hello returning int!");
            return 1;
        }
    }

    public class GenericClass<T> where T : BaseClass
    {
        public void Run()
        {
            var bc = new BaseClass();
            bc.SayHello(); // Hello!

            var dc = new DerivedClass();
            dc.SayHello(); // Hello returning int!

            var dc2 = Activator.CreateInstance<T>();
            dc2.SayHello(); // Hello!
            Console.WriteLine(dc2.GetType()); // TestApp.DerivedClass

            Console.Read();
        }
    }
}

因为组合:

public class GenericClass<T> where T : BaseClass

并且:

new public int SayHello()

告诉编译器,在编译时,T 将是 BaseClass 类型,并且方法重载匹配发生在编译时,而不是在 运行 时。因此,您的 运行 时间类型不同这一事实实际上并没有在这里发挥作用,因为它是 "ran over" 使用 new 修饰符,而不是通过重写虚拟方法调度,就像你的两个方法调用的 return 类型相同 (void).

你在生成的IL中看到了:

GenericClass`1.Run:

IL_001B:  call        01 00 00 2B 
IL_0020:  stloc.2     // dc2
IL_0021:  ldloca.s    02 // dc2
IL_0023:  constrained. 02 00 00 1B 
IL_0029:  callvirt    UserQuery+BaseClass.SayHello

因为您没有重写该方法,所以您正在隐藏它。如果您使该方法成为虚拟方法,并改写它,那么您会得到您期望的结果。

GenericClassRun方法不知道对象的运行时类型,它只知道它是从BaseClass派生的类型,所以它只能绑定编译时 BaseClass 中方法的实现。由于您尚未通过制作方法 virtual 启用虚拟分派,因此它无法了解派生类型的方法。

您可以使用动态关键字:

 dynamic dc2 = Activator.CreateInstance<T>();