在用LINQ进行数据查询操作时,我们时不时的会碰到匿名对象,对匿名对象的操作有时候会很不方便比如
var query = from row in table select new { Col1 = row.col1 };
这时只在当前方法内用query的结果是没问题的,但如果要把query传出去用,方法就要用到这么个声明形式
IQueryable<dynamic> GetCol1()
这样方法外才能这样做
query.First().Col1
似乎很不错了。但是实际开发中还会碰到一种情况,就是字段名本身都是动态传入的,这样一来dynamic也无计可施了,碰到这种情况大多数都会选择尝试用反射来读出匹配的数据,对于这种情况如果dynamic类型能支持query.First()["Col1"]这种索引表示方式就方便多了,这里就介绍一种简单有效的实现。
首先是让dynmaic对象支持[]索引器,这个非常简单,只要继承自DynamicObject对象重写掉2个方法就行了,这里我们先暂时只支持一个键的索引:
public class OneKeyDictionaryObject : DynamicObject
{
Dictionary<dynamic, object> table = new Dictionary<dynamic, object>();
public override bool TrySetIndex(SetIndexBinder binder, object[] indexes, object value)
{
if (indexes.Length == 1)
{
table[indexes[0]] = value;
return true;
}
return base.TrySetIndex(binder, indexes, value);
}
public override bool TryGetIndex(GetIndexBinder binder, object[] indexes, out object result)
{
if (indexes.Length == 1)
{
result = table[indexes[0]];
return true;
}
return base.TryGetIndex(binder, indexes, out result);
}
}
dynamic d = new OneKeyDictionaryObject();
d["Col1"] = "test";
var col1 = d["Col1"];
确定可以正常工作。
有了这个我们只要简单的把匿名对象的每个属性放到对应索引里就行了,用反射?不,这里采用了构造一个表达式来把匿名类型转换成我们定义的动态类型。
先写一个简单的lambda:
Expression<Func<dynamic, dynamic>> f = o => {
dynamic d = new OneKeyDictionaryObject();
d["Col1"] = o.Col1;
return d;
};
遗憾的是这个表达式是无法通过编译的,因为C#编译器不支持编译带语句体、赋值运算符和dynamic类型的表达式。不过不要放弃,编译器不支持没关系,Framework支持就行,手工构建一个:
var elementType = o.GetType();
var properties = elementType.GetProperties();
var setBinder = Binder.SetIndex(CSharpBinderFlags.ResultIndexed, typeof(Program),
new[] { CSharpArgumentInfo.Create(CSharpArgumentInfoFlags.None, null),
CSharpArgumentInfo.Create(CSharpArgumentInfoFlags.Constant | CSharpArgumentInfoFlags.UseCompileTimeType, null),
CSharpArgumentInfo.Create(CSharpArgumentInfoFlags.Constant | CSharpArgumentInfoFlags.UseCompileTimeType, null)
});
var inputParameter = Expression.Parameter(typeof(object), "o");
var localVariable = Expression.Variable(typeof(object), "d");
var assign = Expression.Assign(localVariable, Expression.New(typeof(OneKeyDictionaryObject)));
List<Expression> setters = new List<Expression>();
foreach (var property in properties)
{
var name = property.Name;
var indexer = Expression.Constant(name);
var getBinder = Binder.GetMember(CSharpBinderFlags.None, name, typeof(Program),
new[] { CSharpArgumentInfo.Create(CSharpArgumentInfoFlags.None, null),
CSharpArgumentInfo.Create(CSharpArgumentInfoFlags.UseCompileTimeType, null)
});
var getter = Expression.Dynamic(getBinder, typeof(object), inputParameter);
var setter = Expression.MakeDynamic(typeof(Action<CallSite, object, object, object>), setBinder, localVariable, indexer, getter);
setters.Add(setter);
}
var lable = Expression.Label(typeof(object), "ret");
IEnumerable<Expression> body = new[] { assign }.Union(setters)
.Union(
new Expression[] {
Expression.Return(lable, localVariable, typeof(object)),
Expression.Label(lable, localVariable) });
IEnumerable<ParameterExpression> variables = new ParameterExpression[] { localVariable };
var block = Expression.Block(
typeof(object),
variables,
body
);
var lambda = Expression.Lambda<Func<dynamic, dynamic>>(block, inputParameter);
var func = lambda.Compile();
最后的func就是lambda编译后的delegate,这个构建出来的表达式样子大致就是
Expression<Func<dynamic, dynamic>> f = o => {
dynamic d = new OneKeyDictionaryObject();
d["Col1"] = o.Col1;
d["Col2"] = o.Col2;
d["Col3"] = o.Col3;
...
return d;
};
试一下:
var o = new { Col1 = 1, Col2 = 2 };
dynamic d = func(o);
Console.WriteLine("{0}, {1}", d["Col1"], d["Col2“]);
关于如何用表达式表示一个dynamic操作,参考了http://d.hatena.ne.jp/NetSeed/里的内容。