C# - switch 表达式中的动态模式匹配案例?

C# - Dynamic pattern matching cases in switch expression?

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

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"

这一切都很好。

我最近发现了一种情况,我想要的是必须按照“最具体到最不具体”的顺序检查的一系列规则,如果值不存在,则使用合理的默认值。所以实际上与上面的模式匹配序列相同。但是,我需要我的最终用户能够自己配置模式,并且模式案例是动态的(即来自数据库 table)。

所以,给定这些数据记录:

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. switch 表达式语法不是具有 AddPattern() 的对象。
  2. 表达式中的字符“_”和 _ 运算符不是一回事...

我想到的另一个选择是将记录集映射到字典中,其中 Key 是元组(列:Input1、Input2),值是预期的 return 值(列:返回值)。这个问题是,它没有提供任何能力通过简单的键查找将 NULL 数据库值视为丢弃模式。

归根结底,我的问题是: 我假设 switch 表达式语法只是对幕后更复杂实现的一些很好的糖分覆盖。我可以使用 C# 9 中已有的实现来实现“动态”开关表达式的想法吗?还是我找错了树,需要自己完全实现?

我不确定您是否在寻找某种 code-generation,但是通过一些 Linq 聚合,您可以将 patterns/records 放在一个序列中并将结果用作有点像 switch-expression 模式匹配的功能。重要的是 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"

看到了in action.

您必须自己实施。 switch 模式匹配类似于常规使用的 switch case,需要编译时常量,并且很可能用跳转 table 实现。因此不能在运行时修改。

感觉您要实现的目标应该不会太难。像这样

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;

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