在.NET/4.0它主打动态编程这应该是它的一大特性 但既然作为一个.NET技术平台的开发人员那么有必要去了解其运行原理 我不太清楚有没有人写过这方面的东西 但显然这对我而言并不重要
下方通过一个动态字典的底层实现阐述
在blog上写的内容 建议读众应好好读过C#高级编程(入门)、.NET本质论、深入理解.NET、Win32核心编程
上图是一张标准的动态对象扩展 我为它扩展一个Say方法 当然你可以为它扩展属性与字段 这是可行的 但建议只扩展方法与属性
它的用法是完全模拟.NET中dynamic下的ExpandoObject正如标题所提出的一般 主要是剖析其原理 这会涉及到一些中间代码(此处不提)、元数据表达式编程
但在此处并不是着重于中间代码这块 虽然它的涉及面非常广 如果把中间代码进行人工翻译会很麻烦 我也花不起这个时间 好 那就通过ILSpy进行C#反译编
在上图中三行代码所需要真是代码远比看上去多得多 那么换一种中代码这是一种用于阐述其工作原理巧妙方法
private static void Main(string[] args)
{
object exp = new ExpandoObject();
if (MainApplication.o__SiteContainer0.<>p__Site1 == null)
{
MainApplication.o__SiteContainer0.<>p__Site1 = CallSite>.Create(Binder.SetMember(CSharpBinderFlags.None, "User", typeof(MainApplication), new CSharpArgumentInfo[]
{
CSharpArgumentInfo.Create(CSharpArgumentInfoFlags.None, null),
CSharpArgumentInfo.Create(CSharpArgumentInfoFlags.UseCompileTimeType | CSharpArgumentInfoFlags.Constant, null)
}));
}
MainApplication.o__SiteContainer0.<>p__Site1.Target(MainApplication.o__SiteContainer0.<>p__Site1, exp, 10);
}
上述代码是一个向exp中扩展一个名为User属性的真实C#代码 ILSpy反译出可能有一些误差 从上图你可以明确的看到我们虽然写了这么两行代码它底层所需要的实现远远比看上去的更多 比直接反射更花费效率 只是从这个方面看的话
unsafe static void Main(string[] args)
{
dynamic exp = new ExpandoObject();
exp.User = 10;
}
如上述代码所示
CallSite
public class SayCallSiteBinder : CallSiteBinder {
public void Say() {
Console.WriteLine("老司机带带我 我要上天堂");
}
public override Expression Bind(object[] args, ReadOnlyCollection parameters, LabelTarget returnLabel) {
return Expression.Call(Expression.Constant(this), typeof(SayCallSiteBinder).GetMethod("Say"), parameters);
}
}
[STAThread]
unsafe static void Main(string[] args) {
CallSite> site = CallSite>.Create(new SayCallSiteBinder());
site.Target(site);
}
回到正题.NET/4.0中如果你进行了动态编程 dynamic语法这只是一种糖果仅此而已谈不上表达式 而动态编程表达式可真的不是这个糖一样的东西 后续会提到
C#编译会把所有用到dynamic进行代码封装 姑且以拦截阐述会更好些 但值得注意的地方在于即便是你在dynamic语法下调用了类的静态函数 它也还是会被拦截(C#编译器的一种盲目性)
编译器是通过一种静态编译代码的手段即遇到这块类型代码进行特殊处理后在编译 它们非常类似于AOP中静态注入技术 当然这个是编译器本身支持的 怎么说呢C#的语法糖太多 但大多没有多大意义 是好是坏说得清?
ExpandoObject它实现IDynamicMetaObjectProvider接口 故名思意它是“动态元数据对象提供者” 它是干什么的?上面提到编译器会特殊处理dynamic语法的代码 但同时它们会进行一个上下文绑定封装 然后并且调用这个对象实现IDynamicMetaObjectProvider接口的GetMetaObject 传入一个Express的封装 默认情况下被标记为dynamic语法类型的对象可能没有实现IDynamicMetaObjectProvider接口 那么在Bind的处理过程中则以反射的方式进行动态绑定 这个你可能需先参考最上面提供的ILSpy反译编代码
那么是不可能使用原生的元数据对象的 它并没有任何有效的行为 这个时候就需要去实现这个元数据对象 也就是进行一个元数据编程 比如上图代码中的ExpandoMetaObject(扩展元数据对象)
由于是编写一个“ExpandoObject”的动态扩展对象 那么肯定会需要涉及动态的添加获取调用 但需要一个集合进行托管 实际上我这个代码写的并没有微软自家的复杂 当然带来了执行效率会高一些
这里则是通过构造注入的参数 但显然这里需要进行一个上下文绑定 这根据具体的情况来定义 当然个体的建议则是尽可能绑定dynamic语法所访问的变量
这里重写三个函数BindSetMember、BindGetMember、BindInvokeMember分别对应调用方法访问属性、字段的的重写 但三者我是用了一个通用函数进行处理 这是为了提高代码复用率
同时值得注意的是从上述的简单的元数据表达式编程 你可以明确的看到它是通过调用的方式把自身的行为转移给了另一个事件处理源 通过dynamic实现AOP的拦截是毫无问题的(只是性能非常成本高昂)
但是当你使用dynamic语法时 调用一次那么它就会生成一个元数据对象的封装 调用十次它就会生成十次 当然复杂的情况一行代码就可能让它调用很多次 不得不提它会进行动态的lambda express的代码编译(而这个效率很慢 一般我们通常是进行缓存而不是多次编译) 如果你非要使用这个东西那么可以 你先确认这个性能开销是不是可以接受的 否则建议你通过反射达到类似的效果 同时如果你需要实现的动态功能越复杂所需写的元数据表达式就越复杂 是的、元数据表达式很难写 它也很难令人理解
但不论是反射还是元数据动态编程都需要进行优化 否则它们的效率很难会搞起来 出现这种问题那么问责应该找人而不是机器怪技术
在VS中如果需要提供对动态对象的动态视图支持 你应该需要在元数据对象中实现GetDynamicMemberNames那么VS就可以对动态对象提供的可视化的动态视图
下述为动态扩展对象的源代码
namespace STDLOGIC_SERVER.Data.Dynamic
{
using System;
using System.Collections;
using System.Collections.Generic;
using System.Diagnostics;
using System.Dynamic;
using System.Linq.Expressions;
using System.Reflection;
public sealed class ExpandoObject : IDynamicMetaObjectProvider, IEnumerable
{
[DebuggerBrowsable(DebuggerBrowsableState.Never)]
private IDictionary _dict = new Dictionary();
private class ExpandoMetaObject : DynamicMetaObject
{
private static readonly MethodInfo set = typeof(ExpandoMetaObject).GetMethod("Set", BindingFlags.NonPublic |
BindingFlags.Instance);
private static readonly MethodInfo get = typeof(ExpandoMetaObject).GetMethod("Get", BindingFlags.NonPublic |
BindingFlags.Instance);
private static readonly MethodInfo invoke = typeof(ExpandoMetaObject).GetMethod("Invoke", BindingFlags.NonPublic |
BindingFlags.Instance);
private IDictionary dict = null;
public ExpandoMetaObject(ExpandoObject expando, Expression express)
: base(express, BindingRestrictions.Empty, expando)
{
dict = expando._dict;
}
public override DynamicMetaObject BindGetMember(GetMemberBinder binder)
{
return InvokeMember(binder.Name, get);
}
public override DynamicMetaObject BindSetMember(SetMemberBinder binder, DynamicMetaObject value)
{
return InvokeMember(binder.Name, set, value.Value);
}
private DynamicMetaObject InvokeMember(string key, MethodInfo met, params object[] values)
{
object args = null; if (met == invoke) args = values; else if (values != null && values.Length > 0) args = values[0];
return new DynamicMetaObject(Expression.Call(Expression.Constant(this), met,
Expression.Constant(key, typeof(string)),
Expression.Convert(Expression.Constant(args), typeof(object))),
BindingRestrictions.GetTypeRestriction(base.Expression, base.LimitType));
}
public override DynamicMetaObject BindInvokeMember(InvokeMemberBinder binder, DynamicMetaObject[] args)
{
object[] s = new object[args.Length];
for (int i = 0; i < s.Length; i++)
{
s[i] = args[i].Value;
}
return InvokeMember(binder.Name, invoke, s);
}
private object Invoke(string key, object values)
{
object obj = null;
lock (this)
{
if (dict.ContainsKey(key))
{
obj = dict[key];
}
}
Delegate d = obj as Delegate;
if (d == null)
{
throw new MethodAccessException();
}
return d.DynamicInvoke(values as object[]);
}
public override IEnumerable GetDynamicMemberNames()
{
lock (dict)
{
return dict.Keys;
}
}
private object Get(string key, object value)
{
lock (dict)
{
if (!dict.ContainsKey(key))
{
throw new MemberAccessException(key);
}
return dict[key];
}
}
private object Set(string key, object value)
{
lock (dict)
{
if (dict.ContainsKey(key))
{
dict[key] = value;
}
else
{
dict.Add(key, value);
}
}
return null;
}
}
DynamicMetaObject IDynamicMetaObjectProvider.GetMetaObject(Expression express)
{
return new ExpandoMetaObject(this, express);
}
IEnumerator IEnumerable.GetEnumerator()
{
foreach (string key in this)
{
yield return key;
}
}
IEnumerator IEnumerable.GetEnumerator()
{
lock (_dict)
{
var keys = _dict.Keys;
return keys.GetEnumerator();
}
}
}
}