使用泛型和后期绑定的反射。如何在 运行 时间投射?
Reflection using generics and late-binding. How to cast at run-time?
我正在尝试在 C# 中使用带有反射的泛型来构建一个可以处理多个 classes 的方法。我使用具有一堆 classes 的第 3 方 DLL,在那些 classes 上,有一个我调用的方法。它们都是 return 不同的 return 类型,但是一旦我取回对象,我就会进行相同的处理(在我下面的示例中,这将是 AreaA 和 AreaB) .
基本上,我想开发一种方法,将 class 名称和预期的 return 类型作为通用变量,然后调用正确的方法 (methodName) 作为此方法的参数提供。
下面的程序编译得很好并且 运行s 没有错误,但问题是 'area' 变量的预期类型。在下面的语句中,第一行被类型转换为 (TArea),如果我将鼠标悬停在它上面 In Visual Studio,智能感知显示 属性 'name',但键入 area.name 没有给我值。我必须输入 ((AreaA)area).name。
问题是类型 'AreaA' 在 运行 时可能是另一种类型。在这个例子中,'AreaB' 所以我可以硬编码一个转换。
我怎样才能将 'area' 变量转换为适当的类型,从而允许我看到第 3 方 class 的 public methods/properties?
注意:在我的示例中,所有内容都在同一个 class 中,但实际上 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();
}
}
}
我认为您在这里遇到的问题是将动态加载的类型 (Assembly.Load()) 与直接从您的项目引用的类型混合在一起,您可以在智能感知中看到,即 AreaA。
如果您使用反射动态加载整个程序集,intellisense 将不会帮助您查看 class 成员,因为该信息需要在编译时知道,并且根据定义,您是在加载程序集运行时间。
如果您只想查看您的类型可用的所有 public 属性的列表,那么您可以使用此:
var areaProperties = area.GetType().GetProperties();
但同样,这都是在 运行 时间完成的,因此在编写代码时对您没有帮助。
您可以使用以下方法动态读取 "name" 属性 的值:
var nameValue = area.GetType().GetProperty("name").GetValue(area);
本质上,如果您需要智能感知,请直接从您的 Visual Studio 项目中引用 dll,而不是使用 Assembly.Load()。
希望对您有所帮助。
您的错误来源是您将所有 return 值强制转换为类型 TArea 并使用以下语句:
TArea area = (TArea)dfpMethod.Invoke(instance, new object[] { "Area123" });
根据您的通用规范,TArea 类型唯一向您承诺的是它是一个 class。因此,除了 'object' 类型的成员之外,TArea 不允许您访问任何内容。
相反,取消 TArea 通用参数,转而使用 'dynamic' 关键字:
var area = (dynamic)dfpMethod.Invoke(instance, new object[] { "Area123" });
return area.name; // no error
请注意,只有在第三方库中定义了实际类型 AreaA 和 AreaB(如您所说)并且您无法修改它们时,这才相关。如果你不能修改 classes,你就不能引入接口(这是你真正需要的)。如果不能引入接口,但所有类型都公开相同的签名,则可以使用动态类型假设相关成员的存在。
如果您需要使用 AreaA/AreaB 做很多工作并且您不希望所有动态操作的性能开销,请定义您自己的通用 class 公开您的所有签名需要:
public class MyGenericArea
{
public MyGenericArea(string name)
{
this.Name = name;
}
public string Name {get; set;}
}
然后使用 class 类型的动态转换和 return 填充 class:
var area = (dynamic)dfpMethod.Invoke(instance, new object[] { "Area123" });
return new MyGenericArea(area.name);
我正在尝试在 C# 中使用带有反射的泛型来构建一个可以处理多个 classes 的方法。我使用具有一堆 classes 的第 3 方 DLL,在那些 classes 上,有一个我调用的方法。它们都是 return 不同的 return 类型,但是一旦我取回对象,我就会进行相同的处理(在我下面的示例中,这将是 AreaA 和 AreaB) .
基本上,我想开发一种方法,将 class 名称和预期的 return 类型作为通用变量,然后调用正确的方法 (methodName) 作为此方法的参数提供。
下面的程序编译得很好并且 运行s 没有错误,但问题是 'area' 变量的预期类型。在下面的语句中,第一行被类型转换为 (TArea),如果我将鼠标悬停在它上面 In Visual Studio,智能感知显示 属性 'name',但键入 area.name 没有给我值。我必须输入 ((AreaA)area).name。
问题是类型 'AreaA' 在 运行 时可能是另一种类型。在这个例子中,'AreaB' 所以我可以硬编码一个转换。
我怎样才能将 'area' 变量转换为适当的类型,从而允许我看到第 3 方 class 的 public methods/properties?
注意:在我的示例中,所有内容都在同一个 class 中,但实际上 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();
}
}
}
我认为您在这里遇到的问题是将动态加载的类型 (Assembly.Load()) 与直接从您的项目引用的类型混合在一起,您可以在智能感知中看到,即 AreaA。
如果您使用反射动态加载整个程序集,intellisense 将不会帮助您查看 class 成员,因为该信息需要在编译时知道,并且根据定义,您是在加载程序集运行时间。
如果您只想查看您的类型可用的所有 public 属性的列表,那么您可以使用此:
var areaProperties = area.GetType().GetProperties();
但同样,这都是在 运行 时间完成的,因此在编写代码时对您没有帮助。
您可以使用以下方法动态读取 "name" 属性 的值:
var nameValue = area.GetType().GetProperty("name").GetValue(area);
本质上,如果您需要智能感知,请直接从您的 Visual Studio 项目中引用 dll,而不是使用 Assembly.Load()。 希望对您有所帮助。
您的错误来源是您将所有 return 值强制转换为类型 TArea 并使用以下语句:
TArea area = (TArea)dfpMethod.Invoke(instance, new object[] { "Area123" });
根据您的通用规范,TArea 类型唯一向您承诺的是它是一个 class。因此,除了 'object' 类型的成员之外,TArea 不允许您访问任何内容。
相反,取消 TArea 通用参数,转而使用 'dynamic' 关键字:
var area = (dynamic)dfpMethod.Invoke(instance, new object[] { "Area123" });
return area.name; // no error
请注意,只有在第三方库中定义了实际类型 AreaA 和 AreaB(如您所说)并且您无法修改它们时,这才相关。如果你不能修改 classes,你就不能引入接口(这是你真正需要的)。如果不能引入接口,但所有类型都公开相同的签名,则可以使用动态类型假设相关成员的存在。
如果您需要使用 AreaA/AreaB 做很多工作并且您不希望所有动态操作的性能开销,请定义您自己的通用 class 公开您的所有签名需要:
public class MyGenericArea
{
public MyGenericArea(string name)
{
this.Name = name;
}
public string Name {get; set;}
}
然后使用 class 类型的动态转换和 return 填充 class:
var area = (dynamic)dfpMethod.Invoke(instance, new object[] { "Area123" });
return new MyGenericArea(area.name);