问题描述
我在做什么:我正在创建第二个字典,该字典的值与我试图获取其值的键完全相同。使用 TryGetValue
结果:需要一个值但得到空值;
背景: 我正在尝试在 Unity 中制作一个制作功能。这就是制作成分的类的样子(ICombinable 现在看起来完全一样):
public class Ingredient : ICombinable
{
public string Name { get; set; }
public string Description { get; set; }
public Effect Effect { get; set; }
}
在实践中,我希望用户能够将 ICombinable 类型的对象拖到 UI(未实现)上,然后按下按钮将它们组合成一个新项目。例如,2 种草药和 1 杯水会返回治疗药水(新物品)。
在表面后面,我将拖动/选择的对象存储在 Dictionary<ICombinable,int>
中,其中 int 是每个 ICombinable
的数量。
public class Combiner
{
private Dictionary<Dictionary<ICombinable,int>,ICraftable> _recipebook;
public Combiner()
{
_recipebook = new Dictionary<Dictionary<ICombinable,ICraftable>(); // <Recipe,Item it unlocks>
}
public void AddRecipe(Dictionary<ICombinable,int> recipe,ICraftable item) => _recipebook.Add(recipe,item);
public ICraftable Craft(Dictionary<ICombinable,int> ingredientsAndamount) =>
_recipebook.TryGetValue(ingredientsAndamount,out var item) == false ? null : item;
//FirstOrDefault(x => x.Key.requiredComponents.Equals(givenIngredients)).Value;
}
_recipebook 的关键是由成分及其数量组成的实际配方。 ICraftable 是与该配方对应的对象/项目。在我之前给出的 ICraftable 示例中,将是治疗药水,而 2 根棍子和一杯水将分别是字典中的一个条目,该条目是该值的关键。
最后,Craft 方法需要一个字典(换句话说,一个成分列表及其数量),我希望它在 _recipebook 中检查哪个项目与给定的字典对应。如果成分组合有效,则应返回一个项目,否则返回 null。
我如何测试此功能: 我刚开始这个项目,所以我想从单元测试开始。这是设置:
[Test]
public void combiner_should_return_healing_potion()
{
// Use the Assert class to test conditions
var combiner = new Combiner();
var item = new Item
{
Name = "Healing Potion",Unlocked = false
};
combiner.AddRecipe(new Dictionary<ICombinable,int>
{
{new Ingredient {Name = "Herb",Description = "Has healing properties",Effect = Effect.Heal},3},{new Ingredient {Name = "Water",Description = "Spring water",Effect = default},1},{new Ingredient {Name = "Sugar",Description = "Sweetens",2}
},item);
var actualItem = combiner.Craft(new Dictionary<ICombinable,int>
{
{new Ingredient { Name = "Herb",2}
});
Assert.That(actualItem,Is.EqualTo(item));
}
结果:
combiner_should_return_healing_potion (0.023s)
---
Expected: <Models.Item>
But was: null
---
我正在制作一个叫做治疗药水的物品和一本应该是它的配方的字典。我将这些添加到食谱书中。之后,我正在创建第二个字典来“模拟”用户的输入。该词典的内容与我使用 Add 添加到食谱书中的内容完全相同
食谱()。怎么 TryGetValue
不认为这两个字典是平等的?
我该怎么做才能让它发挥作用?
解决方法
因为您要查找的对象在字典中不存在。
您可以很容易地说服自己,只需遍历 Keys
集合并使用 ==
或 Equals
将每个关键字典与搜索到的字典进行比较。
那就是
_recipebook.Count(x => x.Key == ingredientsAndAmount)
您会找到零个匹配项。
解决方案是提供您自己的 Equals
和 HashCode
实现,或者通过注释中建议的 IEqualityComparer
,或者通过将键字典包装到 Recipe
提供它们的类,并使用 Recipe
作为实际的键。
已解决:
我尝试制作自己的 IEqualitycomparer,但无法让我的字典使用我在其中定义的 Equals。所以我创建了一个 Recipe 类并覆盖了那里的 Equals。
public class Recipe : ICraftable
{
public string Name { get; set; }
public string Description { get; set; }
public Dictionary<ICombinable,int> RequiredComponents { get; set; }
public bool Unlocked { get; set; }
public override bool Equals(object obj)
{
var otherDict = obj as Dictionary<ICombinable,int>;
if (ReferenceEquals(otherDict,RequiredComponents)) return true;
if (ReferenceEquals(otherDict,null)) return false;
if (ReferenceEquals(RequiredComponents,null)) return false;
if (otherDict.GetType() != RequiredComponents.GetType()) return false;
return otherDict.Count == RequiredComponents.Count && AreValuesEqual(otherDict,RequiredComponents);
}
private bool AreValuesEqual(Dictionary<ICombinable,int> x,Dictionary<ICombinable,int> y)
{
var matches = 0;
//Goal is to check if all ingredients have the same name on both sides. Then I'll check if they have the same amount
foreach (var xKvp in x)
{
foreach (var yKvp in y)
{
if (xKvp.Key.Name == yKvp.Key.Name && xKvp.Value == yKvp.Value)
matches++;
}
}
return matches == x.Count;
}
}
我从使用 IEqualityComparer 获得的默认 Equals 中复制了前 4 行,并为第 5 行自定义了一行。我的 AreValuesEqual bool 只是通过名称和计数检查其他字典中是否存在所有成分。
我更改了我的 Combiner 类,使用 Recipe 对象作为键而不是 Dictionary 并相应地调整了 AddRecipe 和 Craft 的方法:
public class Combiner
{
private Dictionary<Recipe,ICraftable> _recipebook;
public Combiner()
{
_recipebook = new Dictionary<Recipe,ICraftable>(); // <Recipe,Item it unlocks>
}
public void AddRecipe(Recipe recipe,ICraftable item) => _recipebook.Add(recipe,item);
public ICraftable Craft(Dictionary<ICombinable,int> ingredientsAndAmount) =>
_recipebook.FirstOrDefault(kvp=> kvp.Key.Equals(ingredientsAndAmount)).Value;
}
这就是我设置单元测试的方式:
[Test]
public void combiner_should_return_healing_potion()
{
// Use the Assert class to test conditions
var combiner = new Combiner();
var potion = new Item
{
Name = "Healing Potion",Unlocked = false
};
combiner.AddRecipe(new Recipe
{
Name = "Healing potion recipe",Description = "Invented by some sage",RequiredComponents = new Dictionary<ICombinable,int>
{
{new Ingredient() { Name = "Herb",Description = "Has healing properties",Effect = Effect.Heal},3},{new Ingredient {Name = "Water",Description = "Spring water",Effect = default},1},{new Ingredient {Name = "Sugar",Description = "Sweetens",2}
}
},potion);
var userGivenIngredientsAndAmount = combiner.Craft(new Dictionary<ICombinable,int>()
{
{new Ingredient() { Name = "Herb",2}
});
Assert.That(userGivenIngredientsAndAmount,Is.EqualTo(potion));
}
它正在按预期运行。更改其中一个字典中的名称或计数会导致它像它应该的那样返回 null。这可能是非常低效的!但我仍处于“让它发挥作用”的阶段。我很快就会“让它变得更好,让它变得更快”。
感谢大家让我走上正轨!我很乐意接受任何关于提高效率的建议。但由于它按预期工作,我将这个问题标记为明天已解决。