-
Notifications
You must be signed in to change notification settings - Fork 11
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Items added by ConcurrentObservableDictionary.AddRange() cannot be removed #35
Comments
Thanks for identifying this problem. Good work diagnosing the cause. This implementation of the ConcurrentObservableDictionary is really old too, for historical reasons (I started writing this in 2005) this class mimics the behavior of a list. I'll try to get a fix out soon when I get to my dev PC. For the next major version of this library I'm thinking about moving ConcurrentObservableDictionary to ConcurrentObservableDictionaryList and creating a new ConcurrentObservableDictionary based on ImmutableDictionary, similar to how the current ConcurrentObservableHashSet works, which is a much newer class. |
Remove() also fails if you just use Add(). I didn't pick this up because I'm using strings for keys everywhere in my unit tests, if I use classes instead then the unit tests fail. Damn.... |
Scrub that last comment, I made an error in my unit test modifications |
Hi @TGirlChu I'm having trouble formulating a unit test that reproduces this issue. Would you be able to provide some sample code I can use to write a unit test. I've written the following 2 unit tests, but they are both passing, not failing as expected. First unit test uses a class type for the Dictionary Key that doesn't override Equals() and GetHashCode() [TestMethod]
public void AddRemoveRangeReferenceTypeTest()
{
IEnumerable<KeyValuePair<KeyStringTest, string>> GetIEnumerable()
{
for (int i = 0; i < 10; ++i)
{
yield return new KeyValuePair<KeyStringTest, string>(new KeyStringTest(i.ToString()), i.ToString());
}
}
// Need to convert to array so we have the same objects being used
var itemsToAdd = GetIEnumerable().ToArray();
var dictionary1 = new ConcurrentObservableDictionary<KeyStringTest, string>();
dictionary1.AddRange(itemsToAdd);
Assert.IsTrue(dictionary1.Count == itemsToAdd.Count(), "Count doesn't match number of items added");
foreach (var item in itemsToAdd)
{
Assert.IsTrue(dictionary1.Remove(item.Key), "Problem removing item");
}
Assert.IsTrue(dictionary1.Count == 0, "Not all items removed");
} The KeyStringTest class looks like this: /// <summary>
/// A test class for creating objects to be used as Keys in Dictionary tests.
/// </summary>
public class KeyStringTest
{
public KeyStringTest(string keyValue)
{
KeyValue = keyValue;
}
public string KeyValue { get; }
} Second unit test uses a class type for the Dictionary Key that does override Equals() and GetHashCode() [TestMethod]
public void AddRemoveRangeReferenceTypeWithOverrideTest()
{
IEnumerable<KeyValuePair<KeyStringWithOverride, string>> GetIEnumerable()
{
for (int i = 0; i < 10; ++i)
{
yield return new KeyValuePair<KeyStringWithOverride, string>(new KeyStringWithOverride(i.ToString()), i.ToString());
}
}
// Don't convert to array, testing the use of reference types that override Equals and GetHashCode
var itemsToAdd = GetIEnumerable();
var dictionary1 = new ConcurrentObservableDictionary<KeyStringWithOverride, string>();
dictionary1.AddRange(itemsToAdd);
Assert.IsTrue(dictionary1.Count == itemsToAdd.Count(), "Count doesn't match number of items added");
foreach (var item in itemsToAdd)
{
Assert.IsTrue(dictionary1.Remove(item.Key), "Problem removing item");
}
Assert.IsTrue(dictionary1.Count == 0, "Not all items removed");
}
/// <summary>
/// A test class for creating objects to be used as Keys in Dictionary tests.
/// </summary>
public class KeyStringWithOverride
{
public KeyStringWithOverride(string keyValue)
{
KeyValue = keyValue;
}
public override int GetHashCode()
{
return KeyValue.GetHashCode();
}
public override bool Equals(object obj)
{
if (obj is KeyStringWithOverride ex)
{
return KeyValue.Equals(ex.KeyValue);
}
else
{
return false;
}
}
public string KeyValue { get; }
} The above 2 test code blocks are now in the repo: Line 593 in 147acaf
Line 619 in 147acaf
|
Here is the test method I added in my own repo:
This test case would pass if |
…he IList<T> is something that doesn't inherit from IList, which was happening in some unit tests under .NET 8.0
Thanks @TGirlChu, I reproduced this issue using the unit test you provided, I investigated it, tried a few solutions and ended up on the same solution you did. While working on this issue I also uplifted the source to to .NET Core 8.0 whilst maintaining support for netstandard2.0 and net48, I made a bunch of other small improvements, and I updated all the nuget packages. I'm now starting the automated publish process, which I haven't done in a while so hopefully it goes ok. |
New nuget package release is here https://www.nuget.org/packages/Swordfish.NET.CollectionsV3/3.3.13 |
Thank you for your work! |
While item added to
ConcurrentObservableDictionary
byAdd
can be removed byRemove
/RemoveRange
, those added byAddRange
cannot.I suspect the cause resides in
ConcurrentObservableDictionary.AddRange
. Due to the fact that the enumeration ofnodes
is deferred, two different set of identicalObservableDictionaryNode
are created, one byDictionary.AddRange(dictionaryEntries)
and the otherList.AddRange(nodes)
.Since
ObservableDictionaryNode
is a reference type withoutEquals
/GetHashCode
overrides, items inDictionary
and those inList
will not be considered equal, thus failing the equality check inImmutableList.RemoveRange
.The text was updated successfully, but these errors were encountered: