为什么字典上的 TryGetValue 的键也是返回 Null 的字典?
Why is TryGetValue on a Dictionary who's key is also a dictionary returning Null?
目标:从字典中获取一个值。所述值有一个字典作为键。
我在做什么:我正在创建第二个字典,它具有与我试图获取其值的键完全相同的值。使用 TryGetValue
结果:需要一个值但得到的是空值;
上下文:
我正在尝试在 Unity 中制作制作功能。这是制作成分的 class 的样子(ICombinable 现在看起来完全一样):
public class Ingredient : ICombinable
{
public string Name { get; set; }
public string Description { get; set; }
public Effect Effect { get; set; }
}
实际上,我希望用户能够将 ICombinable 类型的对象拖到 UI(未实现)上,然后按一个按钮将它们组合成一个新项目。例如,2 种草药和 1 杯水 return 治疗药水(新物品)。
在表面背后,我会将 dragged/selected 个对象存储在 Dictionary<ICombinable, int>
中,其中 int 是每个 ICombinable
的数量。
在另一个 class 中,我正在存储另一个将包含所有食谱的词典。
public class Combiner
{
private Dictionary<Dictionary<ICombinable, int>, ICraftable> _recipebook;
public Combiner()
{
_recipebook = new Dictionary<Dictionary<ICombinable, int>, 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 是与该配方相对应的 object/item。在我之前给出的示例中,ICraftable 将是治疗药水,而 2 根棍子和一杯水将分别成为字典中的一个条目,即该值的键。
最后,Craft 方法采用字典(换句话说,成分及其数量列表),我希望它在 _recipebook 中检查与给定字典对应的项目。如果成分组合有效,则应 return 一项,否则为空。
我如何测试此功能:
我刚开始这个项目,所以我想从单元测试开始。这是设置:
[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", Effect = default}, 2}
},
item);
var actualItem = combiner.Craft(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", Effect = default}, 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
class 提供它们,并使用 Recipe
作为实际密钥。
已解决:
我尝试制作自己的 IEqualitycomparer,但无法让我的词典使用我在其中定义的 Equals。所以我做了一个食谱 class 并覆盖了那里的 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 class 以使用 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", Effect = default}, 2}
}
},
potion);
var userGivenIngredientsAndAmount = combiner.Craft(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", Effect = default}, 2}
});
Assert.That(userGivenIngredientsAndAmount, Is.EqualTo(potion));
}
它正在按预期运行。更改其中一本词典中的 Name 或 count 会导致它像它应该的那样 return null 。这可能是非常低效的!但我还处于 'make it work' 阶段。我很快就会做到'make it nice, make it fast'
感谢大家让我走上正轨!我很乐意接受有关提高效率的任何建议。但由于它按预期工作,我将此问题标记为明天已解决。
目标:从字典中获取一个值。所述值有一个字典作为键。
我在做什么:我正在创建第二个字典,它具有与我试图获取其值的键完全相同的值。使用 TryGetValue
结果:需要一个值但得到的是空值;
上下文: 我正在尝试在 Unity 中制作制作功能。这是制作成分的 class 的样子(ICombinable 现在看起来完全一样):
public class Ingredient : ICombinable
{
public string Name { get; set; }
public string Description { get; set; }
public Effect Effect { get; set; }
}
实际上,我希望用户能够将 ICombinable 类型的对象拖到 UI(未实现)上,然后按一个按钮将它们组合成一个新项目。例如,2 种草药和 1 杯水 return 治疗药水(新物品)。
在表面背后,我会将 dragged/selected 个对象存储在 Dictionary<ICombinable, int>
中,其中 int 是每个 ICombinable
的数量。
在另一个 class 中,我正在存储另一个将包含所有食谱的词典。
public class Combiner
{
private Dictionary<Dictionary<ICombinable, int>, ICraftable> _recipebook;
public Combiner()
{
_recipebook = new Dictionary<Dictionary<ICombinable, int>, 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 是与该配方相对应的 object/item。在我之前给出的示例中,ICraftable 将是治疗药水,而 2 根棍子和一杯水将分别成为字典中的一个条目,即该值的键。
最后,Craft 方法采用字典(换句话说,成分及其数量列表),我希望它在 _recipebook 中检查与给定字典对应的项目。如果成分组合有效,则应 return 一项,否则为空。
我如何测试此功能: 我刚开始这个项目,所以我想从单元测试开始。这是设置:
[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", Effect = default}, 2}
},
item);
var actualItem = combiner.Craft(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", Effect = default}, 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
class 提供它们,并使用 Recipe
作为实际密钥。
已解决:
我尝试制作自己的 IEqualitycomparer,但无法让我的词典使用我在其中定义的 Equals。所以我做了一个食谱 class 并覆盖了那里的 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 class 以使用 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", Effect = default}, 2}
}
},
potion);
var userGivenIngredientsAndAmount = combiner.Craft(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", Effect = default}, 2}
});
Assert.That(userGivenIngredientsAndAmount, Is.EqualTo(potion));
}
它正在按预期运行。更改其中一本词典中的 Name 或 count 会导致它像它应该的那样 return null 。这可能是非常低效的!但我还处于 'make it work' 阶段。我很快就会做到'make it nice, make it fast'
感谢大家让我走上正轨!我很乐意接受有关提高效率的任何建议。但由于它按预期工作,我将此问题标记为明天已解决。