在C#中动态创建数组的方法

6

首先,我在 .Net 技术方面没有太多经验,特别是过去 7 年以来。

我正在尝试开发一个应用程序,并希望将另一个库 (https://github.com/Giorgi/Math-Expression-Evaluator) 加入其中。

该库允许我评估数学表达式,例如 Evaluate("a+b", a: 1,b: 1)。方法签名为 public decimal Evaluate(string expression, object argument = null)

  1. 我想更好地了解 .Net 如何将逗号分隔的参数转换为单个 "argument"。
  2. 我不确定如何动态创建该参数..例如,通过遍历值列表并创建与该签名相匹配的对象。

我只是在寻找有关文档和更多信息的指针..谢谢。

编辑:对不起..刻意保持宽泛,因为我不是在寻找别人帮我做工作..只是找不到自己研究的起点。

该方法的调用方式如下:

dynamic engine = new ExpressionEvaluator() ; 
engine.Evaluate("(c+b)*a", a: 6, b: 4.5, c: 2.6)) ; 

在Evalute()函数的主体中,有这段代码(将该参数转换成一个由字符串和十进制数对组成的字典)。
if (argument == null)
        {
            return new Dictionary<string, decimal>();
        }

        var argumentType = argument.GetType();

        var properties = argumentType.GetProperties(BindingFlags.Instance | BindingFlags.Public)
            .Where(p => p.CanRead && IsNumeric(p.PropertyType));

        var arguments = properties.ToDictionary(property => property.Name,
            property => Convert.ToDecimal(property.GetValue(argument, null)));

        return arguments;

我希望能够解析类似于"a:1,b:2"这样的字符串,并将其转换为与Evaluate()函数签名匹配的对象。

2
即使问题1远远太宽泛,包含了许多步骤。更不用说问题2了。“更好地理解”意味着您已经理解了“某些事情”。那么您理解了什么,又有哪些方面您不理解呢? - MakePeaceGreatAgain
2
我甚至不确定问题#1是什么意思。将逗号分隔的参数转换为单个参数? - user47589
2
除非我自己对 C# 中的某些东西有误解,否则我不认为 Evaluate() 的示例用法与该方法签名完全匹配。 - David
2
你提供的链接是源代码链接,为什么不阅读它并找出它是如何工作的呢? - Neil
2
无法确定这个库是邪恶的、天才的还是邪恶的天才... - DavidG
显示剩余9条评论
1个回答

11

那个库使用了高级魔法...非常高级 :-)

关键在于该类声明为:

public class ExpressionEvaluator : DynamicObject

这是一个实现了.NET 4.0中引入的dynamic魔法的类。

现在,在这个类中有两个Evaluate方法:

public decimal Evaluate(string expression, object argument = null)

并且

private decimal Evaluate(string expression, Dictionary<string, decimal> arguments)

通常可见和可用的方法只有第一种。使用方法如下:
engine.Evaluate("a + b + c", new { a = 1, b = 2, c = 3 });
new { ... }会创建一个匿名对象,然后通过反射在这里被“解包”到一个Dictionary<string, decimal>中,以供private Evaluate()函数使用。
如果您尝试使用类似于:
engine.Evaluate("a + b + c", a: 1, b: 2, c: 3 });

然后,如果.NET无法将方法匹配到存在的public Evaluate(),但是由于该类是DynamicObject的子类,因此C#编译器会编写一些“魔法”代码来启动此方法(仍由ExpressionEvaluator实现):
public override bool TryInvokeMember(InvokeMemberBinder binder, object[] args, out object result)

首先检查我们是否需要调用Evaluate函数:

if (nameof(Evaluate) != binder.Name)

如果我们试图调用Evaluate,它会将参数解包到一个new Dictionary<string, decimal>()中,然后调用private Evaluate()

顺便说一句,要使用“动态”编写Evaluate的方法,您必须像这样声明engine变量;

dynamic dynamicEngine = new ExpressionEvaluator();

使用dynamic变量类型。

现在...由于库是这样编写的,你可以:

  • 使用匿名对象,但匿名对象必须在编译时定义它们的“形状”(因此在编译时,你必须知道你将需要一个abc。如果在编译时没有创建new { a, b, c, d },那么你在运行时就不能需要d)。例如,三年前我曾经发表过一篇回答,介绍如何在运行时创建动态匿名类型。我提供该代码块的原因之一是:

    有些.NET框架的部分非常依赖反射来呈现对象(例如所有各种数据网格)。这些部分与动态对象不兼容,并且通常不支持object[]。解决方法通常是将数据封装在DataTable中...或者你可以用这个:-)

    请注意,在该响应的评论中,有一个链接指向我代码的修改版本,该版本被Dynamic.Linq的许多实现之一使用。

  • 使用非匿名对象(new Foo { a = 1, b = 2 c = 3 })。该库不区分匿名和非匿名对象。因此,与之前相同的限制,因为在编译时需要具有正确参数数量的Foo类。

  • 使用dynamic表示法。遗憾的是,即使是这样也相当静态。你不能轻松地添加新的参数,对于“变量”的数量和名称必须在编译时定义。

一种可能的解决方案是修改源代码(它是单个文件),并使这个方法public

private decimal Evaluate(string expression, Dictionary<string, decimal> arguments)

那么您可以轻松动态地填充 Dictionary<string, decimal> arguments


所以我相信(为了使用相同的签名),我必须动态创建其中一个 new { a = 1, b = 2, c = 3 }。不确定如何做到这一点,但这是一个起点。谢谢。 - Greg
@Greg 是的。但请注意变量的数量和名称是固定的。不幸的是,该库没有公开使用Dictionary<>方法的private Evaluate。这将非常容易地实现真正的动态使用。 - xanatos
我同意,并且已经克隆了他的库并将提交一个拉取请求。但是在开始要求额外功能之前,我想确保没有使用原始签名的方法。再次感谢。 - Greg
@Greg,我觉得最简单的方法是将private改为public并使用该方法。因为在运行时创建匿名对象相当复杂。 - xanatos

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接