C#递归操作字典报无法从void转换为Dictionary<string,bool>错误
报错原因
这个类型转换错误的核心原因非常明确:
C# 中 Dictionary<TKey, TValue>.Add() 方法的返回值是 void(无返回值),但你在递归调用 TT_Check_All 时,第四个参数明确要求传入 Dictionary<string, bool> 类型的对象。你直接把 model.Add(p,true) 作为参数传入,相当于把无返回值的方法执行结果当作字典对象传递,自然会触发 Cannot convert from 'void' to 'System.Collections.Generic.Dictionary<string, bool>' 的编译错误。
除了这个直接触发报错的问题,你的代码里还有多个会导致运行结果完全错误的逻辑问题:
List<T>.Concat()是LINQ扩展方法,不会修改原列表,只会返回拼接后的新列表,你写的result.Concat(ExtractSymbol(s))、symbols.Concat(symbol1)这类写法根本没有把元素加入目标列表PL_True方法中重复写了两次AND分支判断,OR分支的遍历逻辑错误(遍历第一个子节点就直接返回结果,没有校验所有子节点)IF(蕴含连接词)的判断逻辑写反,蕴含规则A=>B等价于!A || B,原判断条件完全不符合真值定义- 递归枚举符号时直接修改传入的原
symbol列表(调用RemoveAt(0)),会导致两个递归分支拿到的符号列表不一致 - 字典是引用类型,如果直接修改传入的原
model字典,两个递归分支的键值对会互相污染,不符合真值表枚举要求分支完全独立的逻辑 - 递归终止条件逻辑错误:当当前model下KB为假时,该model不属于KB成立的场景,应该直接返回
true(不存在反例),原代码返回false会导致判断结果完全错误
修复方案
- 解决核心编译错误:每个递归分支创建独立的字典副本,在副本上添加键值对后,再把新字典作为参数传入递归调用,不要直接把
Add方法的void返回值当参数传 - 修正所有列表拼接逻辑:用
AddRange替代不修改原对象的Concat,或手动接收Concat的返回值赋值给原变量,最后对符号列表去重,避免重复枚举同一个符号 - 修正所有连接词的真值判断逻辑,删掉重复的分支判断
- 递归时不要修改传入的原符号列表,通过跳过首元素的方式生成剩余符号的新列表
- 修正递归终止条件的返回值逻辑
修复后的核心代码
using System.Linq; using System.Collections.Generic; namespace InferenceEngine { internal class TruthTable { TruthTable() { } private List<string> ExtractSymbol(Sentence KB) { List<string> result = new List<string>(); if(KB.symbol != null) { result.Add(KB.symbol); } else { foreach(Sentence s in KB.child) { // 用AddRange直接将递归提取的符号加入当前列表 result.AddRange(ExtractSymbol(s)); } } // 去重避免重复枚举 return result.Distinct().ToList(); } private bool PL_True(Sentence KB, Dictionary<string,bool> model) { if(KB.symbol != null) { return model[KB.symbol]; } else if (KB.connetives == "AND") { // AND要求所有子节点为真才返回真 foreach (Sentence s in KB.child) { if(PL_True(s, model) == false) { return false; } } return true; } else if (KB.connetives == "OR") { // OR只要有一个子节点为真就返回真 foreach (Sentence s in KB.child) { if (PL_True(s, model) == true) { return true; } } return false; } else if (KB.connetives == "IF") { Sentence left = KB.child[0]; Sentence right = KB.child[KB.child.Count - 1]; // 蕴含A->B 仅在A真B假时为假 return !PL_True(left, model) || PL_True(right, model); } else if (KB.connetives == "IFF") { Sentence left = KB.child[0]; Sentence right = KB.child[KB.child.Count - 1]; // 双向蕴含两边真值相等时为真 return PL_True(left, model) == PL_True(right, model); } else if (KB.connetives == "NOT") { Sentence opposite = KB.child[0]; return !PL_True(opposite, model); } return false; } public bool TT_Entails(Sentence KB, Sentence Alpha) { List<string> symbol1 = ExtractSymbol(KB); List<string> symbol2 = ExtractSymbol(Alpha); List<string> symbols = new List<string>(); // 拼接KB和Alpha中的所有符号 symbols.AddRange(symbol1); symbols.AddRange(symbol2); symbols = symbols.Distinct().ToList(); Dictionary<string, bool> table = new(); return TT_Check_All(KB, Alpha, symbols, table ); } private bool TT_Check_All(Sentence KB, Sentence Alpha, List<string> symbol, Dictionary<string, bool> model) { if(symbol.Count == 0) { if(PL_True(KB, model) == true) { return PL_True(Alpha, model); } // KB不成立时当前model不存在反例,返回true return true; } string p = symbol[0]; // 生成剩余符号列表,不修改原传入的列表 List<string> rest = symbol.Skip(1).ToList(); // 为两个递归分支分别创建独立的字典副本,避免数据污染 Dictionary<string, bool> modelTrue = new Dictionary<string, bool>(model); modelTrue.Add(p, true); Dictionary<string, bool> modelFalse = new Dictionary<string, bool>(model); modelFalse.Add(p, false); return TT_Check_All(KB, Alpha, rest, modelTrue) && TT_Check_All(KB, Alpha, rest, modelFalse); } } }
如果项目没有引入LINQ无法使用
Skip、Distinct方法,可以手动写循环实现列表复制、去重逻辑,核心原则是每个递归分支的字典、符号列表都是独立副本,不要直接修改传入的原对象。
内容的提问来源于stack exchange,提问作者Bernard Joshua
相关产品推荐
相关产品推荐

