当 dynamic 遇上 internal
2011-06-29 07:53 鹤冲天 阅读(3505) 评论(22) 编辑 收藏 举报dynamic 简介
.Net 4 引入了 dynamic,我们可以简单动态访问对象的属性或调用其方法,免去反射的繁琐和不雅:
1 2 3 |
dynamic person = new Person { ID = 1, Name = "鹤冲天" }; var name = person.Name; person.Work(); |
dynamic 对成员的访问是有限制的,只允许访问公有成员,访问私有或保护成员将会抛出异常:
这与于 .net 其它部分是一致的。
dynamic 不支持其它程序集中的 internal 类型
但 dynamic 对其它程序集中的 internal 类确不支持,不能不说是一个遗憾。
试看如下解决方案,其中有两个项目:
ClassLibrary1 是个类库,有两个类:
1 2 3 4 5 |
internal class Student { public int ID { get; set; } public string Name { get; set; } } |
1 2 3 4 5 6 7 8 9 10 11 |
public class StudentRepository { public dynamic GetByID(int id) { return new Student { ID = id, Name = "鹤冲天" }; } public dynamic Select() { return new { ID = 2, Name = "鹤中天" }; } } |
Student 为 internal 类型。
WhenDynamicMeetInternal 项目引用了 ClassLibrary1,在 Program 中编码如下:
1 2 3 |
var repository = new StudentRepository(); dynamic student = repository.GetByID(1); var name = student.Name; |
调试时,抛出以下异常:
想必大家都想到了,为什么不把 Student 设成 public 呢?
是的,Students 设成 public 后问题轻松解决:
但只解决了部分,却没解决根本,我们来看第二个调用:
1 2 |
dynamic student2 = repository.Select(); var name2 = student2.Name; |
调试时依然有类似异常:
为什么呢?
从上图的 Watch 窗口中我们可以看出匿名类型 student2 的类型不是 public,则其只能是 internal 的(没有 private 的类或结构)。其实匿名类型都是 internal 的,不确定可以自己试下。
我们可以将 Student 设为 public,但对匿名类型,我们却无能为力。
dynamic 拒绝为外部程序集的 internal 类型服务,会带来好多麻烦。下面我们探讨解决办法。
解决办法
使用 InternalsVisibleTo
在 ClassLibrary1 项目的 AssemblyInfo.cs 文件(见上图)末尾中加入一行代码:
1
|
[assembly: InternalsVisibleTo("WhenDynamicMeetInternal")]
|
这种方式缺点多多,不推荐,也就不多解释了。
创建新的动态类型 ReflectionDynamicObject
创建类 ReflectionDynamicObject,继承至 DynamicObject,重写 TryGetMember、TryInvokeMember 等几个方法,代码如下:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 |
using System.Dynamic; using System.Globalization; using System.Reflection; internal sealed class ReflectionDynamicObject : DynamicObject { private object RealObject { get; set; } public override bool TryConvert(ConvertBinder binder, out object result) { result = this.RealObject; return true; } public override bool TryGetMember(GetMemberBinder binder, out object result) { PropertyInfo property = this.RealObject.GetType().GetProperty(binder.Name, BindingFlags.GetProperty | BindingFlags.Public | BindingFlags.Instance); if (property == null) { result = null; } else { result = property.GetValue(this.RealObject, null); result = WrapObjectIfInternal(result); } return true; } public override bool TryInvokeMember(InvokeMemberBinder binder, object[] args, out object result) { result = this.RealObject.GetType().InvokeMember(binder.Name, BindingFlags.InvokeMethod | BindingFlags.NonPublic | BindingFlags.Public | BindingFlags.Instance, null, this.RealObject, args, CultureInfo.InvariantCulture); return true; } public static object WrapObjectIfInternal(object o) { if (o == null) return null; if (o.GetType().IsPublic) return o; return new ReflectionDynamicObject { RealObject = o }; } public override string ToString() { return this.RealObject.ToString(); } } |
有这个类,就可以解决 internal 的问题了:
1 2 |
dynamic student3 = ReflectionDynamicObject.WrapObjectIfInternal(repository.Select()); var name3 = student3.Name; |
运行截图:
关于 ReflectionDynamicObject
ReflectionDynamicObject 是从 System.Web.WebPages.dll 程序集中反编译出来的:
附源码:WhenDynamicMeetInternal.rar (48KB)
-------------------
思想火花,照亮世界