c# - 使用泛型和后期绑定(bind)的反射。如何在运行时转换?

标签 c# generics system.reflection late-binding

我正在尝试在 C# 中使用泛型和反射来构建一个可以处理多个类的方法。我使用具有一堆类的第 3 方 DLL,在这些类上,有一个我调用的方法。它们都返回不同的返回类型,但是一旦我取回对象(在我下面的示例中,这将是 AreaA 和 AreaB),我就会执行相同的处理。

基本上,我想开发一种方法,将类名和预期的返回类型作为通用变量,然后调用作为此方法的参数提供的正确方法 (methodName) .

下面的程序编译正常并且运行没有错误,但问题是“area”变量的预期类型。在下面的语句中,第一行被类型转换为 (TArea),如果我将鼠标悬停在它上面,在 Visual Studio 中,智能感知会显示属性“名称”,但键入 area.name 不会给我值(value)。我必须输入 ((AreaA)area).name

问题是“AreaA”类型在运行时可能是另一种类型。在这个例子中,'AreaB' 这样我就可以对 Actor 进行硬编码。

如何才能将“区域”变量转换为适当的类型,从而允许我查看第 3 方类的公共(public)方法/属性?

注意:在我的示例中,所有内容都在同一个类中,但实际上 ServiceA、ServiceB、AreaA 和 AreaB 的定义将在第 3 方 DLL 中。

一如既往,提前致谢!

图 1 - 如果转换为“AreaA”,“area”变量只能获得“name”属性

area = (TArea)dfpMethod.Invoke(instance, new object[] { "Area123" });
AreaA areaa = (AreaA)dfpMethod.Invoke(instance, new object[] { "Area123" });

图 2. - 完整程序

using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using System.Reflection;
using System.IO;


namespace ReflectionExample
{
    class Sample
    {
        class ServiceA
        {
            public int size {get; set;}
            public string name {get; set;}

            public ServiceA()
            {
                name = "TestA";
                size = 100;
            }
            public AreaA doWork(string name)
            {
                return new AreaA(name);

            }
        }   

        class AreaA
        {
            public string name { get; set;}
            public AreaA(string name)
            {
                this.name = name;
            }
            public AreaA()
            {

            }

         }

        class ServiceB
        {
            public int size { get; set; }
            public string name { get; set; }

            public ServiceB()
            {
                name = "TestB";
                size = 50;
            }
            public AreaB doWork(string name)
            {
                return new AreaB(name);
            }

        }

        class AreaB
        {
            public string name { get; set; }
            public AreaB(string name)
            {
                this.name = name;
            }
            public AreaB()
            {

            }
        }

        static void Main(string[] args)
        {
            runService<ServiceA, AreaA>("doWork");
        }

        private static void runService<TService, TArea>(string methodName)
            where TService : class, new()
            where TArea : class, new()
        {
            //Compile time processing
            Type areaType = typeof(TArea);  
            Type serviceType = typeof(TService);


            //Print the full assembly name and qualified assembly name
            Console.WriteLine("AreaType--Full assembly name:\t   {0}.", areaType.Assembly.FullName.ToString());            // Print the full assembly name.
            Console.WriteLine("AreaType--Qualified assembly name:\t   {0}.", areaType.AssemblyQualifiedName.ToString());   // Print the qualified assembly name.
            Console.WriteLine("ServiceType--Full assembly name:\t   {0}.", serviceType.Assembly.FullName.ToString());            // Print the full assembly name.
            Console.WriteLine("ServiceType--Qualified assembly name:\t   {0}.", serviceType.AssemblyQualifiedName.ToString());   // Print the qualified assembly name.

            //This is done because in my code, the assembly doesn't reside in the executiy assembly, it is only setup as a reference
            var assembly = Assembly.Load(serviceType.Assembly.FullName);        

            //Initialize the generic area
            TArea area = default(TArea);
            //Get an instance of the service so I can invoke the method later on
            var instance = Activator.CreateInstance(serviceType);

            //Get the methodInfo for the methodName supplied to the runService method
            MethodInfo dfpMethod = serviceType.GetMethod(methodName, BindingFlags.Public | BindingFlags.Instance);

            //area is type casted to (TArea), the intellisense shows the property 'name', but typing area.name doesn't give me the value
            //I have to type ((AreaA)area).name.  Problem is the type 'AreaA' could be another type.  In this example, 'AreaB'
            area = (TArea)dfpMethod.Invoke(instance, new object[] { "Area123" });
            AreaA areaa = (AreaA)dfpMethod.Invoke(instance, new object[] { "Area123" });
            Console.WriteLine();

        }

    }
}

最佳答案

您的错误来源是您将所有返回值强制转换为 TArea 类型的语句:

TArea area = (TArea)dfpMethod.Invoke(instance, new object[] { "Area123" });

根据您的通用规范,TArea 类型唯一向您 promise 的是它是一个类。因此,除了“对象”类型的成员之外,TArea 不会让您访问任何内容。

相反,取消 TArea 通用参数以支持使用“动态”关键字:

var area = (dynamic)dfpMethod.Invoke(instance, new object[] { "Area123" });
return area.name; // no error

请注意,只有在第三方库中定义了实际类型 AreaA 和 AreaB(如您所说)并且您无法修改它们时,这才相关。如果你不能修改类,你就不能引入接口(interface)(这是你真正需要的)。如果不能引入接口(interface),但所有类型都公开相同的签名,则可以假定存在使用动态类型的相关成员。

如果您需要使用 AreaA/AreaB 做很多工作并且您不希望所有动态操作的性能开销,请定义您自己的通用类来公开您需要的所有签名:

public class MyGenericArea 
{
    public MyGenericArea(string name)
    {
         this.Name = name;
    }
    public string Name {get; set;}
} 

然后使用动态转换填充类并返回该类类型:

 var area = (dynamic)dfpMethod.Invoke(instance, new object[] { "Area123" });
 return new MyGenericArea(area.name);

关于c# - 使用泛型和后期绑定(bind)的反射。如何在运行时转换?,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/29656878/

相关文章:

c# - 滚动时 Gridview 卡住/固定标题丢失背景颜色

c# - ComboBox.SelectedItem 绑定(bind)

c# - 存储和调用具有多个未知参数的未知方法的委托(delegate)?

c# - 使用 System.Reflection 加载 ASP.NET 2.0 aspx 页面?

c# - 多核处理器上的多线程奇怪的结果

C# 向现有类型添加隐式转换

c# - 为什么编译器不能解析这些泛型类型

c++ - 具有通用/模板化变量的 STL 容器

java - 实例化抽象类的通用扩展

c# - 抛出 NullReferenceException 但对象通过了 null 检查,这怎么可能?