我最近发现,在将分支逻辑简化为开关表达式和利用模式匹配方面有很多价值。特别是在我有多个需要考虑的数据元素的情况下。
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.
为什么这不起作用是有道理的,原因有几个,我相信不限于:
- 开关表达式语法不是具有AddPattern((的对象
- 表达式中的字符"_"和运算符"_"不是一回事
我想到的另一个选项是将记录集映射到字典中,其中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?
。