【问题标题】:Expression tree to create lambda for generating a c# dictionary表达式树创建用于生成 C# 字典的 lambda
【发布时间】:2021-07-01 15:06:06
【问题描述】:

我有数千个数据流,我需要对其进行转换并添加到列表中。转换通过类似于以下的反射发生

_myObservable.Subscribe(d => {
    PropertyInfo[] props = d.GetType().GetProperties(BindingFlags.Instance | BindingFlags.Public);
    var propValDict = props.ToDictionary(prop => prop.Name, prop => prop.GetValue(d, null));
    myList.Add(propValDict);
});

// Datatype of d is determined during runtime and there are only 8 possibilities of the type

但是这种方法会降低性能,我预计反射的使用可能是原因。我正在考虑通过其他方式提高性能。

建议似乎指向表达式树的使用,创建已编译的 lambda (Func<object,Dictionary<string, object>>) 并事先将其存储在查找字典中。

//Foreach possibleType in PossibleTypes, Do below

PropertyInfo[] props = possibleType.GetType().GetProperties(BindingFlags.Instance | BindingFlags.Public);

var rootParam = Expression.Parameter(typeof(object), "d");
var param = Expression.Parameter(typeof(PropertyInfo), "prop");
var propertyFirst = Expression.Property(param, "Name");

var param2 = Expression.Parameter(typeof(PropertyInfo), "prop");
var callMethod = Expression.Call(param2, typeof(PropertyInfo).GetMethod(nameof(PropertyInfo.GetValue), new Type[] { typeof(object) }), rootParam);

var pro = Expression.Parameter(typeof(Array), "props");

var toDict = Expression.Invoke(pro, propertyFirst, callMethod);
var lambda = Expression.Lambda<Func<object, Dictionary<string, object>>>(toDict, rootParam);
var compiled = lambda.Compile();

我无法调用 Enumerable 类的 ToDictionary 这种方法我缺少一些东西,或者这真的会提高性能吗?

请帮忙...

【问题讨论】:

  • 如果你想知道它是否可以帮助提高性能,也许在迭代道具时在 C# 中生成等效的输出作为临时的东西(它不需要很漂亮),看看它是如何执行?
  • “这真的会提高性能吗?”几乎可以肯定不是。您在运行时做了大量工作,最终得到一个与您在第一个示例中在编译时生成的委托完全相同的委托。情况更糟。
  • @Servy 取决于它们处理相同类型实例的频率。 GetType()GetProperties() 很慢。
  • 如果我是对的,至少我可以避免为每个数据调用PropertyInfo[] props = possibleType.GetType().GetProperties(BindingFlags.Instance | BindingFlags.Public);,因为可能只有八个 PropertyInfo[]。但即使我将道具信息存储在字典中,我也必须根据d.GetType() 检索值
  • @canton7 但他们只是在制作一个调用这些方法的表达式。所以无论哪种方式,这种缓慢仍然存在。

标签: c# .net lambda expression-trees


【解决方案1】:

在使用表达式进行思考时,您总是需要弄清楚等效的 C# 代码会是什么样子。在这种情况下,等效的 C# 代码不会循环遍历 PropertyInfo 的集合,而是可能如下所示:

public static Func<object, Dictionary<string, object>> CreateConvertToPropertyDict<T>()
{
    return input =>
    {
        var d = (T)input;
        return new Dictionary<string, object>())
        {
            { "Foo", d.Foo },
            { "Bar", d.Bar },
        };
    };
}
myList.Add(propValDict);

在表达的领域向侧面移动,你最终会得到类似的东西:

public static Func<object, Dictionary<string, object>> CreatePropertyDict(Type type)
{
    // Consider caching these in a static field, since they're constant
    var dictType = typeof(Dictionary<string, object>);
    var dictCtor = dictType.GetConstructor(new[] { typeof(int) });
    var dictAddMethod = dictType.GetMethod("Add");

    var properties = type.GetProperties(BindingFlags.Instance | BindingFlags.Public);
    
    var blockExpressions = new List<Expression>();
    
    // 'object input' is our input parameter
    var inputParameter = Expression.Parameter(typeof(object), "input");
    // MyType d;
    var dVariable = Expression.Variable(type, "d");
    // d = (MyType)inputObject;
    blockExpressions.Add(Expression.Assign(dVariable, Expression.Convert(inputParameter, type)));
    // Dictionary<string, object> dict;
    var dictVariable = Expression.Variable(dictType, "dict");
    // dict = new Dictionary<string, object>(3) (or however many properties there are)
    blockExpressions.Add(Expression.Assign(dictVariable, Expression.New(dictCtor, Expression.Constant(properties.Length))));
    
    foreach (var property in properties)
    {
        var propertyAccess = Expression.Property(dVariable, property);
        // dict.Add("Foo", (object)d.Foo)
        blockExpressions.Add(Expression.Call(
            dictVariable,
            dictAddMethod,
            Expression.Constant(property.Name),
            Expression.Convert(propertyAccess, typeof(object))));
    };
    
    // The final statement in a block is the return value
    blockExpressions.Add(dictVariable);
    
    var block = Expression.Block(new[] { dVariable, dictVariable }, blockExpressions);
    return Expression.Lambda<Func<object, Dictionary<string, object>>>(block, inputParameter).Compile();
}

用简单的测试用例:

public static void Main()
{
    var test = new Test() { Foo = "woop", Bar = 3 };
    var expr = CreatePropertyDict(typeof(Test));
    expr(test).Dump();
}

See it on dotnetfiddle.

这里有几个更高级的 Expression 用法,我不打算详细介绍每一个。查看the docs,尝试一下C# 编译器为不同的C# 代码位生成的各种表达式。

【讨论】:

  • 试试这个看看效果。谢谢!
  • @Azeeb 确保为每个 Type 对象缓存 compiled 表达式——这些表达式的创建和编译成本很高,因此您只需如果你做正确的缓存,就会看到好处
猜你喜欢
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
相关资源
最近更新 更多