C#-开关表达式中的动态模式匹配情况



我最近发现,在将分支逻辑简化为开关表达式和利用模式匹配方面有很多价值。特别是在我有多个需要考虑的数据元素的情况下。

return tuple switch
{
("Foo", "Bar") => "FooBar",
("Foo", _ ) => "Foo",
(_, "Bar") => "Bar",
(_, _) => "Default Value"
};

我对任何给定值";元组";变量将产生如下结果:

var tuple = ("Hello", "World") -> "Default Value"
var tuple = ("Foo", "World") -> "Foo"
var tuple = ("Foo", "") -> "Foo"
var tuple = ("Foo", null) -> "Foo"
var tuple = ("Hello", "Bar") -> "Bar"

这一切都很好。

我最近发现了一种情况,我想要的是一系列必须在";最具体到最不具体";订单,如果不存在值,则具有合理的默认值。因此,有效地与上面的模式匹配序列相同。然而,我需要我的最终用户能够自己配置模式,并且模式案例是动态的(即来自数据库表(。

因此,给定这些数据记录:

Input1, Input2, ReturnValue
"Foo",  "Bar",  "FooBar"
"Foo",  NULL,   "Foo"
NULL,   "Bar",  "Bar"
NULL,   NULL,   "Default Value"

我想"生成";这些案例,就像我上面的硬编码示例一样。

("Foo", "Bar") => "FooBar",
("Foo", _ ) => "Foo",
(_, "Bar") => "Bar",
(_, _) => "Default Value"

然后,如果用户想要添加一个新的";规则";,他们会添加一个新的记录

Input1, Input2,  ReturnValue
"Hello","World", "I'm the super special return value".

这将创建以下模式案例:

("Hello", "World") => "I'm the super special return value",

评估时的相应结果为:

var tuple = ("Hello", "World") -> "I'm the super special return value"
var tuple = ("Hello", "Other") -> "Default Value"

在我看来,我想做一些事情来达到以下效果:

var switchOptions = dataRecords.Select(record =>
{
var pattern = (record.Input1 ?? '_', record.Input2 ?? '_');
var func = (pattern) => record.Result;
return func;
});
//and then somehow build the switch expression out of these options.

为什么这不起作用是有道理的,原因有几个,我相信不限于:

  1. 开关表达式语法不是具有AddPattern((的对象
  2. 表达式中的字符"_"和运算符"_"不是一回事

我想到的另一个选项是将记录集映射到字典中,其中Key将是元组(列:Input1、Input2(,值是预期的返回值(列:ReturnValue(。这样做的问题是,它不能通过简单的密钥查找将NULL数据库值视为丢弃模式。

最后,我的问题是:我认为switch表达式语法只是一个更复杂的后台实现的一个很好的补充。是一个";动态地";switch表达式——我可以用C#9中已经存在的实现来完成什么?还是我找错了树,需要自己完全实现这一点?

我不确定你是否在寻找某种代码生成,但通过一点Linq聚合,你可以将你的模式/记录放在一个序列中,并将结果用作一个函数,有点像开关表达式模式匹配。重要的是,dataRecords按照您希望评估的顺序包含记录:

public record Record(string Input1, string Input2, string ReturnValue);
public record Pattern(Func<string, string, bool> Predicate, string ReturnValue);
Pattern CreatePattern(Record rec)
{
return new (
(l, r) =>
(l == rec.Input1 || rec.Input1 == null)
&& (r == rec.Input2 || rec.Input2 == null),
rec.ReturnValue
);
}
// Create your pattern matching "switch-expression"
var switchExp = dataRecords
.Reverse()
.Select(CreatePattern)
.Aggregate<Pattern, Func<string, string, string>>(
(_, _) => null,
(next, pattern) =>
(l, r) => pattern.Predicate(l, r) ? pattern.ReturnValue : next(l, r)
);
switchExp("abc", "bar"); // "bar"

看看它在行动中。

您必须自己实现这一点。开关模式匹配类似于常规使用中的开关情况,需要编译时常数,并且很可能是用跳转表实现的。因此,无法在运行时对其进行修改。

你想要达到的目标感觉不应该太难。像这样的

PatternDict = new Dictionary<string, Dictionary<string, string>>();
PatternDict["_"] = new Dictionary<string, string>();
PatternDict["_"]["_"] = null;

更新代码为:

Dictionary<string, string> dict;
if (!PatternDict.TryGetValue(input1, out dict)) {
dict = new Dictionary<string, string>();
dict["_"] = "default";
}
dict[input2] = returnValue;
PatternDict[input1] = dict;

检索代码为:

Dictionary<string, string> dict;
if (!PatternDict.TryGetValue(input1, out dict)) {
dict = PatternDict["_"];
}
string returnVal;
if (!dict.TryGetValue(input2, out returnVal)) {
returnVal = dict["_"];
}
return returnVal;

如果您使用的是支持将null用作默认值键的C#的新版本,那么您还可以将string更改为可为null的字符串string?

最新更新