在编写一个 C# 类或者方法时,默认的可见级别是 internale
。这代表着该类型不能被其他程序集轻松访问。
新建一个类库项目 ClassLibrary1
,写入以下代码:
namespace ClassLibrary1; class BaseClass { public BaseClass() { this.Id = 1024; } public int Id { get; set; } }
新建一个控制台项目,引用 ClassLibrary1
后,写入以下代码:
using ClassLibrary1; var myBase = new BaseClass();
最后一行代码会报错 CS0122 :
“BaseClass”不可访问,因为它具有一定的保护级别 。
CS0122
要想在控制台项目里创建 BaseClass 的实例并拿到 Id 的值,可以使用反射:
using ClassLibrary1; var myBase = Activator.CreateInstance("ClassLibrary1", "ClassLibrary1.BaseClass").Unwrap(); var idProperty = myBase.GetType().GetProperty("Id"); int id = (int)idProperty.GetValue(myBase); Console.WriteLine(id);
这段代码有些冗长,可以用 C# 的动态编程进行优化:
using ClassLibrary1; var myBase = Activator.CreateInstance("ClassLibrary1", "ClassLibrary1.BaseClass").Unwrap(); dynamic dyn = myBase; Console.WriteLine(dyn.Id);
该代码在运行时会报错:
‘object’ does not contain a definition for ‘Id’
Microsoft.CSharp.RuntimeBinder.RuntimeBinderException
要解决这个问题,就需要请出今天的主角: DynamicInternal
。
DynamicInternal 的安装与使用
DynamicInternal
是 NewLife.Core 核心库中一个很轻量的类型,用于包装程序集内部类的动态对象。其代码可以在 GitHub 上找到:
https://github.com/NewLifeX/X/blob/master/NewLife.Core/Reflection/DynamicInternal.cs
你可以将代码直接复制到自己的项目中(该代码没有额外的依赖),也可以使用下面的命令添加 NuGet 引用:
dotnet add package NewLife.Core
修改后的代码如下:
using ClassLibrary1; var myBase = Activator.CreateInstance("ClassLibrary1", "ClassLibrary1.BaseClass").Unwrap(); dynamic dyn = NewLife.Reflection.DynamicInternal.Wrap(myBase); Console.WriteLine(dyn.Id);
总结
- 虽然可以通过反射的方式创建和访问非公开的类型,但是在访问非公开类型的成员时需要编写很多额外的代码。
- 使用动态编程可以避免代码冗长,代价是需要使用
DynamicInternal
包装一次。 DynamicInternal
的功能是通过反射实现的,仍然会有使用反射时的性能问题。DynamicInternal
不支持对静态成员的访问。